Skip to content

Commit a598dfd

Browse files
usiegj00julianwiedmann
authored andcommitted
datapath: Fix BPF masquerade IP selection with multiple IPs per interface
When a network device has multiple IP addresses (both public and private), BPF masquerading was incorrectly selecting the Kubernetes Node IP even when it was a private address and a public address was available on the same interface. The issue was introduced in PR #33629 which added K8s Node IP prioritization. The code was setting both ipv4PublicIndex and ipv4PrivateIndex to the K8s Node IP index, effectively forcing it to be selected regardless of public/private status. This broke the documented "prefer public over private" logic for Primary address selection used by BPF masquerading. The fix ensures that K8s Node IP prioritization only applies within its own category (public or private): - If K8s Node IP is public, it takes precedence over other public IPs - If K8s Node IP is private, it takes precedence over other private IPs - But public IPs still take precedence over private IPs for masquerading This restores the correct behavior where egress traffic is masqueraded using the public IP address when available, which is required for proper routing in environments with both public and private IPs on the same interface. Fixes: #41866 Signed-off-by: Jonathan Siegel <248302+usiegj00@users.noreply.github.com>
1 parent 6e3657e commit a598dfd

2 files changed

Lines changed: 46 additions & 17 deletions

File tree

pkg/datapath/tables/node_address.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -493,9 +493,17 @@ func (n *nodeAddressController) getAddressesFromDevice(dev *Device, k8sIPv4, k8s
493493
isPublic := ip.IsPublicAddr(addr.Addr.AsSlice())
494494
if addr.Addr.Is4() {
495495
if addr.Addr.Unmap() == k8sIPv4.Unmap() {
496-
// Address matches the K8s Node IP. Force this to be picked.
497-
ipv4PublicIndex = index
498-
ipv4PrivateIndex = index
496+
// Address matches the K8s Node IP. Prioritize it within its
497+
// category (public or private) for NodePort address selection.
498+
// We don't force it to both categories, as that would break
499+
// the "prefer public over private" logic for Primary address
500+
// selection used by BPF masquerading.
501+
// See: https://github.com/cilium/cilium/issues/41866
502+
if isPublic {
503+
ipv4PublicIndex = index
504+
} else {
505+
ipv4PrivateIndex = index
506+
}
499507
}
500508
if ipv4PublicIndex < 0 && isPublic {
501509
ipv4PublicIndex = index
@@ -507,9 +515,17 @@ func (n *nodeAddressController) getAddressesFromDevice(dev *Device, k8sIPv4, k8s
507515

508516
if addr.Addr.Is6() {
509517
if addr.Addr == k8sIPv6 {
510-
// Address matches the K8s Node IP. Force this to be picked.
511-
ipv6PublicIndex = index
512-
ipv6PrivateIndex = index
518+
// Address matches the K8s Node IP. Prioritize it within its
519+
// category (public or private) for NodePort address selection.
520+
// We don't force it to both categories, as that would break
521+
// the "prefer public over private" logic for Primary address
522+
// selection used by BPF masquerading.
523+
// See: https://github.com/cilium/cilium/issues/41866
524+
if isPublic {
525+
ipv6PublicIndex = index
526+
} else {
527+
ipv6PrivateIndex = index
528+
}
513529
}
514530
if ipv6PublicIndex < 0 && isPublic {
515531
ipv6PublicIndex = index

pkg/datapath/tables/node_address_test.go

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -215,26 +215,36 @@ var nodeAddressTests = []struct {
215215
},
216216

217217
{
218-
name: "node IP preferred",
218+
// Test that K8s Node IP is prioritized within its category (public/private)
219+
// but doesn't override the public/private preference itself.
220+
// - testNodeIPv4 (172.16.0.1) is private, should be prioritized among private IPs
221+
// - testNodeIPv6 (2222::1) is public, should be prioritized among public IPs
222+
name: "k8s node IP prioritized within category",
219223
addrs: []DeviceAddress{
224+
// IPv4: multiple private IPs + one public
220225
{
221-
Addr: netip.MustParseAddr("10.0.0.1"),
226+
Addr: netip.MustParseAddr("10.0.0.1"), // private, but not K8s IP
222227
Scope: RT_SCOPE_UNIVERSE,
223228
},
224229
{
225-
Addr: netip.MustParseAddr("1.1.1.1"),
230+
Addr: netip.MustParseAddr("1.1.1.1"), // public
226231
Scope: RT_SCOPE_UNIVERSE,
227232
},
228233
{
229-
Addr: testNodeIPv4,
234+
Addr: testNodeIPv4, // private K8s Node IP (172.16.0.1)
230235
Scope: RT_SCOPE_UNIVERSE,
231236
},
237+
// IPv6: multiple public IPs + one private
232238
{
233-
Addr: netip.MustParseAddr("2001:db8::1"),
239+
Addr: netip.MustParseAddr("2001:db8::1"), // private (documentation prefix)
240+
Scope: RT_SCOPE_UNIVERSE,
241+
},
242+
{
243+
Addr: netip.MustParseAddr("2600:beef::1"), // public, but not K8s IP
234244
Scope: RT_SCOPE_UNIVERSE,
235245
},
236246
{
237-
Addr: testNodeIPv6,
247+
Addr: testNodeIPv6, // public K8s Node IP (2222::1)
238248
Scope: RT_SCOPE_UNIVERSE,
239249
},
240250
},
@@ -244,20 +254,23 @@ var nodeAddressTests = []struct {
244254
ciliumHostIPLinkScoped,
245255
netip.MustParseAddr("10.0.0.1"),
246256
netip.MustParseAddr("1.1.1.1"),
247-
netip.MustParseAddr("2001:db8::1"),
248257
testNodeIPv4,
258+
netip.MustParseAddr("2001:db8::1"),
259+
netip.MustParseAddr("2600:beef::1"),
249260
testNodeIPv6,
250261
},
251262

263+
// Primary prefers public; among public IPs, K8s Node IP is prioritized
252264
wantPrimary: []netip.Addr{
253265
ciliumHostIP,
254-
testNodeIPv4,
255-
testNodeIPv6,
266+
netip.MustParseAddr("1.1.1.1"), // IPv4: only public IP
267+
testNodeIPv6, // IPv6: K8s Node IP prioritized among public
256268
},
257269

270+
// NodePort prefers private; among private IPs, K8s Node IP is prioritized
258271
wantNodePort: []netip.Addr{
259-
testNodeIPv4,
260-
testNodeIPv6,
272+
testNodeIPv4, // IPv4: K8s Node IP prioritized among private
273+
netip.MustParseAddr("2001:db8::1"), // IPv6: only private IP
261274
},
262275
},
263276
}

0 commit comments

Comments
 (0)