Repository: metallb/metallb
Pull request #2699 (2025-02-17 02:20:32)

Chore(deps): bump github.com/onsi/ginkgo/v2 from 2.22.0 to 2.22.2
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.22.0 to 2.22.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/onsi/ginkgo/releases">github.com/onsi/ginkgo/v2's releases</a>.</em></p>
<blockquote>
<h2>v2.22.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Bump golang.org/x/net from 0.32.0 to 0.33.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/onsi/ginkgo/pull/1496">onsi/ginkgo#1496</a></li>
<li>Bump golang.org/x/crypto from 0.17.0 to 0.31.0 in /ginkgo/performance/_fixtures/performance_fixture by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/onsi/ginkgo/pull/1498">onsi/ginkgo#1498</a></li>
<li>Bump github.com/onsi/gomega from 1.36.1 to 1.36.2 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/onsi/ginkgo/pull/1499">onsi/ginkgo#1499</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/onsi/ginkgo/compare/v2.22.1...v2.22.2">https://github.com/onsi/ginkgo/compare/v2.22.1...v2.22.2</a></p>
<h2>v2.22.1</h2>
<h2>2.22.1</h2>
<h3>Fixes</h3>
<p>Fix CSV encoding</p>
<ul>
<li>Update tests [aab3da6]</li>
<li>Properly encode CSV rows [c09df39]</li>
<li>Add test case for proper csv escaping [96a80fc]</li>
<li>Add meta-test [43dad69]</li>
</ul>
<h3>Maintenance</h3>
<ul>
<li>ensure *.test files are gitignored so we don't accidentally commit compiled tests again [c88c634]</li>
<li>remove golang.org/x/net/context in favour of stdlib context [4df44bf]</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md">github.com/onsi/ginkgo/v2's changelog</a>.</em></p>
<blockquote>
<h2>2.22.2</h2>
<h3>Maintenance</h3>
<ul>
<li>Bump github.com/onsi/gomega from 1.36.1 to 1.36.2 (<a href="https://redirect.github.com/onsi/ginkgo/issues/1499">#1499</a>) [cc553ce]</li>
<li>Bump golang.org/x/crypto (<a href="https://redirect.github.com/onsi/ginkgo/issues/1498">#1498</a>) [2170370]</li>
<li>Bump golang.org/x/net from 0.32.0 to 0.33.0 (<a href="https://redirect.github.com/onsi/ginkgo/issues/1496">#1496</a>) [a96c44f]</li>
</ul>
<h2>2.22.1</h2>
<h3>Fixes</h3>
<p>Fix CSV encoding</p>
<ul>
<li>Update tests [aab3da6]</li>
<li>Properly encode CSV rows [c09df39]</li>
<li>Add test case for proper csv escaping [96a80fc]</li>
<li>Add meta-test [43dad69]</li>
</ul>
<h3>Maintenance</h3>
<ul>
<li>ensure *.test files are gitignored so we don't accidentally commit compiled tests again [c88c634]</li>
<li>remove golang.org/x/net/context in favour of stdlib context [4df44bf]</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/onsi/ginkgo/commit/f65e80b6237f8705373028b37294c6dde8bdd872"><code>f65e80b</code></a> v2.22.2</li>
<li><a href="https://github.com/onsi/ginkgo/commit/cc553ce3bdd7f6115eb3e8e33b802fc743011c1c"><code>cc553ce</code></a> Bump github.com/onsi/gomega from 1.36.1 to 1.36.2 (<a href="https://redirect.github.com/onsi/ginkgo/issues/1499">#1499</a>)</li>
<li><a href="https://github.com/onsi/ginkgo/commit/217037065572c77908d2b19f3d5dbfd9fa5ab462"><code>2170370</code></a> Bump golang.org/x/crypto (<a href="https://redirect.github.com/onsi/ginkgo/issues/1498">#1498</a>)</li>
<li><a href="https://github.com/onsi/ginkgo/commit/a96c44fb472ad013fd461c6f711c6875b412f250"><code>a96c44f</code></a> Bump golang.org/x/net from 0.32.0 to 0.33.0 (<a href="https://redirect.github.com/onsi/ginkgo/issues/1496">#1496</a>)</li>
<li><a href="https://github.com/onsi/ginkgo/commit/a0190b7b682b2480b027235f5933060c53da5534"><code>a0190b7</code></a> v2.22.1</li>
<li><a href="https://github.com/onsi/ginkgo/commit/4df44bf25090b3e508b64f4f7c6712bef6d8182d"><code>4df44bf</code></a> remove golang.org/x/net/context in favour of stdlib context</li>
<li><a href="https://github.com/onsi/ginkgo/commit/c88c6348d29c42f569e13f987462fb81558d0240"><code>c88c634</code></a> ensure *.test files are gitignored so we don't accidentally commit compiled t...</li>
<li><a href="https://github.com/onsi/ginkgo/commit/aab3da6a39d34de4ae7f1342437e9a7505f569eb"><code>aab3da6</code></a> Update tests</li>
<li><a href="https://github.com/onsi/ginkgo/commit/c09df39f4fd30b7484c825e621f76c1bf58844eb"><code>c09df39</code></a> Properly encode CSV rows</li>
<li><a href="https://github.com/onsi/ginkgo/commit/96a80fc4c807c2fa4d38bdbd4cc2f854bb19e743"><code>96a80fc</code></a> Add test case for proper csv escaping</li>
<li>Additional commits viewable in <a href="https://github.com/onsi/ginkgo/compare/v2.22.0...v2.22.2">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/onsi/ginkgo/v2&package-manager=go_modules&previous-version=2.22.0&new-version=2.22.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/api/v1beta2/bgppeer_types.go b/api/v1beta2/bgppeer_types.go
index 3269439d..d984f5ee 100644
--- a/api/v1beta2/bgppeer_types.go
+++ b/api/v1beta2/bgppeer_types.go
@@ -45,7 +45,16 @@ type BGPPeerSpec struct {
 	DynamicASN DynamicASNMode `json:"dynamicASN,omitempty"`
 
 	// Address to dial when establishing the session.
-	Address string `json:"peerAddress"`
+	// +optional
+	Address string `json:"peerAddress,omitempty"`
+
+	// Interface is the node interface over which the unnumbered BGP peering will
+	// be established. No API validation takes place as that string value
+	// represents an interface name on the host and if user provides an invalid
+	// value, only the actual BGP session will not be established.
+	// Address and Interface are mutually exclusive and one of them must be specified.
+	// +optional
+	Interface string `json:"interface,omitempty"`
 
 	// Source address to use when establishing the session.
 	// +optional
@@ -53,7 +62,7 @@ type BGPPeerSpec struct {
 
 	// Port to dial when establishing the session.
 	// +optional
-	// +kubebuilder:validation:Minimum=0
+	// +kubebuilder:validation:Minimum=1
 	// +kubebuilder:validation:Maximum=16384
 	// +kubebuilder:default:=179
 	Port uint16 `json:"peerPort,omitempty"`
diff --git a/charts/metallb/charts/crds/templates/crds.yaml b/charts/metallb/charts/crds/templates/crds.yaml
index 383c8fea..e8527f94 100644
--- a/charts/metallb/charts/crds/templates/crds.yaml
+++ b/charts/metallb/charts/crds/templates/crds.yaml
@@ -555,6 +555,14 @@ spec:
                 holdTime:
                   description: Requested BGP hold time, per RFC4271.
                   type: string
+                interface:
+                  description: |-
+                    Interface is the node interface over which the unnumbered BGP peering will
+                    be established. No API validation takes place as that string value
+                    represents an interface name on the host and if user provides an invalid
+                    value, only the actual BGP session will not be established.
+                    Address and Interface are mutually exclusive and one of them must be specified.
+                  type: string
                 keepaliveTime:
                   description: Requested BGP keepalive time, per RFC4271.
                   type: string
@@ -649,7 +657,7 @@ spec:
                   default: 179
                   description: Port to dial when establishing the session.
                   maximum: 16384
-                  minimum: 0
+                  minimum: 1
                   type: integer
                 routerID:
                   description: BGP router ID to advertise to the peer
@@ -664,7 +672,6 @@ spec:
                   type: string
               required:
                 - myASN
-                - peerAddress
               type: object
             status:
               description: BGPPeerStatus defines the observed state of Peer.
diff --git a/config/crd/bases/metallb.io_bgppeers.yaml b/config/crd/bases/metallb.io_bgppeers.yaml
index 1b740778..3fd406d0 100644
--- a/config/crd/bases/metallb.io_bgppeers.yaml
+++ b/config/crd/bases/metallb.io_bgppeers.yaml
@@ -224,6 +224,14 @@ spec:
               holdTime:
                 description: Requested BGP hold time, per RFC4271.
                 type: string
+              interface:
+                description: |-
+                  Interface is the node interface over which the unnumbered BGP peering will
+                  be established. No API validation takes place as that string value
+                  represents an interface name on the host and if user provides an invalid
+                  value, only the actual BGP session will not be established.
+                  Address and Interface are mutually exclusive and one of them must be specified.
+                type: string
               keepaliveTime:
                 description: Requested BGP keepalive time, per RFC4271.
                 type: string
@@ -323,7 +331,7 @@ spec:
                 default: 179
                 description: Port to dial when establishing the session.
                 maximum: 16384
-                minimum: 0
+                minimum: 1
                 type: integer
               routerID:
                 description: BGP router ID to advertise to the peer
@@ -338,7 +346,6 @@ spec:
                 type: string
             required:
             - myASN
-            - peerAddress
             type: object
           status:
             description: BGPPeerStatus defines the observed state of Peer.
diff --git a/config/manifests/metallb-frr-k8s-prometheus.yaml b/config/manifests/metallb-frr-k8s-prometheus.yaml
index ef1d2082..3d6ed07f 100644
--- a/config/manifests/metallb-frr-k8s-prometheus.yaml
+++ b/config/manifests/metallb-frr-k8s-prometheus.yaml
@@ -582,6 +582,14 @@ spec:
               holdTime:
                 description: Requested BGP hold time, per RFC4271.
                 type: string
+              interface:
+                description: |-
+                  Interface is the node interface over which the unnumbered BGP peering will
+                  be established. No API validation takes place as that string value
+                  represents an interface name on the host and if user provides an invalid
+                  value, only the actual BGP session will not be established.
+                  Address and Interface are mutually exclusive and one of them must be specified.
+                type: string
               keepaliveTime:
                 description: Requested BGP keepalive time, per RFC4271.
                 type: string
@@ -681,7 +689,7 @@ spec:
                 default: 179
                 description: Port to dial when establishing the session.
                 maximum: 16384
-                minimum: 0
+                minimum: 1
                 type: integer
               routerID:
                 description: BGP router ID to advertise to the peer
@@ -696,7 +704,6 @@ spec:
                 type: string
             required:
             - myASN
-            - peerAddress
             type: object
           status:
             description: BGPPeerStatus defines the observed state of Peer.
diff --git a/config/manifests/metallb-frr-k8s.yaml b/config/manifests/metallb-frr-k8s.yaml
index 29f78f49..69483c67 100644
--- a/config/manifests/metallb-frr-k8s.yaml
+++ b/config/manifests/metallb-frr-k8s.yaml
@@ -582,6 +582,14 @@ spec:
               holdTime:
                 description: Requested BGP hold time, per RFC4271.
                 type: string
+              interface:
+                description: |-
+                  Interface is the node interface over which the unnumbered BGP peering will
+                  be established. No API validation takes place as that string value
+                  represents an interface name on the host and if user provides an invalid
+                  value, only the actual BGP session will not be established.
+                  Address and Interface are mutually exclusive and one of them must be specified.
+                type: string
               keepaliveTime:
                 description: Requested BGP keepalive time, per RFC4271.
                 type: string
@@ -681,7 +689,7 @@ spec:
                 default: 179
                 description: Port to dial when establishing the session.
                 maximum: 16384
-                minimum: 0
+                minimum: 1
                 type: integer
               routerID:
                 description: BGP router ID to advertise to the peer
@@ -696,7 +704,6 @@ spec:
                 type: string
             required:
             - myASN
-            - peerAddress
             type: object
           status:
             description: BGPPeerStatus defines the observed state of Peer.
diff --git a/config/manifests/metallb-frr-prometheus.yaml b/config/manifests/metallb-frr-prometheus.yaml
index d6203147..b54b73e5 100644
--- a/config/manifests/metallb-frr-prometheus.yaml
+++ b/config/manifests/metallb-frr-prometheus.yaml
@@ -582,6 +582,14 @@ spec:
               holdTime:
                 description: Requested BGP hold time, per RFC4271.
                 type: string
+              interface:
+                description: |-
+                  Interface is the node interface over which the unnumbered BGP peering will
+                  be established. No API validation takes place as that string value
+                  represents an interface name on the host and if user provides an invalid
+                  value, only the actual BGP session will not be established.
+                  Address and Interface are mutually exclusive and one of them must be specified.
+                type: string
               keepaliveTime:
                 description: Requested BGP keepalive time, per RFC4271.
                 type: string
@@ -681,7 +689,7 @@ spec:
                 default: 179
                 description: Port to dial when establishing the session.
                 maximum: 16384
-                minimum: 0
+                minimum: 1
                 type: integer
               routerID:
                 description: BGP router ID to advertise to the peer
@@ -696,7 +704,6 @@ spec:
                 type: string
             required:
             - myASN
-            - peerAddress
             type: object
           status:
             description: BGPPeerStatus defines the observed state of Peer.
diff --git a/config/manifests/metallb-frr.yaml b/config/manifests/metallb-frr.yaml
index 156f4e48..c3d7c303 100644
--- a/config/manifests/metallb-frr.yaml
+++ b/config/manifests/metallb-frr.yaml
@@ -582,6 +582,14 @@ spec:
               holdTime:
                 description: Requested BGP hold time, per RFC4271.
                 type: string
+              interface:
+                description: |-
+                  Interface is the node interface over which the unnumbered BGP peering will
+                  be established. No API validation takes place as that string value
+                  represents an interface name on the host and if user provides an invalid
+                  value, only the actual BGP session will not be established.
+                  Address and Interface are mutually exclusive and one of them must be specified.
+                type: string
               keepaliveTime:
                 description: Requested BGP keepalive time, per RFC4271.
                 type: string
@@ -681,7 +689,7 @@ spec:
                 default: 179
                 description: Port to dial when establishing the session.
                 maximum: 16384
-                minimum: 0
+                minimum: 1
                 type: integer
               routerID:
                 description: BGP router ID to advertise to the peer
@@ -696,7 +704,6 @@ spec:
                 type: string
             required:
             - myASN
-            - peerAddress
             type: object
           status:
             description: BGPPeerStatus defines the observed state of Peer.
diff --git a/config/manifests/metallb-native-prometheus.yaml b/config/manifests/metallb-native-prometheus.yaml
index 73076c3d..effb028a 100644
--- a/config/manifests/metallb-native-prometheus.yaml
+++ b/config/manifests/metallb-native-prometheus.yaml
@@ -582,6 +582,14 @@ spec:
               holdTime:
                 description: Requested BGP hold time, per RFC4271.
                 type: string
+              interface:
+                description: |-
+                  Interface is the node interface over which the unnumbered BGP peering will
+                  be established. No API validation takes place as that string value
+                  represents an interface name on the host and if user provides an invalid
+                  value, only the actual BGP session will not be established.
+                  Address and Interface are mutually exclusive and one of them must be specified.
+                type: string
               keepaliveTime:
                 description: Requested BGP keepalive time, per RFC4271.
                 type: string
@@ -681,7 +689,7 @@ spec:
                 default: 179
                 description: Port to dial when establishing the session.
                 maximum: 16384
-                minimum: 0
+                minimum: 1
                 type: integer
               routerID:
                 description: BGP router ID to advertise to the peer
@@ -696,7 +704,6 @@ spec:
                 type: string
             required:
             - myASN
-            - peerAddress
             type: object
           status:
             description: BGPPeerStatus defines the observed state of Peer.
diff --git a/config/manifests/metallb-native.yaml b/config/manifests/metallb-native.yaml
index 22db4d36..3e3d6853 100644
--- a/config/manifests/metallb-native.yaml
+++ b/config/manifests/metallb-native.yaml
@@ -582,6 +582,14 @@ spec:
               holdTime:
                 description: Requested BGP hold time, per RFC4271.
                 type: string
+              interface:
+                description: |-
+                  Interface is the node interface over which the unnumbered BGP peering will
+                  be established. No API validation takes place as that string value
+                  represents an interface name on the host and if user provides an invalid
+                  value, only the actual BGP session will not be established.
+                  Address and Interface are mutually exclusive and one of them must be specified.
+                type: string
               keepaliveTime:
                 description: Requested BGP keepalive time, per RFC4271.
                 type: string
@@ -681,7 +689,7 @@ spec:
                 default: 179
                 description: Port to dial when establishing the session.
                 maximum: 16384
-                minimum: 0
+                minimum: 1
                 type: integer
               routerID:
                 description: BGP router ID to advertise to the peer
@@ -696,7 +704,6 @@ spec:
                 type: string
             required:
             - myASN
-            - peerAddress
             type: object
           status:
             description: BGPPeerStatus defines the observed state of Peer.
diff --git a/go.mod b/go.mod
index e675e81f..8fdd9863 100644
--- a/go.mod
+++ b/go.mod
@@ -12,8 +12,8 @@ require (
 	github.com/mdlayher/ndp v0.0.0-20200602162440-17ab9e3e5567
 	github.com/metallb/frr-k8s v0.0.17
 	github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721
-	github.com/onsi/ginkgo/v2 v2.22.0
-	github.com/onsi/gomega v1.36.1
+	github.com/onsi/ginkgo/v2 v2.22.2
+	github.com/onsi/gomega v1.36.2
 	github.com/open-policy-agent/cert-controller v0.10.2-0.20240531181455-2649f121ab97
 	github.com/ory/dockertest/v3 v3.10.0
 	github.com/prometheus/client_golang v1.20.5
@@ -60,7 +60,7 @@ require (
 	github.com/google/btree v1.1.3 // indirect
 	github.com/google/gnostic-models v0.6.9 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
-	github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
+	github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 	github.com/google/uuid v1.6.0 // indirect
 	github.com/hashicorp/errwrap v1.0.0 // indirect
@@ -103,14 +103,14 @@ require (
 	gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f // indirect
 	go.uber.org/atomic v1.11.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/mod v0.21.0 // indirect
+	golang.org/x/mod v0.22.0 // indirect
 	golang.org/x/net v0.35.0 // indirect
 	golang.org/x/oauth2 v0.26.0 // indirect
 	golang.org/x/sync v0.11.0 // indirect
 	golang.org/x/term v0.29.0 // indirect
 	golang.org/x/text v0.22.0 // indirect
 	golang.org/x/time v0.10.0 // indirect
-	golang.org/x/tools v0.26.0 // indirect
+	golang.org/x/tools v0.28.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
 	google.golang.org/protobuf v1.36.5 // indirect
 	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
diff --git a/go.sum b/go.sum
index 4f7934bc..ff987363 100644
--- a/go.sum
+++ b/go.sum
@@ -96,8 +96,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
-github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
+github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
+github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -185,10 +185,10 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
-github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
-github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
-github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
+github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
+github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
+github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
+github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
 github.com/open-policy-agent/cert-controller v0.10.2-0.20240531181455-2649f121ab97 h1:otk1yQtHleQq5VIy5C+rAYnErOT/WrvDyplqPhYDqb8=
 github.com/open-policy-agent/cert-controller v0.10.2-0.20240531181455-2649f121ab97/go.mod h1:ZoyStAh4D97A4+k4BUj/Q/eyqyeIR4gl/NuVGAWUhvQ=
 github.com/open-policy-agent/frameworks/constraint v0.0.0-20230822235116-f0b62fe1e4c4 h1:5dum5SLEz+95JDLkMls7Z7IDPjvSq3UhJSFe4f5einQ=
@@ -278,8 +278,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
-golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
+golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -333,8 +333,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
-golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
+golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
+golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/bgp/bgp.go b/internal/bgp/bgp.go
index 78fb2d9b..1daf58d9 100644
--- a/internal/bgp/bgp.go
+++ b/internal/bgp/bgp.go
@@ -64,6 +64,8 @@ type Session interface {
 
 type SessionParameters struct {
 	PeerAddress     string
+	PeerPort        uint16
+	PeerInterface   string
 	SourceAddress   net.IP
 	MyASN           uint32
 	RouterID        net.IP
diff --git a/internal/bgp/frr/frr.go b/internal/bgp/frr/frr.go
index 492ece0a..fa8c35e5 100644
--- a/internal/bgp/frr/frr.go
+++ b/internal/bgp/frr/frr.go
@@ -7,7 +7,6 @@ import (
 	"net"
 	"os"
 	"sort"
-	"strconv"
 	"strings"
 	"sync"
 	"time"
@@ -254,17 +253,7 @@ func (sm *sessionManager) createConfig() (*frrConfig, error) {
 
 		neighborName := NeighborName(s.PeerAddress, s.PeerASN, s.DynamicASN, s.VRFName)
 		if neighbor, exist = rout.neighbors[neighborName]; !exist {
-			host, port, err := net.SplitHostPort(s.PeerAddress)
-			if err != nil {
-				return nil, err
-			}
-
-			portUint, err := strconv.ParseUint(port, 10, 16)
-			if err != nil {
-				return nil, err
-			}
-
-			family := ipfamily.ForAddress(net.ParseIP(host))
+			family := ipfamily.ForAddress(net.ParseIP(s.PeerAddress))
 
 			var connectTime int64
 			if s.ConnectTime != nil {
@@ -286,8 +275,8 @@ func (sm *sessionManager) createConfig() (*frrConfig, error) {
 				Name:            neighborName,
 				IPFamily:        family,
 				ASN:             asnFor(s.PeerASN, s.DynamicASN),
-				Addr:            host,
-				Port:            uint16(portUint),
+				Addr:            s.PeerAddress,
+				Port:            s.PeerPort,
 				HoldTime:        holdTime,
 				KeepaliveTime:   keepaliveTime,
 				ConnectTime:     connectTime,
diff --git a/internal/bgp/frrk8s/frrk8s.go b/internal/bgp/frrk8s/frrk8s.go
index b0d9cf1c..9cec413d 100644
--- a/internal/bgp/frrk8s/frrk8s.go
+++ b/internal/bgp/frrk8s/frrk8s.go
@@ -6,7 +6,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"net"
 	"reflect"
 	"sort"
 	"strconv"
@@ -48,13 +47,23 @@ type session struct {
 	logger         log.Logger
 }
 
-// sessionName() defines the format of the key of the 'sessions' map in
-// the 'frrState' struct.
+// sessionName() defines the format of a map key.
 func sessionName(s session) string {
-	baseName := fmt.Sprintf("%d@%s-%d@%s", s.PeerASN, s.PeerAddress, s.MyASN, s.SourceAddress)
+	asn := strconv.FormatUint(uint64(s.PeerASN), 10)
+	if s.DynamicASN != "" {
+		asn = s.DynamicASN
+	}
+
+	peerName := s.PeerAddress
+	if s.PeerInterface != "" {
+		peerName = s.PeerInterface
+	}
+
+	baseName := fmt.Sprintf("%s@%s-%d@%s", asn, peerName, s.MyASN, s.SourceAddress)
 	if s.VRFName == "" {
 		return baseName
 	}
+
 	return baseName + "/" + s.VRFName
 }
 
@@ -227,19 +236,8 @@ func (sm *sessionManager) updateConfig() error {
 			routers[routerName] = rout
 		}
 
-		neighborName := frr.NeighborName(s.PeerAddress, s.PeerASN, s.DynamicASN, s.VRFName)
+		neighborName := sessionName(*s)
 		if neighbor, exist = rout.neighbors[neighborName]; !exist {
-			host, port, err := net.SplitHostPort(s.PeerAddress)
-			if err != nil {
-				return err
-			}
-
-			portUint, err := strconv.ParseUint(port, 10, 16)
-			if err != nil {
-				return err
-			}
-			portUint16 := uint16(portUint)
-
 			if !reflect.DeepEqual(s.PasswordRef, corev1.SecretReference{}) && s.Password != "" {
 				return fmt.Errorf("invalid session with password and secret set: %s", sessionName(*s))
 			}
@@ -259,10 +257,11 @@ func (sm *sessionManager) updateConfig() error {
 			}
 
 			neighbor = frrv1beta1.Neighbor{
+				Address:               s.PeerAddress,
+				Interface:             s.PeerInterface,
 				ASN:                   s.PeerASN,
 				DynamicASN:            frrv1beta1.DynamicASNMode(s.DynamicASN),
-				Address:               host,
-				Port:                  &portUint16,
+				Port:                  &s.PeerPort,
 				HoldTime:              holdTime,
 				KeepaliveTime:         keepaliveTime,
 				ConnectTime:           connectTime,
diff --git a/internal/bgp/native/native.go b/internal/bgp/native/native.go
index eea36036..9413d3a0 100644
--- a/internal/bgp/native/native.go
+++ b/internal/bgp/native/native.go
@@ -44,6 +44,9 @@ type session struct {
 	nextHop        net.IP
 	advertised     map[string]*bgp.Advertisement
 	new            map[string]*bgp.Advertisement
+
+	// peerName identifies this BGP session to be used for metrics
+	peerName string
 }
 
 // The 'Native' implementation does not require a session manager .
@@ -71,6 +74,7 @@ func (sm *sessionManager) NewSession(l log.Logger, args bgp.SessionParameters) (
 		logger:            log.With(l, "peer", args.PeerAddress, "localASN", args.MyASN, "peerASN", args.PeerASN),
 		newHoldTime:       make(chan bool, 1),
 		advertised:        map[string]*bgp.Advertisement{},
+		peerName:          fmt.Sprintf("%s:%d", args.PeerAddress, args.PeerPort),
 	}
 	ret.cond = sync.NewCond(&ret.mu)
 	go ret.sendKeepalives()
@@ -100,7 +104,7 @@ func (sm *sessionManager) SetEventCallback(func(interface{})) {}
 
 // run tries to stay connected to the peer, and pumps route updates to it.
 func (s *session) run() {
-	defer stats.DeleteSession(s.PeerAddress)
+	defer stats.DeleteSession(s.peerName)
 	for {
 		if err := s.connect(); err != nil {
 			if err == errClosed {
@@ -111,7 +115,7 @@ func (s *session) run() {
 			time.Sleep(backoff)
 			continue
 		}
-		stats.SessionUp(s.PeerAddress)
+		stats.SessionUp(s.peerName)
 		s.backoff.Reset()
 
 		level.Info(s.logger).Log("event", "sessionUp", "msg", "BGP session established")
@@ -119,7 +123,7 @@ func (s *session) run() {
 		if !s.sendUpdates() {
 			return
 		}
-		stats.SessionDown(s.PeerAddress)
+		stats.SessionDown(s.peerName)
 		level.Warn(s.logger).Log("event", "sessionDown", "msg", "BGP session down")
 	}
 }
@@ -150,9 +154,9 @@ func (s *session) sendUpdates() bool {
 			level.Error(s.logger).Log("op", "sendUpdate", "ip", c, "error", err, "msg", "failed to send BGP update")
 			return true
 		}
-		stats.UpdateSent(s.PeerAddress)
+		stats.UpdateSent(s.peerName)
 	}
-	stats.AdvertisedPrefixes(s.PeerAddress, len(s.advertised))
+	stats.AdvertisedPrefixes(s.peerName, len(s.advertised))
 
 	for {
 		for s.new == nil && s.conn != nil {
@@ -183,7 +187,7 @@ func (s *session) sendUpdates() bool {
 				level.Error(s.logger).Log("op", "sendUpdate", "prefix", c, "error", err, "msg", "failed to send BGP update")
 				return true
 			}
-			stats.UpdateSent(s.PeerAddress)
+			stats.UpdateSent(s.peerName)
 		}
 
 		wdr := []*net.IPNet{}
@@ -200,10 +204,10 @@ func (s *session) sendUpdates() bool {
 				}
 				return true
 			}
-			stats.UpdateSent(s.PeerAddress)
+			stats.UpdateSent(s.peerName)
 		}
 		s.advertised, s.new = s.new, nil
-		stats.AdvertisedPrefixes(s.PeerAddress, len(s.advertised))
+		stats.AdvertisedPrefixes(s.peerName, len(s.advertised))
 	}
 }
 
@@ -220,7 +224,7 @@ func (s *session) connect() error {
 	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 	defer cancel()
 	deadline, _ := ctx.Deadline()
-	conn, err := dialMD5(ctx, s.PeerAddress, s.SourceAddress, s.Password)
+	conn, err := dialMD5(ctx, fmt.Sprintf("%s:%d", s.PeerAddress, s.PeerPort), s.SourceAddress, s.Password)
 	if err != nil {
 		return fmt.Errorf("dial %q: %s", s.PeerAddress, err)
 	}
@@ -473,7 +477,8 @@ func (s *session) Set(advs ...*bgp.Advertisement) error {
 	}
 
 	s.new = newAdvs
-	stats.PendingPrefixes(s.PeerAddress, len(s.new))
+
+	stats.PendingPrefixes(s.peerName, len(s.new))
 	s.cond.Broadcast()
 
 	return nil
@@ -485,13 +490,14 @@ func (s *session) abort() {
 	if s.conn != nil {
 		s.conn.Close()
 		s.conn = nil
-		stats.SessionDown(s.PeerAddress)
+		stats.SessionDown(s.peerName)
 	}
 	// Next time we retry the connection, we can just skip straight to
 	// the desired end state.
 	if s.new != nil {
 		s.advertised, s.new = s.new, nil
-		stats.PendingPrefixes(s.PeerAddress, len(s.advertised))
+
+		stats.PendingPrefixes(s.peerName, len(s.advertised))
 	}
 	s.cond.Broadcast()
 }
diff --git a/internal/bgp/native/stats.go b/internal/bgp/native/stats.go
index 001d469f..616ed78d 100644
--- a/internal/bgp/native/stats.go
+++ b/internal/bgp/native/stats.go
@@ -53,13 +53,6 @@ func init() {
 	prometheus.MustRegister(stats.pendingPrefixes)
 }
 
-func (m *metrics) NewSession(addr string) {
-	m.sessionUp.WithLabelValues(addr).Set(0)
-	m.prefixes.WithLabelValues(addr).Set(0)
-	m.pendingPrefixes.WithLabelValues(addr).Set(0)
-	m.updatesSent.WithLabelValues(addr).Add(0) // just creates the metric
-}
-
 func (m *metrics) DeleteSession(addr string) {
 	m.sessionUp.DeleteLabelValues(addr)
 	m.prefixes.DeleteLabelValues(addr)
diff --git a/internal/config/config.go b/internal/config/config.go
index 99bd24b8..c6d28be1 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -102,6 +102,9 @@ type Peer struct {
 	DynamicASN string
 	// Address to dial when establishing the session.
 	Addr net.IP
+	// Iface is the Interface to use for Unnumbered BGP peering.
+	// Addr field must be nil.
+	Iface string
 	// Source address to use when establishing the session.
 	SrcAddr net.IP
 	// Port to dial when establishing the session.
@@ -280,7 +283,7 @@ func peersFor(resources ClusterResources, BFDProfiles map[string]*BFDProfile) (m
 	for _, p := range resources.Peers {
 		peer, err := peerFromCR(p, resources.PasswordSecrets)
 		if err != nil {
-			return nil, errors.Join(err, fmt.Errorf("parsing peer %s", p.Name))
+			return nil, fmt.Errorf("parsing peer %s %w", p.Name, err)
 		}
 		if peer.BFDProfile != "" {
 			if _, ok := BFDProfiles[peer.BFDProfile]; !ok {
@@ -398,9 +401,12 @@ func peerFromCR(p metallbv1beta2.BGPPeer, passwordSecrets map[string]corev1.Secr
 	if p.Spec.ASN == p.Spec.MyASN && p.Spec.EBGPMultiHop {
 		return nil, errors.New("invalid ebgp-multihop parameter set for an ibgp peer")
 	}
-	ip := net.ParseIP(p.Spec.Address)
-	if ip == nil {
-		return nil, fmt.Errorf("invalid BGPPeer address %q", p.Spec.Address)
+	if p.Spec.Address == "" && p.Spec.Interface == "" {
+		return nil, fmt.Errorf("peer has no Address or Interface specified")
+	}
+
+	if p.Spec.Address != "" && p.Spec.Interface != "" {
+		return nil, fmt.Errorf("peer has both Address and Interface specified")
 	}
 
 	holdTime, keepaliveTime, err := parseTimers(p.Spec.HoldTime, p.Spec.KeepaliveTime)
@@ -408,6 +414,14 @@ func peerFromCR(p metallbv1beta2.BGPPeer, passwordSecrets map[string]corev1.Secr
 		return nil, fmt.Errorf("invalid BGPPeer timers: %w", err)
 	}
 
+	var ip net.IP
+	if p.Spec.Address != "" {
+		ip = net.ParseIP(p.Spec.Address)
+		if ip == nil {
+			return nil, fmt.Errorf("invalid BGPPeer address %q", p.Spec.Address)
+		}
+	}
+
 	// Ideally we would set a default RouterID here, instead of having
 	// to do it elsewhere in the code. Unfortunately, we don't know
 	// the node IP here.
@@ -464,6 +478,7 @@ func peerFromCR(p metallbv1beta2.BGPPeer, passwordSecrets map[string]corev1.Secr
 		ASN:                   p.Spec.ASN,
 		DynamicASN:            string(p.Spec.DynamicASN),
 		Addr:                  ip,
+		Iface:                 p.Spec.Interface,
 		SrcAddr:               src,
 		Port:                  p.Spec.Port,
 		HoldTime:              holdTime,
diff --git a/internal/config/validation.go b/internal/config/validation.go
index 2d1759e1..66ece4b7 100644
--- a/internal/config/validation.go
+++ b/internal/config/validation.go
@@ -54,6 +54,9 @@ func DiscardFRROnly(c ClusterResources) error {
 		if p.Spec.DynamicASN != "" {
 			return fmt.Errorf("peer %s has dynamicASN set on native bgp mode", p.Spec.Address)
 		}
+		if p.Spec.Interface != "" {
+			return fmt.Errorf("peer %s has interface set on native bgp mode", p.Spec.Address)
+		}
 	}
 	if len(c.BFDProfiles) > 0 {
 		return errors.New("bfd profiles section set")
diff --git a/speaker/bgp_controller.go b/speaker/bgp_controller.go
index 47061ef6..655e1b88 100644
--- a/speaker/bgp_controller.go
+++ b/speaker/bgp_controller.go
@@ -19,7 +19,6 @@ import (
 	"net"
 	"reflect"
 	"sort"
-	"strconv"
 	"sync"
 
 	"github.com/go-kit/log"
@@ -59,6 +58,7 @@ const (
 type peer struct {
 	cfg     *config.Peer
 	session bgp.Session
+	id      string
 }
 
 type bgpController struct {
@@ -90,9 +90,15 @@ newPeers:
 				continue newPeers
 			}
 		}
+		id := p.Iface
+		if p.Addr != nil {
+			id = p.Addr.String()
+		}
+
 		// No existing peers match, create a new one.
 		newPeers = append(newPeers, &peer{
 			cfg: p,
+			id:  id,
 		})
 	}
 
@@ -103,14 +109,14 @@ newPeers:
 		if p == nil {
 			continue
 		}
-		level.Info(l).Log("event", "peerRemoved", "peer", p.cfg.Addr, "reason", "removedFromConfig", "msg", "peer deconfigured, closing BGP session")
+		level.Info(l).Log("event", "peerRemoved", "peer", p.id, "reason", "removedFromConfig", "msg", "peer deconfigured, closing BGP session")
 
 		if p.session != nil {
 			if err := p.session.Close(); err != nil {
-				level.Error(l).Log("op", "setConfig", "error", err, "peer", p.cfg.Addr, "msg", "failed to shut down BGP session")
+				level.Error(l).Log("op", "setConfig", "error", err, "peer", p.id, "msg", "failed to shut down BGP session")
 			}
 		}
-		level.Debug(l).Log("event", "peerRemoved", "peer", p.cfg.Addr, "reason", "removedFromConfig", "msg", "peer deconfigured, BGP session closed")
+		level.Debug(l).Log("event", "peerRemoved", "peer", p.id, "reason", "removedFromConfig", "msg", "peer deconfigured, BGP session closed")
 	}
 
 	err := c.syncBFDProfiles(cfg.BFDProfiles)
@@ -222,22 +228,28 @@ func (c *bgpController) syncPeers(l log.Logger) error {
 		// Now, compare current state to intended state, and correct.
 		if p.session != nil && !shouldRun {
 			// Oops, session is running but shouldn't be. Shut it down.
-			level.Info(l).Log("event", "peerRemoved", "peer", p.cfg.Addr, "reason", "filteredByNodeSelector", "msg", "peer deconfigured, closing BGP session")
+			level.Info(l).Log("event", "peerRemoved", "peer", p.id, "reason", "filteredByNodeSelector", "msg", "peer deconfigured, closing BGP session")
 			if err := p.session.Close(); err != nil {
-				level.Error(l).Log("op", "syncPeers", "error", err, "peer", p.cfg.Addr, "msg", "failed to shut down BGP session")
+				level.Error(l).Log("op", "syncPeers", "error", err, "peer", p.id, "msg", "failed to shut down BGP session")
 			}
 			p.session = nil
 		} else if p.session == nil && shouldRun {
 			// Session doesn't exist, but should be running. Create
 			// it.
-			level.Info(l).Log("event", "peerAdded", "peer", p.cfg.Addr, "msg", "peer configured, starting BGP session")
+			level.Info(l).Log("event", "peerAdded", "peer", p.id, "msg", "peer configured, starting BGP session")
 			var routerID net.IP
 			if p.cfg.RouterID != nil {
 				routerID = p.cfg.RouterID
 			}
 
+			peerAddr := "" // we need because otherwise the value will "<nil>"
+			if p.cfg.Addr != nil {
+				peerAddr = p.cfg.Addr.String()
+			}
 			sessionParams := bgp.SessionParameters{
-				PeerAddress:     net.JoinHostPort(p.cfg.Addr.String(), strconv.Itoa(int(p.cfg.Port))),
+				PeerAddress:     peerAddr,
+				PeerPort:        p.cfg.Port,
+				PeerInterface:   p.cfg.Iface,
 				SourceAddress:   p.cfg.SrcAddr,
 				MyASN:           p.cfg.MyASN,
 				RouterID:        routerID,
@@ -259,7 +271,7 @@ func (c *bgpController) syncPeers(l log.Logger) error {
 			s, err := c.sessionManager.NewSession(c.logger, sessionParams)
 
 			if err != nil {
-				level.Error(l).Log("op", "syncPeers", "error", err, "peer", p.cfg.Addr, "msg", "failed to create BGP session")
+				level.Error(l).Log("op", "syncPeers", "error", err, "peer", p.id, "msg", "failed to create BGP session")
 				errs++
 			} else {
 				p.session = s
diff --git a/website/content/apis/_index.md b/website/content/apis/_index.md
index 6ea3adc4..91ae9eb6 100644
--- a/website/content/apis/_index.md
+++ b/website/content/apis/_index.md
@@ -288,6 +288,7 @@ _Appears in:_
 | `peerASN` _integer_ | AS number to expect from the remote end of the session.<br />ASN and DynamicASN are mutually exclusive and one of them must be specified. |
 | `dynamicASN` _[DynamicASNMode](#dynamicasnmode)_ | DynamicASN detects the AS number to use for the remote end of the session<br />without explicitly setting it via the ASN field. Limited to:<br />internal - if the neighbor's ASN is different than MyASN connection is denied.<br />external - if the neighbor's ASN is the same as MyASN the connection is denied.<br />ASN and DynamicASN are mutually exclusive and one of them must be specified. |
 | `peerAddress` _string_ | Address to dial when establishing the session. |
+| `interface` _string_ | Interface is the node interface over which the unnumbered BGP peering will<br />be established. No API validation takes place as that string value<br />represents an interface name on the host and if user provides an invalid<br />value, only the actual BGP session will not be established.<br />Address and Interface are mutually exclusive and one of them must be specified. |
 | `sourceAddress` _string_ | Source address to use when establishing the session. |
 | `peerPort` _integer_ | Port to dial when establishing the session. |
 | `holdTime` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#duration-v1-meta)_ | Requested BGP hold time, per RFC4271. |


Test patch:
diff --git a/e2etest/bgptests/dump.go b/e2etest/bgptests/dump.go
index 68d51599..c85cdb6c 100644
--- a/e2etest/bgptests/dump.go
+++ b/e2etest/bgptests/dump.go
@@ -12,11 +12,12 @@ import (
 	"github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	"go.universe.tf/e2etest/pkg/frr"
+	frrcontainer "go.universe.tf/e2etest/pkg/frr/container"
 	"go.universe.tf/e2etest/pkg/metallb"
 	clientset "k8s.io/client-go/kubernetes"
 )
 
-func dumpBGPInfo(basePath, testName string, cs clientset.Interface, namespace string) {
+func dumpBGPInfo(basePath, testName string, cs clientset.Interface, namespace string, extraContainers ...*frrcontainer.FRR) {
 	testPath := path.Join(basePath, strings.ReplaceAll(testName, " ", "-"))
 	err := os.Mkdir(testPath, 0755)
 	if err != nil && !errors.Is(err, os.ErrExist) {
@@ -24,7 +25,7 @@ func dumpBGPInfo(basePath, testName string, cs clientset.Interface, namespace st
 		return
 	}
 
-	for _, c := range FRRContainers {
+	for _, c := range append(FRRContainers, extraContainers...) {
 		dump, err := frr.RawDump(c, "/etc/frr/bgpd.conf", "/tmp/frr.log", "/etc/frr/daemons")
 		if err != nil {
 			ginkgo.GinkgoWriter.Printf("External frr dump for container %s failed %v", c.Name, err)
diff --git a/e2etest/bgptests/unnummbered.go b/e2etest/bgptests/unnummbered.go
new file mode 100644
index 00000000..0717fffe
--- /dev/null
+++ b/e2etest/bgptests/unnummbered.go
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier:Apache-2.0
+
+package bgptests
+
+import (
+	"context"
+	"fmt"
+	"net/netip"
+	"time"
+
+	"go.universe.tf/e2etest/pkg/config"
+	"go.universe.tf/e2etest/pkg/container"
+	"go.universe.tf/e2etest/pkg/executor"
+	"go.universe.tf/e2etest/pkg/frr"
+	frrconfig "go.universe.tf/e2etest/pkg/frr/config"
+	frrcontainer "go.universe.tf/e2etest/pkg/frr/container"
+	jigservice "go.universe.tf/e2etest/pkg/jigservice"
+	"go.universe.tf/e2etest/pkg/netdev"
+	testservice "go.universe.tf/e2etest/pkg/service"
+
+	"go.universe.tf/e2etest/pkg/k8s"
+	"go.universe.tf/e2etest/pkg/k8sclient"
+	"go.universe.tf/e2etest/pkg/metallb"
+	metallbv1beta1 "go.universe.tf/metallb/api/v1beta1"
+	metallbv1beta2 "go.universe.tf/metallb/api/v1beta2"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	clientset "k8s.io/client-go/kubernetes"
+)
+
+var FRRImage string
+var _ = ginkgo.Describe("FRRK8S-MODE Unnumbered BGP", func() {
+	var (
+		testNamespace         string
+		nodeWithP2PConnection corev1.Node
+		remoteP2PContainer    *frrcontainer.FRR
+
+		cs clientset.Interface
+	)
+
+	ginkgo.BeforeEach(func() {
+		ginkgo.By("Clearing any previous configuration")
+		err := ConfigUpdater.Clean()
+		Expect(err).NotTo(HaveOccurred())
+
+		cs = k8sclient.New()
+		allNodes, err := cs.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{})
+		Expect(err).NotTo(HaveOccurred())
+		nodeWithP2PConnection = allNodes.Items[0]
+
+		err = k8s.DeleteNamespace(k8sclient.New(), "unnumbered-bgp")
+		Expect(err).NotTo(HaveOccurred())
+		testNamespace, err = k8s.CreateTestNamespace(k8sclient.New(), "unnumbered-bgp")
+		Expect(err).NotTo(HaveOccurred())
+		ginkgo.DeferCleanup(func() {
+			err := k8s.DeleteNamespace(k8sclient.New(), testNamespace)
+			Expect(err).NotTo(HaveOccurred())
+		})
+	})
+
+	ginkgo.AfterEach(func() {
+		if ginkgo.CurrentSpecReport().Failed() {
+			k8s.DumpInfo(Reporter, ginkgo.CurrentSpecReport().LeafNodeText)
+			dumpBGPInfo(ReportPath, ginkgo.CurrentSpecReport().LeafNodeText, cs, testNamespace, remoteP2PContainer)
+		}
+
+		err := frrcontainer.Delete([]*frrcontainer.FRR{remoteP2PContainer})
+		Expect(err).NotTo(HaveOccurred())
+	})
+
+	ginkgo.DescribeTable("Session is established and route is advertised", func(prefixSendFromLocal []string, p2pInterface string, tweakService func(svc *corev1.Service)) {
+		rc := frrconfig.RouterConfigUnnumbered{
+			ASNLocal:  metalLBASN,
+			ASNRemote: metalLBASN,
+			Hostname:  "tor1",
+			Interface: p2pInterface,
+			RouterID:  "1.1.1.1",
+		}
+
+		var err error
+		ginkgo.By(fmt.Sprintf("creating p2p %s:%s -- %s:remote-p2p-container", nodeWithP2PConnection.Name, p2pInterface, p2pInterface))
+		remoteP2PContainer, err = frrcontainer.CreateP2PPeerFor(nodeWithP2PConnection.Name, p2pInterface, FRRImage)
+		Expect(err).NotTo(HaveOccurred())
+		ginkgo.By(fmt.Sprintf("updating frrconfig to %s", remoteP2PContainer.Name))
+
+		c, err := rc.Config()
+		Expect(err).NotTo(HaveOccurred())
+		err = remoteP2PContainer.UpdateBGPConfigFile(c)
+		Expect(err).NotTo(HaveOccurred())
+
+		resources := config.Resources{
+			Peers: []metallbv1beta2.BGPPeer{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "tor",
+						Namespace: metallb.Namespace,
+					},
+					Spec: metallbv1beta2.BGPPeerSpec{
+						Interface:  p2pInterface,
+						ASN:        rc.ASNRemote,
+						MyASN:      rc.ASNLocal,
+						BFDProfile: "simple",
+					},
+				},
+			},
+			BFDProfiles: []metallbv1beta1.BFDProfile{{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "simple",
+				},
+			},
+			},
+			Pools: []metallbv1beta1.IPAddressPool{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "bgp-test",
+					},
+					Spec: metallbv1beta1.IPAddressPoolSpec{
+						Addresses: prefixSendFromLocal,
+					},
+				},
+			},
+			BGPAdvs: []metallbv1beta1.BGPAdvertisement{
+				{ObjectMeta: metav1.ObjectMeta{Name: "empty"}},
+			},
+		}
+
+		err = ConfigUpdater.Update(resources)
+		Expect(err).NotTo(HaveOccurred(), "apply the CR in k8s api failed")
+
+		nodeP2PContainer := executor.ForContainer(nodeWithP2PConnection.Name)
+		nodeLLA, err := netdev.LinkLocalAddressForDevice(nodeP2PContainer, p2pInterface)
+		Expect(err).NotTo(HaveOccurred())
+		ginkgo.By("validating the node and p2p container peered")
+		validateUnnumberedBGPPeering(remoteP2PContainer, nodeLLA)
+
+		svc, _ := testservice.CreateWithBackend(cs, testNamespace, "unnumbered-lb", tweakService)
+		ginkgo.By("checking the service gets an ip assigned")
+		for _, i := range svc.Status.LoadBalancer.Ingress {
+			ingressIP := jigservice.GetIngressPoint(&i)
+			err = config.ValidateIPInRange(resources.Pools, ingressIP)
+			Expect(err).NotTo(HaveOccurred())
+		}
+
+		ginkgo.By(fmt.Sprintf("validating the p2p peer %s received the routes from node", remoteP2PContainer.Name))
+		validatePeerRoutesViaDevice(remoteP2PContainer, p2pInterface, nodeLLA, prefixSendFromLocal)
+
+	},
+		ginkgo.Entry("IPV4", []string{"5.5.5.5/32"}, "net10", func(_ *corev1.Service) {}),
+		ginkgo.Entry("IPV6", []string{"5555::1/128"}, "net20", func(_ *corev1.Service) {}),
+		ginkgo.Entry("DUALSTACK", []string{"5.5.5.5/32", "5555::1/128"}, "net30",
+			func(svc *corev1.Service) { testservice.DualStack(svc) }),
+	)
+})
+
+func validateUnnumberedBGPPeering(peer *frrcontainer.FRR, nodeLLA string) {
+	ginkgo.By(fmt.Sprintf("validating BGP peering to %s", peer.Name))
+	Eventually(func() error {
+		neighbors, err := frr.NeighborsInfo(peer)
+		if err != nil {
+			return err
+		}
+		for _, n := range neighbors {
+			if n.BGPNeighborAddr == nodeLLA && n.Connected && n.BFDInfo.Status == "Up" {
+				return nil
+			}
+		}
+		return fmt.Errorf("no connected neighbor was found with %s", nodeLLA)
+	}, 2*time.Minute, 10*time.Second).ShouldNot(HaveOccurred(),
+		"timed out waiting to validate nodes peered with the frr instance")
+}
+
+// validatePeerRoutesViaDevice validates that the peer has BGP routes to the
+// specified prefixes with the expected next-hop address on the specified device.
+func validatePeerRoutesViaDevice(peer executor.Executor, dev, nextHop string, prefixes ...[]string) {
+	ginkgo.By(fmt.Sprintf("validating prefix %s to %s dev %s", prefixes, nextHop, dev))
+	Eventually(func() error {
+		nextHopAddr := netip.MustParseAddr(nextHop)
+		want := make(map[netip.Prefix]map[netip.Addr]struct{})
+		for _, prf := range prefixes {
+			for _, p := range prf {
+				want[netip.MustParsePrefix(p)] = map[netip.Addr]struct{}{nextHopAddr: {}}
+			}
+		}
+
+		got, err := container.BGPRoutes(peer, dev)
+		if err != nil {
+			return err
+		}
+		if !cmp.Equal(want, got) {
+			return fmt.Errorf("want %v\n got %v\n diff %v", want, got, cmp.Diff(want, got))
+		}
+		return nil
+	}, 30*time.Second, 5*time.Second).ShouldNot(HaveOccurred(), fmt.Sprintf("peer should have the routes %s", prefixes))
+}
diff --git a/e2etest/e2etest_suite_test.go b/e2etest/e2etest_suite_test.go
index 7fa8367b..5dae4350 100644
--- a/e2etest/e2etest_suite_test.go
+++ b/e2etest/e2etest_suite_test.go
@@ -133,6 +133,7 @@ var _ = ginkgo.BeforeSuite(func() {
 		bgptests.FRRContainers, err = bgptests.HostContainerSetup(frrImage, hostBGPMode)
 		Expect(err).NotTo(HaveOccurred())
 	default:
+		bgptests.FRRImage = frrImage
 		bgptests.FRRContainers, err = bgptests.KindnetContainersSetup(cs, frrImage)
 		Expect(err).NotTo(HaveOccurred())
 		if withVRF {
diff --git a/e2etest/go.work.sum b/e2etest/go.work.sum
index 3fed727b..f85097d6 100644
--- a/e2etest/go.work.sum
+++ b/e2etest/go.work.sum
@@ -648,6 +648,7 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -680,6 +681,7 @@ github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6
 github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
 github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
 github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
+github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -697,6 +699,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
+github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
 github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM=
@@ -716,7 +719,6 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
@@ -780,6 +782,7 @@ github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHz
 github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
 github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
 github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
+github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -797,6 +800,7 @@ github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLO
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@@ -804,6 +808,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk
 github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
 github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
+github.com/metallb/frr-k8s v0.0.0-20241217094531-80d4b7a5591b h1:3+sSwasbGnVDjF84TRsxlCjbgko9tkDbw/kaj+NKPD4=
+github.com/metallb/frr-k8s v0.0.0-20241217094531-80d4b7a5591b/go.mod h1:gcOVS4IiQ7r6SRKc2HBgYll3Ku9SHEatxaqgNiuC/Tg=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
 github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
@@ -841,6 +847,7 @@ github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xl
 github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw=
 github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
 github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
+github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
 github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
 github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
 github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
@@ -850,6 +857,7 @@ github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8P
 github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
 github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk=
 github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
+github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
 github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
 github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
 github.com/open-policy-agent/cert-controller v0.10.1/go.mod h1:4uRbBLY5DsPOog+a9pqk3JLxuuhrWsbUedQW65HcLTI=
@@ -893,6 +901,8 @@ github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr
 github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
 github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
 github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
+github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
+github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
 github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
 github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
 github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
@@ -904,6 +914,7 @@ github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO
 github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
 github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
 github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ=
+github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
 github.com/prometheus/exporter-toolkit v0.7.1 h1:c6RXaK8xBVercEeUQ4tRNL8UGWzDHfvj9dseo1FcK1Y=
 github.com/prometheus/exporter-toolkit v0.7.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -976,6 +987,7 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
 github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
 github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
@@ -1182,6 +1194,7 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
+golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -1210,6 +1223,7 @@ golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1250,9 +1264,11 @@ golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
+golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
 golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
 golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
 golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
+golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1267,6 +1283,9 @@ golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQ
 golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
 golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
 golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
+golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1343,6 +1362,7 @@ golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY=
 golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
@@ -1350,6 +1370,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
+golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -1362,13 +1383,16 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
 golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -1554,7 +1578,9 @@ google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh
 google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
 google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@@ -1659,6 +1685,7 @@ k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/s
 k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
 k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo=
 k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8=
+k8s.io/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
 k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
 k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
 k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
@@ -1679,6 +1706,7 @@ k8s.io/kube-aggregator v0.29.3/go.mod h1:xGJqV/SJJ1fbwTGfQLAZfwgqX1EMoaqfotDTkDr
 k8s.io/kube-controller-manager v0.29.3/go.mod h1:Tf+BQG52nu8f2Ra9he9MsNFJ2sK7U3TSVvmTB+OVgiw=
 k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
 k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
+k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
 k8s.io/kube-proxy v0.29.3/go.mod h1:VyAG5i+d+crJ0WM6Ik/4wSq+VttAFDfD4n3z+0hEQl8=
 k8s.io/kube-scheduler v0.29.3/go.mod h1:1NLHViSwFFddWHH4U9UGD57clINAtje/PEs6PjOYQZg=
 k8s.io/kubectl v0.29.3 h1:RuwyyIU42MAISRIePaa8Q7A3U74Q9P4MoJbDFz9o3us=
@@ -1701,6 +1729,8 @@ k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/
 k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
@@ -1713,8 +1743,11 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
 sigs.k8s.io/controller-runtime v0.17.2/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s=
 sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg=
+sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
+sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
 sigs.k8s.io/kustomize/kustomize/v5 v5.0.4-0.20230601165947-6ce0bf390ce3/go.mod h1:/d88dHCvoy7d0AKFT0yytezSGZKjsZBVs9YTkBHSGFk=
 sigs.k8s.io/kustomize/kustomize/v5 v5.4.2/go.mod h1:5ypfJVYlPb2MKKeoGknVLxvHemDlQT+szI4+KOhnD6k=
 sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
+sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
 sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/e2etest/pkg/container/routes.go b/e2etest/pkg/container/routes.go
index 8c2ebfd5..45561c9f 100644
--- a/e2etest/pkg/container/routes.go
+++ b/e2etest/pkg/container/routes.go
@@ -134,30 +134,80 @@ func WireContainers(containerA, containerB, dev string) error {
 	return nil
 }
 
+type Nexthop struct {
+	Gateway string `json:"gateway"`
+	Dev     string `json:"dev"`
+}
+
+type IPRoute struct {
+	Dst      string    `json:"dst"`
+	Nexthops []Nexthop `json:"nexthops,omitempty"`
+	Gateway  string    `json:"gateway,omitempty"`
+	Via      *struct {
+		Family string `json:"family"`
+		Host   string `json:"host"`
+	} `json:"via,omitempty"`
+	Dev string `json:"dev"`
+}
+
+func parseIPRouteOutput(r IPRoute, dev string) (netip.Prefix, map[netip.Addr]struct{}, error) {
+	dst, err := netip.ParsePrefix(r.Dst)
+	if err != nil {
+		// `ip route` command does not print the prefix when /32 or /128
+		addr, err := netip.ParseAddr(r.Dst)
+		if err != nil {
+			return netip.Prefix{}, nil, fmt.Errorf("invalid prefix %s: %w", r.Dst, err)
+		}
+		dst, err = netip.ParsePrefix(r.Dst + "/32")
+		if addr.Is6() {
+			dst, err = netip.ParsePrefix(r.Dst + "/128")
+		}
+	}
+
+	nextHops := make(map[netip.Addr]struct{})
+	// this is for ipv4 single next-hop
+	if r.Via != nil {
+		addr, err := netip.ParseAddr(r.Via.Host)
+		if err != nil {
+			return netip.Prefix{}, nil, fmt.Errorf("invalid next-hop %s: %w", r.Via.Host, err)
+		}
+		if dev == "" || r.Dev == dev {
+			nextHops[addr] = struct{}{}
+		}
+	}
+
+	// this is for ipv4 multiple next-hops
+	for _, nh := range r.Nexthops {
+		addr, err := netip.ParseAddr(nh.Gateway)
+		if err != nil {
+			return netip.Prefix{}, nil, fmt.Errorf("invalid next-hop %s: %w", nh.Gateway, err)
+		}
+
+		if dev == "" || nh.Dev == dev {
+			nextHops[addr] = struct{}{}
+		}
+	}
+
+	// this is for ipv6
+	if r.Gateway != "" {
+		addr, err := netip.ParseAddr(r.Gateway)
+		if err != nil {
+			return netip.Prefix{}, nil, fmt.Errorf("invalid next-hop %s: %w", r.Gateway, err)
+		}
+		if dev == "" || r.Dev == dev {
+			nextHops[addr] = struct{}{}
+		}
+	}
+
+	return dst, nextHops, nil
+}
+
 // BGPRoutes executes `ip route show proto bgp` in the executor and returns all
 // routes filtered by device name e.g. net0. If device name is the empty string
 // no filtering takes place. The return is map[destination CIDR]-> set[nextHops].
 func BGPRoutes(exc executor.Executor, dev string) (map[netip.Prefix]map[netip.Addr]struct{}, error) {
 	ret := make(map[netip.Prefix]map[netip.Addr]struct{})
 
-	type Nexthop struct {
-		Gateway string   `json:"gateway"`
-		Dev     string   `json:"dev"`
-		Weight  int      `json:"weight"`
-		Flags   []string `json:"flags"`
-	}
-
-	type IPRoute struct {
-		Dst      string    `json:"dst"`
-		Nexthops []Nexthop `json:"nexthops,omitempty"`
-		Gateway  string    `json:"gateway,omitempty"`
-		Via      *struct {
-			Family string `json:"family"`
-			Host   string `json:"host"`
-		} `json:"via,omitempty"`
-		Dev string `json:"dev"`
-	}
-
 	for _, proto := range []string{"-4", "-6"} {
 		out, err := exc.Exec("ip", proto, "--json", "route", "show", "proto", "bgp")
 		if err != nil {
@@ -171,49 +221,15 @@ func BGPRoutes(exc executor.Executor, dev string) (map[netip.Prefix]map[netip.Ad
 		}
 
 		for _, r := range routes {
-			dst, err := netip.ParsePrefix(r.Dst)
+			dst, nextHops, err := parseIPRouteOutput(r, dev)
 			if err != nil {
-				return nil, fmt.Errorf("invalid prefix %s: %w", r.Dst, err)
-			}
-
-			nextHops := make(map[netip.Addr]struct{})
-			// this is for ipv4 single next-hop
-			if r.Via != nil {
-				addr, err := netip.ParseAddr(r.Via.Host)
-				if err != nil {
-					return nil, fmt.Errorf("invalid next-hop %s: %w", r.Via.Host, err)
-				}
-				if dev == "" || r.Dev == dev {
-					nextHops[addr] = struct{}{}
-				}
-			}
-
-			// this is for ipv4 multiple next-hops
-			for _, nh := range r.Nexthops {
-				addr, err := netip.ParseAddr(nh.Gateway)
-				if err != nil {
-					return nil, fmt.Errorf("invalid next-hop %s: %w", nh.Gateway, err)
-				}
-
-				if dev == "" || nh.Dev == dev {
-					nextHops[addr] = struct{}{}
-				}
-			}
-
-			// this is for ipv6
-			if r.Gateway != "" {
-				addr, err := netip.ParseAddr(r.Gateway)
-				if err != nil {
-					return nil, fmt.Errorf("invalid next-hop %s: %w", r.Gateway, err)
-				}
-				if dev == "" || r.Dev == dev {
-					nextHops[addr] = struct{}{}
-				}
+				return nil, err
 			}
 
 			if len(nextHops) == 0 {
 				continue
 			}
+
 			ret[dst] = nextHops
 		}
 	}
diff --git a/internal/bgp/frr/frr_bfd_test.go b/internal/bgp/frr/frr_bfd_test.go
index e7cb6a0f..b2bc0779 100644
--- a/internal/bgp/frr/frr_bfd_test.go
+++ b/internal/bgp/frr/frr_bfd_test.go
@@ -124,7 +124,8 @@ func TestBFDWithSession(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
diff --git a/internal/bgp/frr/frr_test.go b/internal/bgp/frr/frr_test.go
index 0bcdfd4a..0d0320c4 100644
--- a/internal/bgp/frr/frr_test.go
+++ b/internal/bgp/frr/frr_test.go
@@ -119,7 +119,8 @@ func TestSingleEBGPSessionMultiHop(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -146,7 +147,8 @@ func TestSingleEBGPSessionOneHop(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "127.0.0.2:179",
+			PeerAddress:   "127.0.0.2",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -174,7 +176,8 @@ func TestSingleIPv6EBGPSessionOneHop(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "[127:0:0::2]:179",
+			PeerAddress:   "127:0:0::2",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10:1:1::254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -202,7 +205,8 @@ func TestSingleIBGPSession(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -230,7 +234,8 @@ func TestSingleIPv6IBGPSession(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "[10:2:2::254]:179",
+			PeerAddress:   "10:2:2::254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10:1:1::254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -259,7 +264,8 @@ func TestSingleSessionClose(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -287,7 +293,8 @@ func TestSingleSessionWithGracefulRestart(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:     "10.2.2.254:179",
+			PeerAddress:     "10.2.2.254",
+			PeerPort:        179,
 			SourceAddress:   net.ParseIP("10.1.1.254"),
 			MyASN:           102,
 			RouterID:        net.ParseIP("10.1.1.254"),
@@ -311,7 +318,8 @@ func TestTwoSessions(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -330,7 +338,8 @@ func TestTwoSessions(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.4.4.255:179",
+			PeerAddress:   "10.4.4.255",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.3.3.254"),
 			MyASN:         300,
 			RouterID:      net.ParseIP("10.3.3.254"),
@@ -360,7 +369,8 @@ func TestTwoIPv6Sessions(t *testing.T) {
 
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "[10:2:2::254]:179",
+			PeerAddress:   "10:2:2::254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10:1:1::254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -377,7 +387,8 @@ func TestTwoIPv6Sessions(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "[10:4:4::255]:179",
+			PeerAddress:   "10:4:4::255",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10:3:3::254"),
 			MyASN:         300,
 			RouterID:      net.ParseIP("10.3.3.254"),
@@ -405,7 +416,8 @@ func TestIPv4AndIPv6SessionsDisableMP(t *testing.T) {
 
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "[10:2:2::254]:179",
+			PeerAddress:   "10:2:2::254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10:1:1::254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -424,7 +436,8 @@ func TestIPv4AndIPv6SessionsDisableMP(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.4.4.255:179",
+			PeerAddress:   "10.4.4.255",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.3.3.254"),
 			MyASN:         300,
 			RouterID:      net.ParseIP("10.3.3.254"),
@@ -452,7 +465,8 @@ func TestTwoSessionsDuplicate(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -470,7 +484,8 @@ func TestTwoSessionsDuplicate(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -498,7 +513,8 @@ func TestTwoSessionsDuplicateRouter(t *testing.T) {
 
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -515,7 +531,8 @@ func TestTwoSessionsDuplicateRouter(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.4.4.255:179",
+			PeerAddress:   "10.4.4.255",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -543,7 +560,8 @@ func TestSingleAdvertisement(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -591,7 +609,8 @@ func TestSingleAdvertisementNoRouterID(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      nil,
@@ -625,34 +644,6 @@ func TestSingleAdvertisementNoRouterID(t *testing.T) {
 	testCheckConfigFile(t)
 }
 
-func TestSingleAdvertisementInvalidNoPort(t *testing.T) {
-	testSetup(t)
-
-	l := log.NewNopLogger()
-	sessionManager := mockNewSessionManager(l, logging.LevelInfo)
-	defer close(sessionManager.reloadConfig)
-	session, err := sessionManager.NewSession(l,
-		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254",
-			SourceAddress: net.ParseIP("10.1.1.254"),
-			MyASN:         100,
-			RouterID:      net.ParseIP("10.1.1.254"),
-			PeerASN:       200,
-			HoldTime:      ptr.To(time.Second),
-			KeepAliveTime: ptr.To(time.Second),
-			Password:      "password",
-			CurrentNode:   "hostname",
-			EBGPMultiHop:  true,
-			SessionName:   "test-peer"})
-
-	if err == nil {
-		session.Close()
-		t.Fatalf("Should not be able to create session")
-	}
-
-	// Not checking the file since this test won't create it
-}
-
 func TestSingleAdvertisementStop(t *testing.T) {
 	testSetup(t)
 
@@ -661,7 +652,8 @@ func TestSingleAdvertisementStop(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -708,7 +700,8 @@ func TestSingleAdvertisementChange(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -763,7 +756,8 @@ func TestTwoAdvertisements(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -816,7 +810,8 @@ func TestTwoAdvertisementsDuplicate(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -863,7 +858,8 @@ func TestTwoAdvertisementsTwoSessions(t *testing.T) {
 		func() {
 			sessionsParameters := []bgp.SessionParameters{
 				{
-					PeerAddress:   "10.2.2.254:179",
+					PeerAddress:   "10.2.2.254",
+					PeerPort:      179,
 					SourceAddress: net.ParseIP("10.1.1.254"),
 					MyASN:         100,
 					RouterID:      net.ParseIP("10.1.1.254"),
@@ -876,7 +872,8 @@ func TestTwoAdvertisementsTwoSessions(t *testing.T) {
 					EBGPMultiHop:  true,
 					SessionName:   "test-peer"},
 				{
-					PeerAddress:   "10.2.2.255:179",
+					PeerAddress:   "10.2.2.255",
+					PeerPort:      179,
 					SourceAddress: net.ParseIP("10.1.1.254"),
 					MyASN:         100,
 					RouterID:      net.ParseIP("10.1.1.254"),
@@ -965,7 +962,8 @@ func TestSingleSessionExtras(t *testing.T) {
 	}
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "127.0.0.2:179",
+			PeerAddress:   "127.0.0.2",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -1046,7 +1044,8 @@ func TestLargeCommunities(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -1095,7 +1094,8 @@ func TestManyAdvertisementsSameCommunity(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -1143,7 +1143,8 @@ func TestSingleSessionWithNoTimers(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress: "10.2.2.254:179",
+			PeerAddress: "10.2.2.254",
+			PeerPort:    179,
 			MyASN:       102,
 			PeerASN:     100,
 			SessionName: "test-peer"})
@@ -1164,7 +1165,8 @@ func TestSingleSessionWithZeroTimers(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			MyASN:         102,
 			HoldTime:      ptr.To(0 * time.Second),
 			KeepAliveTime: ptr.To(0 * time.Second),
@@ -1366,7 +1368,8 @@ func TestSingleSessionWithInternalASN(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:     "10.2.2.254:179",
+			PeerAddress:     "10.2.2.254",
+			PeerPort:        179,
 			SourceAddress:   net.ParseIP("10.1.1.254"),
 			MyASN:           102,
 			RouterID:        net.ParseIP("10.1.1.254"),
@@ -1390,7 +1393,8 @@ func TestSingleSessionWithExternalASN(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:     "10.2.2.254:179",
+			PeerAddress:     "10.2.2.254",
+			PeerPort:        179,
 			SourceAddress:   net.ParseIP("10.1.1.254"),
 			MyASN:           102,
 			RouterID:        net.ParseIP("10.1.1.254"),
diff --git a/internal/bgp/frr/frr_vrf_test.go b/internal/bgp/frr/frr_vrf_test.go
index c21a1e1f..3d1dc7f9 100644
--- a/internal/bgp/frr/frr_vrf_test.go
+++ b/internal/bgp/frr/frr_vrf_test.go
@@ -22,7 +22,8 @@ func TestVRFSingleEBGPSessionMultiHop(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -50,7 +51,8 @@ func TestSingleVRFIBGPSession(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -79,7 +81,8 @@ func TestTwoSessionsOneVRF(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -97,7 +100,8 @@ func TestTwoSessionsOneVRF(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.4.4.255:179",
+			PeerAddress:   "10.4.4.255",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.3.3.254"),
 			MyASN:         300,
 			RouterID:      net.ParseIP("10.3.3.254"),
@@ -126,7 +130,8 @@ func TestTwoSessionsSameIPVRF(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -144,7 +149,8 @@ func TestTwoSessionsSameIPVRF(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.3.3.254"),
 			MyASN:         300,
 			RouterID:      net.ParseIP("10.3.3.254"),
@@ -173,7 +179,8 @@ func TestTwoSessionsSameIPRouterIDASNVRF(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			MyASN:         100,
 			PeerASN:       200,
 			HoldTime:      ptr.To(time.Second),
@@ -189,7 +196,8 @@ func TestTwoSessionsSameIPRouterIDASNVRF(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			MyASN:         100,
 			PeerASN:       200,
 			HoldTime:      ptr.To(time.Second),
@@ -216,7 +224,8 @@ func TestSingleAdvertisementVRF(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -264,7 +273,8 @@ func TestSingleAdvertisementChangeVRF(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -320,7 +330,8 @@ func TestTwoAdvertisementsVRF(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -374,7 +385,8 @@ func TestTwoAdvertisementsTwoSessionsOneVRF(t *testing.T) {
 	defer close(sessionManager.reloadConfig)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -392,7 +404,8 @@ func TestTwoAdvertisementsTwoSessionsOneVRF(t *testing.T) {
 
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.255:179",
+			PeerAddress:   "10.2.2.255",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
diff --git a/internal/bgp/frrk8s/frrk8s_bfd_test.go b/internal/bgp/frrk8s/frrk8s_bfd_test.go
index 075e0f92..f98092cf 100644
--- a/internal/bgp/frrk8s/frrk8s_bfd_test.go
+++ b/internal/bgp/frrk8s/frrk8s_bfd_test.go
@@ -112,7 +112,8 @@ func TestBFDWithSession(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
diff --git a/internal/bgp/frrk8s/frrk8s_test.go b/internal/bgp/frrk8s/frrk8s_test.go
index a7cafdd4..9c044939 100644
--- a/internal/bgp/frrk8s/frrk8s_test.go
+++ b/internal/bgp/frrk8s/frrk8s_test.go
@@ -121,7 +121,8 @@ func TestSingleEBGPSessionGracefullRestart(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:     "10.2.2.254:179",
+			PeerAddress:     "10.2.2.254",
+			PeerPort:        179,
 			SourceAddress:   net.ParseIP("10.1.1.254"),
 			MyASN:           100,
 			RouterID:        net.ParseIP("10.1.1.254"),
@@ -146,7 +147,8 @@ func TestSingleEBGPSessionMultiHop(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -170,7 +172,8 @@ func TestSingleEBGPSessionOneHop(t *testing.T) {
 	sessionManager := newTestSessionManager(t)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "127.0.0.2:179",
+			PeerAddress:   "127.0.0.2",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -195,7 +198,8 @@ func TestSingleIPv6EBGPSessionOneHop(t *testing.T) {
 	sessionManager := newTestSessionManager(t)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "[127:0:0::2]:179",
+			PeerAddress:   "127:0:0::2",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10:1:1::254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -220,7 +224,8 @@ func TestSingleIBGPSession(t *testing.T) {
 	sessionManager := newTestSessionManager(t)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -245,7 +250,8 @@ func TestSingleIPv6IBGPSession(t *testing.T) {
 	sessionManager := newTestSessionManager(t)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "[10:2:2::254]:179",
+			PeerAddress:   "10:2:2::254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10:1:1::254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -270,7 +276,8 @@ func TestSingleSessionClose(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -294,7 +301,8 @@ func TestTwoSessions(t *testing.T) {
 	sessionManager := newTestSessionManager(t)
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -312,7 +320,8 @@ func TestTwoSessions(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.4.4.255:179",
+			PeerAddress:   "10.4.4.255",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.3.3.254"),
 			MyASN:         300,
 			RouterID:      net.ParseIP("10.3.3.254"),
@@ -341,7 +350,8 @@ func TestTwoIPv6Sessions(t *testing.T) {
 
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "[10:2:2::254]:179",
+			PeerAddress:   "10:2:2::254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10:1:1::254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -358,7 +368,8 @@ func TestTwoIPv6Sessions(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "[10:4:4::255]:179",
+			PeerAddress:   "10:4:4::255",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10:3:3::254"),
 			MyASN:         300,
 			RouterID:      net.ParseIP("10.3.3.254"),
@@ -382,7 +393,8 @@ func TestTwoSessionsDuplicate(t *testing.T) {
 	sessionManager := newTestSessionManager(t)
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -400,7 +412,8 @@ func TestTwoSessionsDuplicate(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -425,7 +438,8 @@ func TestTwoSessionsDuplicateRouter(t *testing.T) {
 
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -442,7 +456,8 @@ func TestTwoSessionsDuplicateRouter(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.4.4.255:179",
+			PeerAddress:   "10.4.4.255",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -467,7 +482,8 @@ func TestSingleAdvertisement(t *testing.T) {
 	sessionManager := newTestSessionManager(t)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -511,7 +527,8 @@ func TestTwoAdvertisementsWithSamePrefix(t *testing.T) {
 	sessionManager := newTestSessionManager(t)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -564,7 +581,8 @@ func TestSingleAdvertisementNoRouterID(t *testing.T) {
 	sessionManager := newTestSessionManager(t)
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      nil,
@@ -598,39 +616,14 @@ func TestSingleAdvertisementNoRouterID(t *testing.T) {
 	testCheckConfigFile(t)
 }
 
-func TestSingleAdvertisementInvalidNoPort(t *testing.T) {
-	sessionManager := newTestSessionManager(t)
-
-	l := log.NewNopLogger()
-	session, err := sessionManager.NewSession(l,
-		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254",
-			SourceAddress: net.ParseIP("10.1.1.254"),
-			MyASN:         100,
-			RouterID:      net.ParseIP("10.1.1.254"),
-			PeerASN:       200,
-			HoldTime:      ptr.To(time.Second),
-			KeepAliveTime: ptr.To(time.Second),
-			Password:      "password",
-			CurrentNode:   "hostname",
-			EBGPMultiHop:  true,
-			SessionName:   "test-peer"})
-
-	if err == nil {
-		session.Close()
-		t.Fatalf("Should not be able to create session")
-	}
-
-	// Not checking the file since this test won't create it
-}
-
 func TestSingleAdvertisementStop(t *testing.T) {
 	l := log.NewNopLogger()
 	sessionManager := newTestSessionManager(t)
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -675,7 +668,8 @@ func TestSingleAdvertisementChange(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -728,7 +722,8 @@ func TestTwoAdvertisements(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -781,7 +776,8 @@ func TestTwoAdvertisementsTwoSessions(t *testing.T) {
 		func() {
 			sessionsParameters := []bgp.SessionParameters{
 				{
-					PeerAddress:   "10.2.2.254:179",
+					PeerAddress:   "10.2.2.254",
+					PeerPort:      179,
 					SourceAddress: net.ParseIP("10.1.1.254"),
 					MyASN:         100,
 					RouterID:      net.ParseIP("10.1.1.254"),
@@ -793,7 +789,8 @@ func TestTwoAdvertisementsTwoSessions(t *testing.T) {
 					EBGPMultiHop:  true,
 					SessionName:   "test-peer"},
 				{
-					PeerAddress:   "10.2.2.255:179",
+					PeerAddress:   "10.2.2.255",
+					PeerPort:      179,
 					SourceAddress: net.ParseIP("10.1.1.254"),
 					MyASN:         100,
 					RouterID:      net.ParseIP("10.1.1.254"),
@@ -874,7 +871,8 @@ func TestLargeCommunities(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -921,7 +919,8 @@ func TestSingleSessionWithNoTimers(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress: "10.2.2.254:179",
+			PeerAddress: "10.2.2.254",
+			PeerPort:    179,
 			MyASN:       102,
 			PeerASN:     100,
 			SessionName: "test-peer"})
@@ -940,7 +939,8 @@ func TestSingleSessionWithZeroTimers(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			MyASN:         102,
 			HoldTime:      ptr.To(0 * time.Second),
 			KeepAliveTime: ptr.To(0 * time.Second),
@@ -961,7 +961,8 @@ func TestSingleSessionWithInternalASN(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress: "10.2.2.254:179",
+			PeerAddress: "10.2.2.254",
+			PeerPort:    179,
 			MyASN:       100,
 			RouterID:    net.ParseIP("10.1.1.254"),
 			DynamicASN:  "internal",
@@ -981,7 +982,8 @@ func TestSingleSessionWithExternalASN(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress: "10.2.2.254:179",
+			PeerAddress: "10.2.2.254",
+			PeerPort:    179,
 			MyASN:       100,
 			RouterID:    net.ParseIP("10.1.1.254"),
 			DynamicASN:  "external",
@@ -994,3 +996,24 @@ func TestSingleSessionWithExternalASN(t *testing.T) {
 
 	testCheckConfigFile(t)
 }
+
+func TestSingleSessionWithInterface(t *testing.T) {
+	sessionManager := newTestSessionManager(t)
+	l := log.NewNopLogger()
+
+	session, err := sessionManager.NewSession(l,
+		bgp.SessionParameters{
+			PeerPort:      179,
+			PeerInterface: "net0",
+			MyASN:         100,
+			RouterID:      net.ParseIP("10.1.1.254"),
+			DynamicASN:    "internal",
+			CurrentNode:   "hostname",
+			SessionName:   "test-peer"})
+	if err != nil {
+		t.Fatalf("Could not create session: %s", err)
+	}
+	defer session.Close()
+
+	testCheckConfigFile(t)
+}
diff --git a/internal/bgp/frrk8s/frrk8s_vrf_test.go b/internal/bgp/frrk8s/frrk8s_vrf_test.go
index 681cbfe5..d89c4aec 100644
--- a/internal/bgp/frrk8s/frrk8s_vrf_test.go
+++ b/internal/bgp/frrk8s/frrk8s_vrf_test.go
@@ -19,7 +19,8 @@ func TestVRFSingleEBGPSessionMultiHop(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -45,7 +46,8 @@ func TestSingleVRFIBGPSession(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -72,7 +74,8 @@ func TestTwoSessionsOneVRF(t *testing.T) {
 
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -90,7 +93,8 @@ func TestTwoSessionsOneVRF(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.4.4.255:179",
+			PeerAddress:   "10.4.4.255",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.3.3.254"),
 			MyASN:         300,
 			RouterID:      net.ParseIP("10.3.3.254"),
@@ -117,7 +121,8 @@ func TestTwoSessionsSameIPVRF(t *testing.T) {
 
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -135,7 +140,8 @@ func TestTwoSessionsSameIPVRF(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.3.3.254"),
 			MyASN:         300,
 			RouterID:      net.ParseIP("10.3.3.254"),
@@ -162,7 +168,8 @@ func TestTwoSessionsSameIPRouterIDASNVRF(t *testing.T) {
 
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			MyASN:         100,
 			PeerASN:       200,
 			HoldTime:      ptr.To(time.Second),
@@ -178,7 +185,8 @@ func TestTwoSessionsSameIPRouterIDASNVRF(t *testing.T) {
 	defer session1.Close()
 	session2, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			MyASN:         100,
 			PeerASN:       200,
 			HoldTime:      ptr.To(time.Second),
@@ -203,7 +211,8 @@ func TestSingleAdvertisementVRF(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -249,7 +258,8 @@ func TestSingleAdvertisementChangeVRF(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -303,7 +313,8 @@ func TestTwoAdvertisementsVRF(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -355,7 +366,8 @@ func TestTwoAdvertisementsTwoSessionsOneVRF(t *testing.T) {
 
 	session, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.254:179",
+			PeerAddress:   "10.2.2.254",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
@@ -373,7 +385,8 @@ func TestTwoAdvertisementsTwoSessionsOneVRF(t *testing.T) {
 
 	session1, err := sessionManager.NewSession(l,
 		bgp.SessionParameters{
-			PeerAddress:   "10.2.2.255:179",
+			PeerAddress:   "10.2.2.255",
+			PeerPort:      179,
 			SourceAddress: net.ParseIP("10.1.1.254"),
 			MyASN:         100,
 			RouterID:      net.ParseIP("10.1.1.254"),
diff --git a/internal/bgp/frrk8s/testdata/TestSingleSessionWithInterface.golden b/internal/bgp/frrk8s/testdata/TestSingleSessionWithInterface.golden
new file mode 100644
index 00000000..e7320660
--- /dev/null
+++ b/internal/bgp/frrk8s/testdata/TestSingleSessionWithInterface.golden
@@ -0,0 +1,38 @@
+{
+    "metadata": {
+        "name": "metallb-testnodename",
+        "namespace": "testnamespace",
+        "creationTimestamp": null
+    },
+    "spec": {
+        "bgp": {
+            "routers": [
+                {
+                    "asn": 100,
+                    "id": "10.1.1.254",
+                    "neighbors": [
+                        {
+                            "dynamicASN": "internal",
+                            "interface": "net0",
+                            "port": 179,
+                            "passwordSecret": {},
+                            "toAdvertise": {
+                                "allowed": {}
+                            },
+                            "toReceive": {
+                                "allowed": {}
+                            }
+                        }
+                    ]
+                }
+            ]
+        },
+        "raw": {},
+        "nodeSelector": {
+            "matchLabels": {
+                "kubernetes.io/hostname": "testnodename"
+            }
+        }
+    },
+    "status": {}
+}
\ No newline at end of file
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index d08ba6a2..39866140 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -3499,6 +3499,72 @@ func TestParse(t *testing.T) {
 				},
 			},
 		},
+		{
+			desc: "unnumbered peer without address with interface ok",
+			crs: ClusterResources{
+
+				Peers: []v1beta2.BGPPeer{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "peer1",
+						},
+						Spec: v1beta2.BGPPeerSpec{
+							MyASN:      42,
+							DynamicASN: v1beta2.InternalASNMode,
+							Interface:  "net0",
+						},
+					},
+				},
+			},
+			want: &Config{
+				Peers: map[string]*Peer{
+					"peer1": {
+						Name:          "peer1",
+						Iface:         "net0",
+						MyASN:         42,
+						DynamicASN:    "internal",
+						NodeSelectors: []labels.Selector{labels.Everything()},
+						EBGPMultiHop:  false,
+					},
+				},
+				Pools:       &Pools{ByName: map[string]*Pool{}},
+				BFDProfiles: map[string]*BFDProfile{},
+			},
+		},
+		{
+			desc: "unnumbered peer without address without interface nok",
+			crs: ClusterResources{
+				Peers: []v1beta2.BGPPeer{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "invalid",
+						},
+						Spec: v1beta2.BGPPeerSpec{
+							MyASN:      42,
+							DynamicASN: "internal",
+						},
+					},
+				},
+			},
+		},
+		{
+			desc: "unnumbered peer with address with interface nok",
+			crs: ClusterResources{
+				Peers: []v1beta2.BGPPeer{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "invalid",
+						},
+						Spec: v1beta2.BGPPeerSpec{
+							Address:    "1.2.3.4",
+							Interface:  "net0",
+							MyASN:      42,
+							DynamicASN: "internal",
+						},
+					},
+				},
+			},
+		},
 	}
 
 	for _, test := range tests {
diff --git a/internal/config/validation_test.go b/internal/config/validation_test.go
index cd4ae44c..5a2a0a1b 100644
--- a/internal/config/validation_test.go
+++ b/internal/config/validation_test.go
@@ -207,6 +207,19 @@ func TestValidate(t *testing.T) {
 			},
 			mustFail: true,
 		},
+		{
+			desc: "interface",
+			config: ClusterResources{
+				Peers: []v1beta2.BGPPeer{
+					{
+						Spec: v1beta2.BGPPeerSpec{
+							Interface: "eth0",
+						},
+					},
+				},
+			},
+			mustFail: true,
+		},
 		{
 			desc: "keepalive time",
 			config: ClusterResources{
diff --git a/speaker/bgp_controller_test.go b/speaker/bgp_controller_test.go
index 1490737d..0c131d98 100644
--- a/speaker/bgp_controller_test.go
+++ b/speaker/bgp_controller_test.go
@@ -285,7 +285,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				}},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
+				"1.2.3.4": nil,
 			},
 		},
 		{
@@ -313,7 +313,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
+				"1.2.3.4": nil,
 			},
 		},
 
@@ -343,7 +343,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": {
+				"1.2.3.4": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
@@ -377,7 +377,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
+				"1.2.3.4": nil,
 			},
 		},
 
@@ -420,7 +420,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": {
+				"1.2.3.4": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
@@ -493,7 +493,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
+				"1.2.3.4": nil,
 			},
 		},
 
@@ -509,7 +509,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 			},
 			eps: []discovery.EndpointSlice{},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
+				"1.2.3.4": nil,
 			},
 		},
 
@@ -539,7 +539,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
+				"1.2.3.4": nil,
 			},
 		},
 
@@ -578,7 +578,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": {
+				"1.2.3.4": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
@@ -613,7 +613,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": {
+				"1.2.3.4": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
@@ -648,7 +648,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": {
+				"1.2.3.4": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
@@ -712,7 +712,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": {
+				"1.2.3.4": {
 					{
 						Prefix:    ipnet("10.20.30.1/32"),
 						LocalPref: 100,
@@ -786,7 +786,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": {
+				"1.2.3.4": {
 					{
 						Prefix:    ipnet("10.20.30.1/32"),
 						LocalPref: 100,
@@ -859,7 +859,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 			},
 
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": {
+				"1.2.3.4": {
 					{
 						Prefix:    ipnet("10.20.30.1/32"),
 						LocalPref: 100,
@@ -927,12 +927,12 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": {
+				"1.2.3.4": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
 				},
-				"1.2.3.5:0": {
+				"1.2.3.5": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
@@ -965,12 +965,12 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": {
+				"1.2.3.4": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
 				},
-				"1.2.3.5:0": {
+				"1.2.3.5": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
@@ -1004,7 +1004,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": {
+				"1.2.3.4": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
@@ -1012,7 +1012,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 						Prefix: ipnet("10.20.30.5/32"),
 					},
 				},
-				"1.2.3.5:0": {
+				"1.2.3.5": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
@@ -1049,7 +1049,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": {
+				"1.2.3.4": {
 					// Prefixes duplicated because the dedupe happens
 					// inside the real BGP session.
 					{
@@ -1059,7 +1059,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 						Prefix: ipnet("10.20.30.1/32"),
 					},
 				},
-				"1.2.3.5:0": {
+				"1.2.3.5": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
@@ -1074,12 +1074,12 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 			desc:     "Delete svc",
 			balancer: "test1",
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": {
+				"1.2.3.4": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
 				},
-				"1.2.3.5:0": {
+				"1.2.3.5": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
@@ -1132,7 +1132,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.5:0": {
+				"1.2.3.5": {
 					{
 						Prefix: ipnet("10.20.30.1/32"),
 					},
@@ -1144,7 +1144,7 @@ func TestBGPSpeakerEPSlices(t *testing.T) {
 			desc:     "Delete second svc",
 			balancer: "test2",
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.5:0": nil,
+				"1.2.3.5": nil,
 			},
 		},
 	}
@@ -1223,7 +1223,7 @@ func TestNodeSelectors(t *testing.T) {
 				Pools: &config.Pools{ByName: pools},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
+				"1.2.3.4": nil,
 			},
 		},
 
@@ -1245,7 +1245,7 @@ func TestNodeSelectors(t *testing.T) {
 				Pools: &config.Pools{ByName: pools},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
+				"1.2.3.4": nil,
 			},
 		},
 
@@ -1260,8 +1260,8 @@ func TestNodeSelectors(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
-				"2.3.4.5:0": nil,
+				"1.2.3.4": nil,
+				"2.3.4.5": nil,
 			},
 		},
 
@@ -1276,7 +1276,7 @@ func TestNodeSelectors(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
+				"1.2.3.4": nil,
 			},
 		},
 
@@ -1298,8 +1298,8 @@ func TestNodeSelectors(t *testing.T) {
 				Pools: &config.Pools{ByName: pools},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
-				"2.3.4.5:0": nil,
+				"1.2.3.4": nil,
+				"2.3.4.5": nil,
 			},
 		},
 
@@ -1314,8 +1314,8 @@ func TestNodeSelectors(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
-				"2.3.4.5:0": nil,
+				"1.2.3.4": nil,
+				"2.3.4.5": nil,
 			},
 		},
 
@@ -1338,8 +1338,8 @@ func TestNodeSelectors(t *testing.T) {
 				Pools: &config.Pools{ByName: pools},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
-				"2.3.4.5:0": nil,
+				"1.2.3.4": nil,
+				"2.3.4.5": nil,
 			},
 		},
 
@@ -1354,8 +1354,8 @@ func TestNodeSelectors(t *testing.T) {
 				},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
-				"2.3.4.5:0": nil,
+				"1.2.3.4": nil,
+				"2.3.4.5": nil,
 			},
 		},
 
@@ -1371,8 +1371,8 @@ func TestNodeSelectors(t *testing.T) {
 				Spec: v1.NodeSpec{Unschedulable: true},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
-				"2.3.4.5:0": nil,
+				"1.2.3.4": nil,
+				"2.3.4.5": nil,
 			},
 			wantReturnState: controllers.SyncStateReprocessAll,
 		},
@@ -1389,8 +1389,8 @@ func TestNodeSelectors(t *testing.T) {
 				Spec: v1.NodeSpec{Unschedulable: true},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
-				"2.3.4.5:0": nil,
+				"1.2.3.4": nil,
+				"2.3.4.5": nil,
 			},
 		},
 
@@ -1406,8 +1406,8 @@ func TestNodeSelectors(t *testing.T) {
 				Spec: v1.NodeSpec{Unschedulable: false},
 			},
 			wantAds: map[string][]*bgp.Advertisement{
-				"1.2.3.4:0": nil,
-				"2.3.4.5:0": nil,
+				"1.2.3.4": nil,
+				"2.3.4.5": nil,
 			},
 			wantReturnState: controllers.SyncStateReprocessAll,
 		},