diff --git a/pkg/proxy/iptables/proxier.go b/pkg/proxy/iptables/proxier.go index 3ff3c77ef70..0855096c1b1 100644 --- a/pkg/proxy/iptables/proxier.go +++ b/pkg/proxy/iptables/proxier.go @@ -264,9 +264,12 @@ func NewProxier(ipt utiliptables.Interface, healthzServer healthcheck.ProxierHealthUpdater, nodePortAddresses []string, ) (*Proxier, error) { - // Set the route_localnet sysctl we need for - if err := utilproxy.EnsureSysctl(sysctl, sysctlRouteLocalnet, 1); err != nil { - return nil, err + if utilproxy.ContainsIPv4Loopback(nodePortAddresses) { + // Set the route_localnet sysctl we need for exposing NodePorts on loopback addresses + klog.InfoS("Setting route_localnet=1, use nodePortAddresses to filter loopback addresses for NodePorts to skip it https://issues.k8s.io/90259") + if err := utilproxy.EnsureSysctl(sysctl, sysctlRouteLocalnet, 1); err != nil { + return nil, err + } } // Proxy needs br_netfilter and bridge-nf-call-iptables=1 when containers diff --git a/pkg/proxy/util/utils.go b/pkg/proxy/util/utils.go index 88ae712f901..f20fc194307 100644 --- a/pkg/proxy/util/utils.go +++ b/pkg/proxy/util/utils.go @@ -78,6 +78,37 @@ func BuildPortsToEndpointsMap(endpoints *v1.Endpoints) map[string][]string { return portsToEndpoints } +// ContainsIPv4Loopback returns true if the input is empty or one of the CIDR contains an IPv4 loopback address. +func ContainsIPv4Loopback(cidrStrings []string) bool { + if len(cidrStrings) == 0 { + return true + } + // RFC 5735 127.0.0.0/8 - This block is assigned for use as the Internet host loopback address + ipv4LoopbackStart := netutils.ParseIPSloppy("127.0.0.0") + for _, cidr := range cidrStrings { + if IsZeroCIDR(cidr) { + return true + } + + ip, ipnet, err := netutils.ParseCIDRSloppy(cidr) + if err != nil { + continue + } + + if netutils.IsIPv6CIDR(ipnet) { + continue + } + + if ip.IsLoopback() { + return true + } + if ipnet.Contains(ipv4LoopbackStart) { + return true + } + } + return false +} + // IsZeroCIDR checks whether the input CIDR string is either // the IPv4 or IPv6 zero CIDR func IsZeroCIDR(cidr string) bool { diff --git a/pkg/proxy/util/utils_test.go b/pkg/proxy/util/utils_test.go index fb6fb8e5ae1..4b0c83e1a87 100644 --- a/pkg/proxy/util/utils_test.go +++ b/pkg/proxy/util/utils_test.go @@ -1401,3 +1401,68 @@ func TestAddressSet(t *testing.T) { } } } + +func TestContainsIPv4Loopback(t *testing.T) { + tests := []struct { + name string + cidrStrings []string + want bool + }{ + { + name: "empty", + want: true, + }, + { + name: "all zeros ipv4", + cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "0.0.0.0/0"}, + want: true, + }, + { + name: "all zeros ipv4 and invalid cidr", + cidrStrings: []string{"invalid.cidr", "192.168.0.0/16", "fd00:1:d::/64", "0.0.0.0/0"}, + want: true, + }, + { + name: "all zeros ipv6", // interpret all zeros equal for IPv4 and IPv6 as Golang stdlib + cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "::/0"}, + want: true, + }, + { + name: "ipv4 loopback", + cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "127.0.0.0/8"}, + want: true, + }, + { + name: "ipv6 loopback", + cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "::1/128"}, + want: false, + }, + { + name: "ipv4 loopback smaller range", + cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "127.0.2.0/28"}, + want: true, + }, + { + name: "ipv4 loopback within larger range", + cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "64.0.0.0/2"}, + want: true, + }, + { + name: "non loop loopback", + cidrStrings: []string{"128.0.2.0/28", "224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64"}, + want: false, + }, + { + name: "invalid cidr", + cidrStrings: []string{"invalid.ip/invalid.mask"}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ContainsIPv4Loopback(tt.cidrStrings); got != tt.want { + t.Errorf("ContainLoopback() = %v, want %v", got, tt.want) + } + }) + } +}