diff --git a/pkg/proxy/util/nodeport_addresses.go b/pkg/proxy/util/nodeport_addresses.go new file mode 100644 index 00000000000..27d186964d0 --- /dev/null +++ b/pkg/proxy/util/nodeport_addresses.go @@ -0,0 +1,116 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "net" + + "k8s.io/apimachinery/pkg/util/sets" + netutils "k8s.io/utils/net" +) + +// GetNodeAddresses return all matched node IP addresses based on given cidr slice. +// Some callers, e.g. IPVS proxier, need concrete IPs, not ranges, which is why this exists. +// NetworkInterfacer is injected for test purpose. +// We expect the cidrs passed in is already validated. +// Given an empty input `[]`, it will return `0.0.0.0/0` and `::/0` directly. +// If multiple cidrs is given, it will return the minimal IP sets, e.g. given input `[1.2.0.0/16, 0.0.0.0/0]`, it will +// only return `0.0.0.0/0`. +// NOTE: GetNodeAddresses only accepts CIDRs, if you want concrete IPs, e.g. 1.2.3.4, then the input should be 1.2.3.4/32. +func GetNodeAddresses(cidrs []string, nw NetworkInterfacer) (sets.String, error) { + uniqueAddressList := sets.NewString() + if len(cidrs) == 0 { + uniqueAddressList.Insert(IPv4ZeroCIDR) + uniqueAddressList.Insert(IPv6ZeroCIDR) + return uniqueAddressList, nil + } + // First round of iteration to pick out `0.0.0.0/0` or `::/0` for the sake of excluding non-zero IPs. + for _, cidr := range cidrs { + if IsZeroCIDR(cidr) { + uniqueAddressList.Insert(cidr) + } + } + + addrs, err := nw.InterfaceAddrs() + if err != nil { + return nil, fmt.Errorf("error listing all interfaceAddrs from host, error: %v", err) + } + + // Second round of iteration to parse IPs based on cidr. + for _, cidr := range cidrs { + if IsZeroCIDR(cidr) { + continue + } + + _, ipNet, _ := netutils.ParseCIDRSloppy(cidr) + for _, addr := range addrs { + var ip net.IP + // nw.InterfaceAddrs may return net.IPAddr or net.IPNet on windows, and it will return net.IPNet on linux. + switch v := addr.(type) { + case *net.IPAddr: + ip = v.IP + case *net.IPNet: + ip = v.IP + default: + continue + } + + if ipNet.Contains(ip) { + if netutils.IsIPv6(ip) && !uniqueAddressList.Has(IPv6ZeroCIDR) { + uniqueAddressList.Insert(ip.String()) + } + if !netutils.IsIPv6(ip) && !uniqueAddressList.Has(IPv4ZeroCIDR) { + uniqueAddressList.Insert(ip.String()) + } + } + } + } + + if uniqueAddressList.Len() == 0 { + return nil, fmt.Errorf("no addresses found for cidrs %v", cidrs) + } + + return uniqueAddressList, nil +} + +// 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 { + 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 +} diff --git a/pkg/proxy/util/nodeport_addresses_test.go b/pkg/proxy/util/nodeport_addresses_test.go new file mode 100644 index 00000000000..b77f6075902 --- /dev/null +++ b/pkg/proxy/util/nodeport_addresses_test.go @@ -0,0 +1,325 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "net" + "testing" + + "k8s.io/apimachinery/pkg/util/sets" + fake "k8s.io/kubernetes/pkg/proxy/util/testing" + netutils "k8s.io/utils/net" +) + +type InterfaceAddrsPair struct { + itf net.Interface + addrs []net.Addr +} + +func TestGetNodeAddresses(t *testing.T) { + testCases := []struct { + name string + cidrs []string + itfAddrsPairs []InterfaceAddrsPair + expected sets.String + }{ + { + name: "IPv4 single", + cidrs: []string{"10.20.30.0/24"}, + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("10.20.30.51"), Mask: net.CIDRMask(24, 32)}}, + }, + { + itf: net.Interface{Index: 2, MTU: 0, Name: "eth1", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("100.200.201.1"), Mask: net.CIDRMask(24, 32)}}, + }, + }, + expected: sets.NewString("10.20.30.51"), + }, + { + name: "IPv4 zero CIDR", + cidrs: []string{"0.0.0.0/0"}, + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("10.20.30.51"), Mask: net.CIDRMask(24, 32)}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}}, + }, + }, + expected: sets.NewString("0.0.0.0/0"), + }, + { + name: "IPv6 multiple", + cidrs: []string{"2001:db8::/64", "::1/128"}, + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("2001:db8::1"), Mask: net.CIDRMask(64, 128)}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}}, + }, + }, + expected: sets.NewString("2001:db8::1", "::1"), + }, + { + name: "IPv6 zero CIDR", + cidrs: []string{"::/0"}, + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("2001:db8::1"), Mask: net.CIDRMask(64, 128)}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}}, + }, + }, + expected: sets.NewString("::/0"), + }, + { + name: "IPv4 localhost exact", + cidrs: []string{"127.0.0.1/32"}, + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("10.20.30.51"), Mask: net.CIDRMask(24, 32)}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}}, + }, + }, + expected: sets.NewString("127.0.0.1"), + }, + { + name: "IPv4 localhost subnet", + cidrs: []string{"127.0.0.0/8"}, + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.1.1"), Mask: net.CIDRMask(8, 32)}}, + }, + }, + expected: sets.NewString("127.0.1.1"), + }, + { + name: "IPv4 multiple", + cidrs: []string{"10.20.30.0/24", "100.200.201.0/24"}, + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("10.20.30.51"), Mask: net.CIDRMask(24, 32)}}, + }, + { + itf: net.Interface{Index: 2, MTU: 0, Name: "eth1", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("100.200.201.1"), Mask: net.CIDRMask(24, 32)}}, + }, + }, + expected: sets.NewString("10.20.30.51", "100.200.201.1"), + }, + { + name: "IPv4 multiple, no match", + cidrs: []string{"10.20.30.0/24", "100.200.201.0/24"}, + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("192.168.1.2"), Mask: net.CIDRMask(24, 32)}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}}, + }, + }, + expected: nil, + }, + { + name: "empty list, IPv4 addrs", + cidrs: []string{}, + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("192.168.1.2"), Mask: net.CIDRMask(24, 32)}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}}, + }, + }, + expected: sets.NewString("0.0.0.0/0", "::/0"), + }, + { + name: "empty list, IPv6 addrs", + cidrs: []string{}, + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("2001:db8::1"), Mask: net.CIDRMask(64, 128)}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}}, + }, + }, + expected: sets.NewString("0.0.0.0/0", "::/0"), + }, + { + name: "IPv4 redundant CIDRs", + cidrs: []string{"1.2.3.0/24", "0.0.0.0/0"}, + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("1.2.3.4"), Mask: net.CIDRMask(30, 32)}}, + }, + }, + expected: sets.NewString("0.0.0.0/0"), + }, + { + name: "Dual-stack, redundant IPv4", + cidrs: []string{"0.0.0.0/0", "1.2.3.0/24", "2001:db8::1/128"}, + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{ + &net.IPNet{IP: netutils.ParseIPSloppy("1.2.3.4"), Mask: net.CIDRMask(30, 32)}, + &net.IPNet{IP: netutils.ParseIPSloppy("2001:db8::1"), Mask: net.CIDRMask(64, 128)}, + }, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{ + &net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}, + &net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}, + }, + }, + }, + expected: sets.NewString("0.0.0.0/0", "2001:db8::1"), + }, + { + name: "Dual-stack, redundant IPv6", + cidrs: []string{"::/0", "1.2.3.0/24", "2001:db8::1/128"}, + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{ + &net.IPNet{IP: netutils.ParseIPSloppy("1.2.3.4"), Mask: net.CIDRMask(30, 32)}, + &net.IPNet{IP: netutils.ParseIPSloppy("2001:db8::1"), Mask: net.CIDRMask(64, 128)}, + }, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{ + &net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}, + &net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}, + }, + }, + }, + expected: sets.NewString("::/0", "1.2.3.4"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + nw := fake.NewFakeNetwork() + for _, pair := range tc.itfAddrsPairs { + nw.AddInterfaceAddr(&pair.itf, pair.addrs) + } + + addrList, err := GetNodeAddresses(tc.cidrs, nw) + // The fake InterfaceAddrs() never returns an error, so the only + // error GetNodeAddresses will return is "no addresses found". + if err != nil && tc.expected != nil { + t.Errorf("unexpected error: %v", err) + } + + if !addrList.Equal(tc.expected) { + t.Errorf("unexpected mismatch, expected: %v, got: %v", tc.expected, addrList) + } + }) + } +} + +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", + cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "::/0"}, + want: false, + }, + { + 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("ContainsIPv4Loopback() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/proxy/util/utils.go b/pkg/proxy/util/utils.go index 92ef4658082..b67a1c1de49 100644 --- a/pkg/proxy/util/utils.go +++ b/pkg/proxy/util/utils.go @@ -78,37 +78,6 @@ 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 { @@ -228,70 +197,6 @@ func ShouldSkipService(service *v1.Service) bool { return false } -// GetNodeAddresses return all matched node IP addresses based on given cidr slice. -// Some callers, e.g. IPVS proxier, need concrete IPs, not ranges, which is why this exists. -// NetworkInterfacer is injected for test purpose. -// We expect the cidrs passed in is already validated. -// Given an empty input `[]`, it will return `0.0.0.0/0` and `::/0` directly. -// If multiple cidrs is given, it will return the minimal IP sets, e.g. given input `[1.2.0.0/16, 0.0.0.0/0]`, it will -// only return `0.0.0.0/0`. -// NOTE: GetNodeAddresses only accepts CIDRs, if you want concrete IPs, e.g. 1.2.3.4, then the input should be 1.2.3.4/32. -func GetNodeAddresses(cidrs []string, nw NetworkInterfacer) (sets.String, error) { - uniqueAddressList := sets.NewString() - if len(cidrs) == 0 { - uniqueAddressList.Insert(IPv4ZeroCIDR) - uniqueAddressList.Insert(IPv6ZeroCIDR) - return uniqueAddressList, nil - } - // First round of iteration to pick out `0.0.0.0/0` or `::/0` for the sake of excluding non-zero IPs. - for _, cidr := range cidrs { - if IsZeroCIDR(cidr) { - uniqueAddressList.Insert(cidr) - } - } - - addrs, err := nw.InterfaceAddrs() - if err != nil { - return nil, fmt.Errorf("error listing all interfaceAddrs from host, error: %v", err) - } - - // Second round of iteration to parse IPs based on cidr. - for _, cidr := range cidrs { - if IsZeroCIDR(cidr) { - continue - } - - _, ipNet, _ := netutils.ParseCIDRSloppy(cidr) - for _, addr := range addrs { - var ip net.IP - // nw.InterfaceAddrs may return net.IPAddr or net.IPNet on windows, and it will return net.IPNet on linux. - switch v := addr.(type) { - case *net.IPAddr: - ip = v.IP - case *net.IPNet: - ip = v.IP - default: - continue - } - - if ipNet.Contains(ip) { - if netutils.IsIPv6(ip) && !uniqueAddressList.Has(IPv6ZeroCIDR) { - uniqueAddressList.Insert(ip.String()) - } - if !netutils.IsIPv6(ip) && !uniqueAddressList.Has(IPv4ZeroCIDR) { - uniqueAddressList.Insert(ip.String()) - } - } - } - } - - if uniqueAddressList.Len() == 0 { - return nil, fmt.Errorf("no addresses found for cidrs %v", cidrs) - } - - return uniqueAddressList, nil -} - // AddressSet validates the addresses in the slice using the "isValid" function. // Addresses that pass the validation are returned as a string Set. func AddressSet(isValid func(ip net.IP) bool, addrs []net.Addr) sets.String { diff --git a/pkg/proxy/util/utils_test.go b/pkg/proxy/util/utils_test.go index d3138f53203..750fdf8a2d2 100644 --- a/pkg/proxy/util/utils_test.go +++ b/pkg/proxy/util/utils_test.go @@ -18,7 +18,6 @@ package util import ( "context" - "fmt" "math/rand" "net" "reflect" @@ -28,7 +27,6 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" - fake "k8s.io/kubernetes/pkg/proxy/util/testing" netutils "k8s.io/utils/net" ) @@ -399,224 +397,6 @@ func (t *testResolver) LookupIPAddr(_ context.Context, address string) ([]net.IP return t.addrs, t.err } -type InterfaceAddrsPair struct { - itf net.Interface - addrs []net.Addr -} - -func TestGetNodeAddresses(t *testing.T) { - testCases := []struct { - cidrs []string - nw *fake.FakeNetwork - itfAddrsPairs []InterfaceAddrsPair - expected sets.String - expectedErr error - }{ - { // case 0 - cidrs: []string{"10.20.30.0/24"}, - nw: fake.NewFakeNetwork(), - itfAddrsPairs: []InterfaceAddrsPair{ - { - itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("10.20.30.51"), Mask: net.CIDRMask(24, 32)}}, - }, - { - itf: net.Interface{Index: 2, MTU: 0, Name: "eth1", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("100.200.201.1"), Mask: net.CIDRMask(24, 32)}}, - }, - }, - expected: sets.NewString("10.20.30.51"), - }, - { // case 1 - cidrs: []string{"0.0.0.0/0"}, - nw: fake.NewFakeNetwork(), - itfAddrsPairs: []InterfaceAddrsPair{ - { - itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("10.20.30.51"), Mask: net.CIDRMask(24, 32)}}, - }, - { - itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}}, - }, - }, - expected: sets.NewString("0.0.0.0/0"), - }, - { // case 2 - cidrs: []string{"2001:db8::/32", "::1/128"}, - nw: fake.NewFakeNetwork(), - itfAddrsPairs: []InterfaceAddrsPair{ - { - itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("2001:db8::1"), Mask: net.CIDRMask(32, 128)}}, - }, - { - itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}}, - }, - }, - expected: sets.NewString("2001:db8::1", "::1"), - }, - { // case 3 - cidrs: []string{"::/0"}, - nw: fake.NewFakeNetwork(), - itfAddrsPairs: []InterfaceAddrsPair{ - { - itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("2001:db8::1"), Mask: net.CIDRMask(32, 128)}}, - }, - { - itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}}, - }, - }, - expected: sets.NewString("::/0"), - }, - { // case 4 - cidrs: []string{"127.0.0.1/32"}, - nw: fake.NewFakeNetwork(), - itfAddrsPairs: []InterfaceAddrsPair{ - { - itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("10.20.30.51"), Mask: net.CIDRMask(24, 32)}}, - }, - { - itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}}, - }, - }, - expected: sets.NewString("127.0.0.1"), - }, - { // case 5 - cidrs: []string{"127.0.0.0/8"}, - nw: fake.NewFakeNetwork(), - itfAddrsPairs: []InterfaceAddrsPair{ - { - itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.1.1"), Mask: net.CIDRMask(8, 32)}}, - }, - }, - expected: sets.NewString("127.0.1.1"), - }, - { // case 6 - cidrs: []string{"10.20.30.0/24", "100.200.201.0/24"}, - nw: fake.NewFakeNetwork(), - itfAddrsPairs: []InterfaceAddrsPair{ - { - itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("10.20.30.51"), Mask: net.CIDRMask(24, 32)}}, - }, - { - itf: net.Interface{Index: 2, MTU: 0, Name: "eth1", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("100.200.201.1"), Mask: net.CIDRMask(24, 32)}}, - }, - }, - expected: sets.NewString("10.20.30.51", "100.200.201.1"), - }, - { // case 7 - cidrs: []string{"10.20.30.0/24", "100.200.201.0/24"}, - nw: fake.NewFakeNetwork(), - itfAddrsPairs: []InterfaceAddrsPair{ - { - itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("192.168.1.2"), Mask: net.CIDRMask(24, 32)}}, - }, - { - itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}}, - }, - }, - expected: nil, - expectedErr: fmt.Errorf("no addresses found for cidrs %v", []string{"10.20.30.0/24", "100.200.201.0/24"}), - }, - { // case 8 - cidrs: []string{}, - nw: fake.NewFakeNetwork(), - itfAddrsPairs: []InterfaceAddrsPair{ - { - itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("192.168.1.2"), Mask: net.CIDRMask(24, 32)}}, - }, - { - itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}}, - }, - }, - expected: sets.NewString("0.0.0.0/0", "::/0"), - }, - { // case 9 - cidrs: []string{}, - nw: fake.NewFakeNetwork(), - itfAddrsPairs: []InterfaceAddrsPair{ - { - itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("2001:db8::1"), Mask: net.CIDRMask(32, 128)}}, - }, - { - itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}}, - }, - }, - expected: sets.NewString("0.0.0.0/0", "::/0"), - }, - { // case 9 - cidrs: []string{"1.2.3.0/24", "0.0.0.0/0"}, - nw: fake.NewFakeNetwork(), - itfAddrsPairs: []InterfaceAddrsPair{ - { - itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("1.2.3.4"), Mask: net.CIDRMask(30, 32)}}, - }, - }, - expected: sets.NewString("0.0.0.0/0"), - }, - { // case 10 - cidrs: []string{"0.0.0.0/0", "1.2.3.0/24", "::1/128"}, - nw: fake.NewFakeNetwork(), - itfAddrsPairs: []InterfaceAddrsPair{ - { - itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("1.2.3.4"), Mask: net.CIDRMask(30, 32)}}, - }, - { - itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}}, - }, - }, - expected: sets.NewString("0.0.0.0/0", "::1"), - }, - { // case 11 - cidrs: []string{"::/0", "1.2.3.0/24", "::1/128"}, - nw: fake.NewFakeNetwork(), - itfAddrsPairs: []InterfaceAddrsPair{ - { - itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("1.2.3.4"), Mask: net.CIDRMask(30, 32)}}, - }, - { - itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, - addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}}, - }, - }, - expected: sets.NewString("::/0", "1.2.3.4"), - }, - } - - for i := range testCases { - for _, pair := range testCases[i].itfAddrsPairs { - testCases[i].nw.AddInterfaceAddr(&pair.itf, pair.addrs) - } - addrList, err := GetNodeAddresses(testCases[i].cidrs, testCases[i].nw) - if !reflect.DeepEqual(err, testCases[i].expectedErr) { - t.Errorf("case [%d], unexpected error: %v", i, err) - } - - if !addrList.Equal(testCases[i].expected) { - t.Errorf("case [%d], unexpected mismatch, expected: %v, got: %v", i, testCases[i].expected, addrList) - } - } -} - func TestAppendPortIfNeeded(t *testing.T) { testCases := []struct { name string @@ -1405,71 +1185,6 @@ 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) - } - }) - } -} - func TestIsZeroCIDR(t *testing.T) { testCases := []struct { name string