diff --git a/staging/src/k8s.io/apimachinery/pkg/util/net/interface.go b/staging/src/k8s.io/apimachinery/pkg/util/net/interface.go index 95d398c86e5..e8d090e4513 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/net/interface.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/net/interface.go @@ -43,6 +43,7 @@ type Route struct { // TODO: add more fields here if needed } +// getRoutes obtains the IPv4 routes, and filters out non-default routes. func getRoutes(input io.Reader) ([]Route, error) { routes := []Route{} if input == nil { @@ -59,24 +60,30 @@ func getRoutes(input io.Reader) ([]Route, error) { continue } fields := strings.Fields(line) - routes = append(routes, Route{}) - route := &routes[len(routes)-1] - route.Interface = fields[0] - ip, err := parseIP(fields[1]) + dest, err := parseHexToIPv4(fields[1]) if err != nil { return nil, err } - route.Destination = ip - ip, err = parseIP(fields[2]) + gw, err := parseHexToIPv4(fields[2]) if err != nil { return nil, err } - route.Gateway = ip + if !dest.Equal(net.IPv4zero) { + continue + } + routes = append(routes, Route{ + Interface: fields[0], + Destination: dest, + Gateway: gw, + }) } return routes, nil } -func parseIP(str string) (net.IP, error) { +// parseHexToIPv4 takes the hex IP address string from route file and converts it +// from little endian to big endian for creation of a net.IP address. +// a net.IP, using big endian ordering. +func parseHexToIPv4(str string) (net.IP, error) { if str == "" { return nil, fmt.Errorf("input is nil") } @@ -84,12 +91,10 @@ func parseIP(str string) (net.IP, error) { if err != nil { return nil, err } - //TODO add ipv6 support if len(bytes) != net.IPv4len { - return nil, fmt.Errorf("only IPv4 is supported") + return nil, fmt.Errorf("invalid IPv4 address in route") } - bytes[0], bytes[1], bytes[2], bytes[3] = bytes[3], bytes[2], bytes[1], bytes[0] - return net.IP(bytes), nil + return net.IP([]byte{bytes[3], bytes[2], bytes[1], bytes[0]}), nil } func isInterfaceUp(intf *net.Interface) bool { @@ -107,10 +112,18 @@ func isLoopbackOrPointToPoint(intf *net.Interface) bool { return intf.Flags&(net.FlagLoopback|net.FlagPointToPoint) != 0 } -//getFinalIP method receives all the IP addrs of a Interface -//and returns a nil if the address is Loopback, Ipv6, link-local or nil. -//It returns a valid IPv4 if an Ipv4 address is found in the array. -func getFinalIP(addrs []net.Addr) (net.IP, error) { +func inFamily(ip net.IP, expectedFamily AddressFamily) bool { + ipFamily := familyIPv4 + if ip.To4() == nil { + ipFamily = familyIPv6 + } + return ipFamily == expectedFamily +} + +// getMatchingGlobalIP method checks all the IP addresses of a Interface looking +// for a valid non-loopback/link-local address of the requested family and returns +// it, if found. +func getMatchingGlobalIP(addrs []net.Addr, family AddressFamily) (net.IP, error) { if len(addrs) > 0 { for i := range addrs { glog.V(4).Infof("Checking addr %s.", addrs[i].String()) @@ -118,17 +131,15 @@ func getFinalIP(addrs []net.Addr) (net.IP, error) { if err != nil { return nil, err } - //Only IPv4 - //TODO : add IPv6 support - if ip.To4() != nil { - if !ip.IsLoopback() && !ip.IsLinkLocalMulticast() && !ip.IsLinkLocalUnicast() { + if inFamily(ip, family) { + if ip.IsGlobalUnicast() { glog.V(4).Infof("IP found %v", ip) return ip, nil } else { - glog.V(4).Infof("Loopback/link-local found %v", ip) + glog.V(4).Infof("non-global IP found %v", ip) } } else { - glog.V(4).Infof("%v is not a valid IPv4 address", ip) + glog.V(4).Infof("%v is not an IPv%d address", ip, int(family)) } } @@ -136,7 +147,7 @@ func getFinalIP(addrs []net.Addr) (net.IP, error) { return nil, nil } -func getIPFromInterface(intfName string, nw networkInterfacer) (net.IP, error) { +func getIPFromInterface(intfName string, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) { intf, err := nw.InterfaceByName(intfName) if err != nil { return nil, err @@ -147,16 +158,15 @@ func getIPFromInterface(intfName string, nw networkInterfacer) (net.IP, error) { return nil, err } glog.V(4).Infof("Interface %q has %d addresses :%v.", intfName, len(addrs), addrs) - finalIP, err := getFinalIP(addrs) + matchingIP, err := getMatchingGlobalIP(addrs, forFamily) if err != nil { return nil, err } - if finalIP != nil { - glog.V(4).Infof("valid IPv4 address for interface %q found as %v.", intfName, finalIP) - return finalIP, nil + if matchingIP != nil { + glog.V(4).Infof("Found valid IPv%d address %v for interface %q.", int(forFamily), matchingIP, intfName) + return matchingIP, nil } } - return nil, nil } @@ -268,27 +278,30 @@ func chooseHostInterfaceFromRoute(inFile io.Reader, nw networkInterfacer) (net.I if err != nil { return nil, err } - zero := net.IP{0, 0, 0, 0} - var finalIP net.IP - for i := range routes { - //find interface with gateway - if routes[i].Destination.Equal(zero) { - glog.V(4).Infof("Default route transits interface %q", routes[i].Interface) - finalIP, err := getIPFromInterface(routes[i].Interface, nw) + if len(routes) == 0 { + return nil, fmt.Errorf("No default routes.") + } + // TODO: append IPv6 routes for processing - currently only have IPv4 routes + for _, family := range []AddressFamily{familyIPv4, familyIPv6} { + glog.V(4).Infof("Looking for default routes with IPv%d addresses", uint(family)) + for _, route := range routes { + // TODO: When have IPv6 routes, filter here to speed up processing + // if route.Family != family { + // continue + // } + glog.V(4).Infof("Default route transits interface %q", route.Interface) + finalIP, err := getIPFromInterface(route.Interface, family, nw) if err != nil { return nil, err } if finalIP != nil { - glog.V(4).Infof("Choosing IP %v ", finalIP) + glog.V(4).Infof("Found active IP %v ", finalIP) return finalIP, nil } } } - glog.V(4).Infof("No valid IP found") - if finalIP == nil { - return nil, fmt.Errorf("Unable to select an IP.") - } - return nil, nil + glog.V(4).Infof("No active IP found by looking at default routes") + return nil, fmt.Errorf("unable to select an IP from default routes.") } // If bind-address is usable, return it directly diff --git a/staging/src/k8s.io/apimachinery/pkg/util/net/interface_test.go b/staging/src/k8s.io/apimachinery/pkg/util/net/interface_test.go index ab810cca5cd..373af00b5c6 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/net/interface_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/net/interface_test.go @@ -48,19 +48,19 @@ virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 ` const nothing = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT ` -const gatewayfirstIpv6_1 = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +const badDestination = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT eth3 00000000 0100FE0A 0003 0 0 1024 00000000 0 0 0 eth3 0000FE0AA1 00000000 0001 0 0 0 0080FFFF 0 0 0 docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 ` -const gatewayfirstIpv6_2 = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +const badGateway = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT eth3 00000000 0100FE0AA1 0003 0 0 1024 00000000 0 0 0 eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0 docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 ` -const route_Invalidhex = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +const route_Invalidhex = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT eth3 00000000 0100FE0AA 0003 0 0 1024 00000000 0 0 0 eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0 docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 @@ -97,25 +97,42 @@ var ( upIntf = makeIntf(1, "eth3", flagUp) ) +var ( + ipv4Route = Route{Interface: "eth3", Gateway: net.ParseIP("10.254.0.1")} +) + func TestGetRoutes(t *testing.T) { testCases := []struct { - tcase string - route string - expected int + tcase string + route string + count int + expected *Route + errStrFrag string }{ - {"gatewayfirst", gatewayfirst, 4}, - {"gatewaymiddle", gatewaymiddle, 4}, - {"gatewaylast", gatewaylast, 4}, - {"nothing", nothing, 0}, - {"gatewayfirstIpv6_1", gatewayfirstIpv6_1, 0}, - {"gatewayfirstIpv6_2", gatewayfirstIpv6_2, 0}, - {"route_Invalidhex", route_Invalidhex, 0}, + {"gatewayfirst", gatewayfirst, 1, &ipv4Route, ""}, + {"gatewaymiddle", gatewaymiddle, 1, &ipv4Route, ""}, + {"gatewaylast", gatewaylast, 1, &ipv4Route, ""}, + {"no routes", nothing, 0, nil, ""}, + {"badDestination", badDestination, 0, nil, "invalid IPv4"}, + {"badGateway", badGateway, 0, nil, "invalid IPv4"}, + {"route_Invalidhex", route_Invalidhex, 0, nil, "odd length hex string"}, + {"no default routes", noInternetConnection, 0, nil, ""}, } for _, tc := range testCases { r := strings.NewReader(tc.route) routes, err := getRoutes(r) - if len(routes) != tc.expected { - t.Errorf("case[%v]: expected %v, got %v .err : %v", tc.tcase, tc.expected, len(routes), err) + if err != nil { + if !strings.Contains(err.Error(), tc.errStrFrag) { + t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) + } + } else if tc.errStrFrag != "" { + t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag) + } else { + if tc.count != len(routes) { + t.Errorf("case[%s]: expected %d routes, have %v", tc.tcase, tc.count, routes) + } else if tc.count == 1 && !tc.expected.Gateway.Equal(routes[0].Gateway) { + t.Errorf("case[%s]: expected %v, got %v .err : %v", tc.tcase, tc.expected, routes, err) + } } } } @@ -136,7 +153,7 @@ func TestParseIP(t *testing.T) { {"valid", "12345678", true, net.IP{120, 86, 52, 18}}, } for _, tc := range testCases { - ip, err := parseIP(tc.ip) + ip, err := parseHexToIPv4(tc.ip) if !ip.Equal(tc.expected) { t.Errorf("case[%v]: expected %q, got %q . err : %v", tc.tcase, tc.expected, ip, err) } @@ -146,15 +163,15 @@ func TestParseIP(t *testing.T) { func TestIsInterfaceUp(t *testing.T) { testCases := []struct { tcase string - intf net.Interface + intf *net.Interface expected bool }{ - {"up", net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: net.FlagUp}, true}, - {"down", net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: 0}, false}, - {"nothing", net.Interface{}, false}, + {"up", &net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: net.FlagUp}, true}, + {"down", &net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: 0}, false}, + {"no interface", nil, false}, } for _, tc := range testCases { - it := isInterfaceUp(&tc.intf) + it := isInterfaceUp(tc.intf) if it != tc.expected { t.Errorf("case[%v]: expected %v, got %v .", tc.tcase, tc.expected, it) } @@ -174,17 +191,24 @@ func TestFinalIP(t *testing.T) { testCases := []struct { tcase string addr []net.Addr + family AddressFamily expected net.IP }{ - {"ipv6", []net.Addr{addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}}, nil}, - {"invalidCIDR", []net.Addr{addrStruct{val: "fe80::2f7:67fff:fe6e:2956/64"}}, nil}, - {"loopback", []net.Addr{addrStruct{val: "127.0.0.1/24"}}, nil}, - {"ip4", []net.Addr{addrStruct{val: "10.254.12.132/17"}}, net.ParseIP("10.254.12.132")}, + {"no ipv4", []net.Addr{addrStruct{val: "2001::5/64"}}, familyIPv4, nil}, + {"no ipv6", []net.Addr{addrStruct{val: "10.128.0.4/32"}}, familyIPv6, nil}, + {"invalidV4CIDR", []net.Addr{addrStruct{val: "10.20.30.40.50/24"}}, familyIPv4, nil}, + {"invalidV6CIDR", []net.Addr{addrStruct{val: "fe80::2f7:67fff:fe6e:2956/64"}}, familyIPv6, nil}, + {"loopback", []net.Addr{addrStruct{val: "127.0.0.1/24"}}, familyIPv4, nil}, + {"loopbackv6", []net.Addr{addrStruct{val: "::1/128"}}, familyIPv6, nil}, + {"link local v4", []net.Addr{addrStruct{val: "169.254.1.10/16"}}, familyIPv4, nil}, + {"link local v6", []net.Addr{addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}}, familyIPv6, nil}, + {"ip4", []net.Addr{addrStruct{val: "10.254.12.132/17"}}, familyIPv4, net.ParseIP("10.254.12.132")}, + {"ip6", []net.Addr{addrStruct{val: "2001::5/64"}}, familyIPv6, net.ParseIP("2001::5")}, - {"nothing", []net.Addr{}, nil}, + {"no addresses", []net.Addr{}, familyIPv4, nil}, } for _, tc := range testCases { - ip, err := getFinalIP(tc.addr) + ip, err := getMatchingGlobalIP(tc.addr, tc.family) if !ip.Equal(tc.expected) { t.Errorf("case[%v]: expected %v, got %v .err : %v", tc.tcase, tc.expected, ip, err) } @@ -203,40 +227,23 @@ func TestAddrs(t *testing.T) { } } +// Has a valid IPv4 address (IPv6 is LLA) type validNetworkInterface struct { } func (_ validNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { - c := net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: net.FlagUp} - return &c, nil + return &upIntf, nil } func (_ validNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { var ifat []net.Addr ifat = []net.Addr{ - addrStruct{val: "2001::200/64"}, addrStruct{val: "10.254.71.145/17"}} + addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}, addrStruct{val: "10.254.71.145/17"}} return ifat, nil } func (_ validNetworkInterface) Interfaces() ([]net.Interface, error) { return []net.Interface{upIntf}, nil } -// First is link-local, second is not. -type validNetworkInterfaceWithLinkLocal struct { -} - -func (_ validNetworkInterfaceWithLinkLocal) InterfaceByName(intfName string) (*net.Interface, error) { - c := net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: net.FlagUp} - return &c, nil -} -func (_ validNetworkInterfaceWithLinkLocal) Addrs(intf *net.Interface) ([]net.Addr, error) { - var ifat []net.Addr - ifat = []net.Addr{addrStruct{val: "169.254.162.166/16"}, addrStruct{val: "45.55.47.146/19"}} - return ifat, nil -} -func (_ validNetworkInterfaceWithLinkLocal) Interfaces() ([]net.Interface, error) { - return []net.Interface{upIntf}, nil -} - // Interface with only IPv6 address type ipv6NetworkInterface struct { } @@ -249,6 +256,7 @@ func (_ ipv6NetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { ifat = []net.Addr{addrStruct{val: "2001::200/64"}} return ifat, nil } + func (_ ipv6NetworkInterface) Interfaces() ([]net.Interface, error) { return []net.Interface{upIntf}, nil } @@ -288,7 +296,7 @@ type noNetworkInterface struct { } func (_ noNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { - return nil, fmt.Errorf("unable get Interface") + return nil, fmt.Errorf("no such network interface") } func (_ noNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { return nil, nil @@ -395,18 +403,31 @@ func (_ networkInterfaceWithInvalidAddr) Interfaces() ([]net.Interface, error) { func TestGetIPFromInterface(t *testing.T) { testCases := []struct { - tcase string - nwname string - nw networkInterfacer - expected net.IP + tcase string + nwname string + family AddressFamily + nw networkInterfacer + expected net.IP + errStrFrag string }{ - {"valid", "eth3", validNetworkInterface{}, net.ParseIP("10.254.71.145")}, - {"ipv6", "eth3", ipv6NetworkInterface{}, nil}, - {"nothing", "eth3", noNetworkInterface{}, nil}, + {"ipv4", "eth3", familyIPv4, validNetworkInterface{}, net.ParseIP("10.254.71.145"), ""}, + {"ipv6", "eth3", familyIPv6, ipv6NetworkInterface{}, net.ParseIP("2001::200"), ""}, + {"no ipv4", "eth3", familyIPv4, ipv6NetworkInterface{}, nil, ""}, + {"no ipv6", "eth3", familyIPv6, validNetworkInterface{}, nil, ""}, + {"I/F down", "eth3", familyIPv4, downNetworkInterface{}, nil, ""}, + {"I/F get fail", "eth3", familyIPv4, noNetworkInterface{}, nil, "no such network interface"}, + {"fail get addr", "eth3", familyIPv4, networkInterfaceFailGetAddrs{}, nil, "unable to get Addrs"}, + {"bad addr", "eth3", familyIPv4, networkInterfaceWithInvalidAddr{}, nil, "invalid CIDR"}, } for _, tc := range testCases { - ip, err := getIPFromInterface(tc.nwname, tc.nw) - if !ip.Equal(tc.expected) { + ip, err := getIPFromInterface(tc.nwname, tc.family, tc.nw) + if err != nil { + if !strings.Contains(err.Error(), tc.errStrFrag) { + t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) + } + } else if tc.errStrFrag != "" { + t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag) + } else if !ip.Equal(tc.expected) { t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err) } } @@ -419,17 +440,14 @@ func TestChooseHostInterfaceFromRoute(t *testing.T) { nw networkInterfacer expected net.IP }{ - {"valid_routefirst", strings.NewReader(gatewayfirst), validNetworkInterface{}, net.ParseIP("10.254.71.145")}, - {"valid_routelast", strings.NewReader(gatewaylast), validNetworkInterface{}, net.ParseIP("10.254.71.145")}, - {"valid_routemiddle", strings.NewReader(gatewaymiddle), validNetworkInterface{}, net.ParseIP("10.254.71.145")}, - {"valid_routemiddle_ipv6", strings.NewReader(gatewaymiddle), ipv6NetworkInterface{}, nil}, - {"no internet connection", strings.NewReader(noInternetConnection), validNetworkInterface{}, nil}, - {"1st ip link-local", strings.NewReader(gatewayfirstLinkLocal), validNetworkInterfaceWithLinkLocal{}, net.ParseIP("45.55.47.146")}, - {"no route", strings.NewReader(nothing), validNetworkInterface{}, nil}, + {"ipv4", strings.NewReader(gatewayfirst), validNetworkInterface{}, net.ParseIP("10.254.71.145")}, + {"ipv6", strings.NewReader(gatewaymiddle), ipv6NetworkInterface{}, net.ParseIP("2001::200")}, + {"no non-link-local ip", strings.NewReader(gatewaymiddle), networkInterfaceWithOnlyLinkLocals{}, nil}, + {"no routes", strings.NewReader(nothing), validNetworkInterface{}, nil}, {"no route file", nil, validNetworkInterface{}, nil}, {"no interfaces", nil, noNetworkInterface{}, nil}, - {"no interface Addrs", strings.NewReader(gatewaymiddle), networkInterfaceWithNoAddrs{}, nil}, - {"Invalid Addrs", strings.NewReader(gatewaymiddle), ipv6NetworkInterface{}, nil}, + {"no interface addrs", strings.NewReader(gatewaymiddle), networkInterfaceWithNoAddrs{}, nil}, + {"fail get addrs", strings.NewReader(gatewaymiddle), networkInterfaceFailGetAddrs{}, nil}, } for _, tc := range testCases { ip, err := chooseHostInterfaceFromRoute(tc.inFile, tc.nw)