diff --git a/pkg/proxy/util/interface.go b/pkg/proxy/util/interface.go new file mode 100644 index 00000000000..2c6f02c9134 --- /dev/null +++ b/pkg/proxy/util/interface.go @@ -0,0 +1,83 @@ +/* +Copyright 2017 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" +) + +// NetworkInterface defines an interface for several net library functions. Production +// code will forward to net library functions, and unit tests will override the methods +// for testing purposes. +type NetworkInterface interface { + Addrs(intf *net.Interface) ([]net.Addr, error) + Interfaces() ([]net.Interface, error) +} + +// RealNetwork implements the NetworkInterface interface for production code, just +// wrapping the underlying net library function calls. +type RealNetwork struct{} + +// Addrs wraps net.Interface.Addrs() +func (_ RealNetwork) Addrs(intf *net.Interface) ([]net.Addr, error) { + return intf.Addrs() +} + +// Interfaces wraps net.Interfaces() +func (_ RealNetwork) Interfaces() ([]net.Interface, error) { + return net.Interfaces() +} + +// RealNetwork implements the NetworkInterface interface for production code, just +// wrapping the underlying net library function calls. +type FakeNetwork struct { + NetworkInterfaces []net.Interface + // The key of map Addrs is interface name + Address map[string][]net.Addr +} + +func NewFakeNetwork() *FakeNetwork { + return &FakeNetwork{ + NetworkInterfaces: make([]net.Interface, 0), + Address: make(map[string][]net.Addr), + } +} + +// AddInterfaceAddr create an interface and its associated addresses for FakeNetwork implementation. +func (f *FakeNetwork) AddInterfaceAddr(intf *net.Interface, addrs []net.Addr) { + f.NetworkInterfaces = append(f.NetworkInterfaces, *intf) + f.Address[intf.Name] = addrs +} + +// Addrs is part of FakeNetwork interface. +func (f *FakeNetwork) Addrs(intf *net.Interface) ([]net.Addr, error) { + return f.Address[intf.Name], nil +} + +// Interfaces is part of FakeNetwork interface. +func (f *FakeNetwork) Interfaces() ([]net.Interface, error) { + return f.NetworkInterfaces, nil +} + +type AddrStruct struct{ Val string } + +func (a AddrStruct) Network() string { + return a.Val +} +func (a AddrStruct) String() string { + return a.Val +} diff --git a/pkg/proxy/util/utils.go b/pkg/proxy/util/utils.go index cac0140c386..63ae1f7c7b6 100644 --- a/pkg/proxy/util/utils.go +++ b/pkg/proxy/util/utils.go @@ -17,15 +17,32 @@ limitations under the License. package util import ( + "fmt" "net" "k8s.io/apimachinery/pkg/types" + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/pkg/util/sets" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/core/helper" + "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig" + "k8s.io/kubernetes/pkg/util/conntrack" "github.com/golang/glog" ) +const ( + IPv4ZeroCIDR = "0.0.0.0/0" + IPv6ZeroCIDR = "::/0" +) + +func IsZeroCIDR(cidr string) bool { + if cidr == IPv4ZeroCIDR || cidr == IPv6ZeroCIDR { + return true + } + return false +} + func IsLocalIP(ip string) (bool, error) { addrs, err := net.InterfaceAddrs() if err != nil { @@ -56,3 +73,70 @@ func ShouldSkipService(svcName types.NamespacedName, service *api.Service) bool } return false } + +// GetNodeAddresses list all match node IP addresses based on given cidr list. Inject NetworkInterface for test purpose. +// We expect the cidr list passed in is already validated. +// Given `default-route`, it will the IP of host interface having default route. +// Given an empty input `[]`, it will return `0.0.0.0/0` and `::/0` directly. +func GetNodeAddresses(cidrs []string, nw NetworkInterface) (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) + } + } + // Second round of iteration to parse IPs based on cidr. + for _, cidr := range cidrs { + if IsZeroCIDR(cidr) { + continue + } + if cidr == string(kubeproxyconfig.DefaultRoute) { + hostIP, err := utilnet.ChooseHostInterface() + if err != nil { + return nil, fmt.Errorf("error selecting a host interface having default route, error: %v", err) + } + if conntrack.IsIPv6(hostIP) && !uniqueAddressList.Has(IPv6ZeroCIDR) { + uniqueAddressList.Insert(hostIP.String()) + } + if !conntrack.IsIPv6(hostIP) && !uniqueAddressList.Has(IPv4ZeroCIDR) { + uniqueAddressList.Insert(hostIP.String()) + } + continue + } + _, ipNet, _ := net.ParseCIDR(cidr) + itfs, err := nw.Interfaces() + if err != nil { + return nil, fmt.Errorf("error listing all interfaces from host, error: %v", err) + } + for _, itf := range itfs { + addrs, err := nw.Addrs(&itf) + if err != nil { + return nil, fmt.Errorf("error getting address from interface %s, error: %v", itf.Name, err) + } + for _, addr := range addrs { + if addr == nil { + continue + } + ip, _, err := net.ParseCIDR(addr.String()) + if err != nil { + return nil, fmt.Errorf("error parsing CIDR for interface %s, error: %v", itf.Name, err) + } + if ipNet.Contains(ip) { + if conntrack.IsIPv6(ip) && !uniqueAddressList.Has(IPv6ZeroCIDR) { + uniqueAddressList.Insert(ip.String()) + } + if !conntrack.IsIPv6(ip) && !uniqueAddressList.Has(IPv4ZeroCIDR) { + uniqueAddressList.Insert(ip.String()) + } + } + } + } + } + return uniqueAddressList, nil +} diff --git a/pkg/proxy/util/utils_test.go b/pkg/proxy/util/utils_test.go index 5810788714c..74221c2d9e8 100644 --- a/pkg/proxy/util/utils_test.go +++ b/pkg/proxy/util/utils_test.go @@ -17,10 +17,12 @@ limitations under the License. package util import ( + "net" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" api "k8s.io/kubernetes/pkg/apis/core" ) @@ -109,3 +111,218 @@ func TestShouldSkipService(t *testing.T) { } } } + +type InterfaceAddrsPair struct { + itf net.Interface + addrs []net.Addr +} + +func TestGetNodeAddressses(t *testing.T) { + testCases := []struct { + cidrs []string + nw *FakeNetwork + itfAddrsPairs []InterfaceAddrsPair + expected sets.String + }{ + { // case 0 + cidrs: []string{"10.20.30.0/24"}, + nw: NewFakeNetwork(), + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "10.20.30.51/24"}}, + }, + { + itf: net.Interface{Index: 2, MTU: 0, Name: "eth1", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "100.200.201.1/24"}}, + }, + }, + expected: sets.NewString("10.20.30.51"), + }, + { // case 1 + cidrs: []string{"0.0.0.0/0"}, + nw: NewFakeNetwork(), + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "10.20.30.51/24"}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "127.0.0.1/8"}}, + }, + }, + expected: sets.NewString("0.0.0.0/0"), + }, + { // case 2 + cidrs: []string{"2001:db8::/32", "::1/128"}, + nw: NewFakeNetwork(), + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "2001:db8::1/32"}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "::1/128"}}, + }, + }, + expected: sets.NewString("2001:db8::1", "::1"), + }, + { // case 3 + cidrs: []string{"::/0"}, + nw: NewFakeNetwork(), + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "2001:db8::1/32"}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "::1/128"}}, + }, + }, + expected: sets.NewString("::/0"), + }, + { // case 4 + cidrs: []string{"127.0.0.1/32"}, + nw: NewFakeNetwork(), + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "10.20.30.51/24"}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "127.0.0.1/8"}}, + }, + }, + expected: sets.NewString("127.0.0.1"), + }, + { // case 5 + cidrs: []string{"127.0.0.0/8"}, + nw: NewFakeNetwork(), + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "127.0.1.1/8"}}, + }, + }, + expected: sets.NewString("127.0.1.1"), + }, + { // case 6 + cidrs: []string{"10.20.30.0/24", "100.200.201.0/24"}, + nw: NewFakeNetwork(), + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "10.20.30.51/24"}}, + }, + { + itf: net.Interface{Index: 2, MTU: 0, Name: "eth1", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "100.200.201.1/24"}}, + }, + }, + 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: NewFakeNetwork(), + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "192.168.1.2/24"}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "127.0.0.1/8"}}, + }, + }, + expected: sets.NewString(), + }, + { // case 8 + cidrs: []string{}, + nw: NewFakeNetwork(), + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "192.168.1.2/24"}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "127.0.0.1/8"}}, + }, + }, + expected: sets.NewString("0.0.0.0/0", "::/0"), + }, + { // case 9 + cidrs: []string{}, + nw: NewFakeNetwork(), + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "2001:db8::1/32"}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "::1/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: NewFakeNetwork(), + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "1.2.3.4/30"}}, + }, + }, + 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: NewFakeNetwork(), + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "1.2.3.4/30"}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "::1/128"}}, + }, + }, + expected: sets.NewString("0.0.0.0/0", "::1"), + }, + { // case 11 + cidrs: []string{"::/0", "1.2.3.0/24", "::1/128"}, + nw: NewFakeNetwork(), + itfAddrsPairs: []InterfaceAddrsPair{ + { + itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "1.2.3.4/30"}}, + }, + { + itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, + addrs: []net.Addr{AddrStruct{Val: "::1/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 err != nil { + 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) + } + } +}