mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 22:46:12 +00:00
Merge pull request #46874 from pmichali/issue44848c
Automatic merge from submit-queue (batch tested with PRs 46210, 48607, 46874, 46598, 49240) Ipv6 support for ChooseHostInterface (part 3 of 3) **What this PR does / why we need it**: Provides IPv6 support for the ChooseHostInterface() and ChooseBindAddress() functions in the apimachinery's util/net/interface.go file. It strives to maintain backward compatibility with existing use of IPv4, but allow IPv6 to be used when available. This is part 3 of a set of PRs, and has been rebased on top of #46138, which is rebased on top of #46044. This is part of an overall effort to provide IPv6 support in the code, by addressing places where IPv4 logic exists and making it IPv6 ready. **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #44848 **Special notes for your reviewer**: You can look at the other PRs for the commit description of what each of them does. I do see some unrelated test failures in those commits - appears to be flakes. **Release note**: ```release-noteNONE ```
This commit is contained in:
commit
c90fda8260
@ -36,19 +36,40 @@ const (
|
||||
familyIPv6 AddressFamily = 6
|
||||
)
|
||||
|
||||
const (
|
||||
ipv4RouteFile = "/proc/net/route"
|
||||
ipv6RouteFile = "/proc/net/ipv6_route"
|
||||
)
|
||||
|
||||
type Route struct {
|
||||
Interface string
|
||||
Destination net.IP
|
||||
Gateway net.IP
|
||||
// TODO: add more fields here if needed
|
||||
Family AddressFamily
|
||||
}
|
||||
|
||||
// getRoutes obtains the IPv4 routes, and filters out non-default routes.
|
||||
func getRoutes(input io.Reader) ([]Route, error) {
|
||||
routes := []Route{}
|
||||
if input == nil {
|
||||
return nil, fmt.Errorf("input is nil")
|
||||
type RouteFile struct {
|
||||
name string
|
||||
parse func(input io.Reader) ([]Route, error)
|
||||
}
|
||||
|
||||
var (
|
||||
v4File = RouteFile{name: ipv4RouteFile, parse: getIPv4DefaultRoutes}
|
||||
v6File = RouteFile{name: ipv6RouteFile, parse: getIPv6DefaultRoutes}
|
||||
)
|
||||
|
||||
func (rf RouteFile) extract() ([]Route, error) {
|
||||
file, err := os.Open(rf.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
return rf.parse(file)
|
||||
}
|
||||
|
||||
// getIPv4DefaultRoutes obtains the IPv4 routes, and filters out non-default routes.
|
||||
func getIPv4DefaultRoutes(input io.Reader) ([]Route, error) {
|
||||
routes := []Route{}
|
||||
scanner := bufio.NewReader(input)
|
||||
for {
|
||||
line, err := scanner.ReadString('\n')
|
||||
@ -60,11 +81,15 @@ func getRoutes(input io.Reader) ([]Route, error) {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
dest, err := parseHexToIPv4(fields[1])
|
||||
// Interested in fields:
|
||||
// 0 - interface name
|
||||
// 1 - destination address
|
||||
// 2 - gateway
|
||||
dest, err := parseIP(fields[1], familyIPv4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gw, err := parseHexToIPv4(fields[2])
|
||||
gw, err := parseIP(fields[2], familyIPv4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -75,15 +100,52 @@ func getRoutes(input io.Reader) ([]Route, error) {
|
||||
Interface: fields[0],
|
||||
Destination: dest,
|
||||
Gateway: gw,
|
||||
Family: familyIPv4,
|
||||
})
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func getIPv6DefaultRoutes(input io.Reader) ([]Route, error) {
|
||||
routes := []Route{}
|
||||
scanner := bufio.NewReader(input)
|
||||
for {
|
||||
line, err := scanner.ReadString('\n')
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
// Interested in fields:
|
||||
// 0 - destination address
|
||||
// 4 - gateway
|
||||
// 9 - interface name
|
||||
dest, err := parseIP(fields[0], familyIPv6)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gw, err := parseIP(fields[4], familyIPv6)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !dest.Equal(net.IPv6zero) {
|
||||
continue
|
||||
}
|
||||
if gw.Equal(net.IPv6zero) {
|
||||
continue // loopback
|
||||
}
|
||||
routes = append(routes, Route{
|
||||
Interface: fields[9],
|
||||
Destination: dest,
|
||||
Gateway: gw,
|
||||
Family: familyIPv6,
|
||||
})
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// parseIP takes the hex IP address string from route file and converts it
|
||||
// to a net.IP address. For IPv4, the value must be converted to big endian.
|
||||
func parseIP(str string, family AddressFamily) (net.IP, error) {
|
||||
if str == "" {
|
||||
return nil, fmt.Errorf("input is nil")
|
||||
}
|
||||
@ -91,10 +153,17 @@ func parseHexToIPv4(str string) (net.IP, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(bytes) != net.IPv4len {
|
||||
return nil, fmt.Errorf("invalid IPv4 address in route")
|
||||
if family == familyIPv4 {
|
||||
if len(bytes) != net.IPv4len {
|
||||
return nil, fmt.Errorf("invalid IPv4 address in route")
|
||||
}
|
||||
return net.IP([]byte{bytes[3], bytes[2], bytes[1], bytes[0]}), nil
|
||||
}
|
||||
return net.IP([]byte{bytes[3], bytes[2], bytes[1], bytes[0]}), nil
|
||||
// Must be IPv6
|
||||
if len(bytes) != net.IPv6len {
|
||||
return nil, fmt.Errorf("invalid IPv6 address in route")
|
||||
}
|
||||
return net.IP(bytes), nil
|
||||
}
|
||||
|
||||
func isInterfaceUp(intf *net.Interface) bool {
|
||||
@ -112,17 +181,8 @@ func isLoopbackOrPointToPoint(intf *net.Interface) bool {
|
||||
return intf.Flags&(net.FlagLoopback|net.FlagPointToPoint) != 0
|
||||
}
|
||||
|
||||
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.
|
||||
// getMatchingGlobalIP returns the first valid global unicast address of the given
|
||||
// 'family' from the list of 'addrs'.
|
||||
func getMatchingGlobalIP(addrs []net.Addr, family AddressFamily) (net.IP, error) {
|
||||
if len(addrs) > 0 {
|
||||
for i := range addrs {
|
||||
@ -131,12 +191,12 @@ func getMatchingGlobalIP(addrs []net.Addr, family AddressFamily) (net.IP, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if inFamily(ip, family) {
|
||||
if memberOf(ip, family) {
|
||||
if ip.IsGlobalUnicast() {
|
||||
glog.V(4).Infof("IP found %v", ip)
|
||||
return ip, nil
|
||||
} else {
|
||||
glog.V(4).Infof("non-global IP found %v", ip)
|
||||
glog.V(4).Infof("Non-global unicast address found %v", ip)
|
||||
}
|
||||
} else {
|
||||
glog.V(4).Infof("%v is not an IPv%d address", ip, int(family))
|
||||
@ -147,6 +207,8 @@ func getMatchingGlobalIP(addrs []net.Addr, family AddressFamily) (net.IP, error)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// getIPFromInterface gets the IPs on an interface and returns a global unicast address, if any. The
|
||||
// interface must be up, the IP must in the family requested, and the IP must be a global unicast address.
|
||||
func getIPFromInterface(intfName string, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) {
|
||||
intf, err := nw.InterfaceByName(intfName)
|
||||
if err != nil {
|
||||
@ -231,21 +293,21 @@ func chooseIPFromHostInterfaces(nw networkInterfacer) (net.IP, error) {
|
||||
return nil, fmt.Errorf("no acceptable interface with global unicast address found on host")
|
||||
}
|
||||
|
||||
//ChooseHostInterface is a method used fetch an IP for a daemon.
|
||||
//It uses data from /proc/net/route file.
|
||||
//For a node with no internet connection ,it returns error
|
||||
//For a multi n/w interface node it returns the IP of the interface with gateway on it.
|
||||
// ChooseHostInterface is a method used fetch an IP for a daemon.
|
||||
// If there is no routing info file, it will choose a global IP from the system
|
||||
// interfaces. Otherwise, it will use IPv4 and IPv6 route information to return the
|
||||
// IP of the interface with a gateway on it (with priority given to IPv4). For a node
|
||||
// with no internet connection, it returns error.
|
||||
func ChooseHostInterface() (net.IP, error) {
|
||||
var nw networkInterfacer = networkInterface{}
|
||||
inFile, err := os.Open("/proc/net/route")
|
||||
if _, err := os.Stat(ipv4RouteFile); os.IsNotExist(err) {
|
||||
return chooseIPFromHostInterfaces(nw)
|
||||
}
|
||||
routes, err := getAllDefaultRoutes()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return chooseIPFromHostInterfaces(nw)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer inFile.Close()
|
||||
return chooseHostInterfaceFromRoute(inFile, nw)
|
||||
return chooseHostInterfaceFromRoute(routes, nw)
|
||||
}
|
||||
|
||||
// networkInterfacer defines an interface for several net library functions. Production
|
||||
@ -273,22 +335,33 @@ func (_ networkInterface) Interfaces() ([]net.Interface, error) {
|
||||
return net.Interfaces()
|
||||
}
|
||||
|
||||
func chooseHostInterfaceFromRoute(inFile io.Reader, nw networkInterfacer) (net.IP, error) {
|
||||
routes, err := getRoutes(inFile)
|
||||
// getAllDefaultRoutes obtains IPv4 and IPv6 default routes on the node. If unable
|
||||
// to read the IPv4 routing info file, we return an error. If unable to read the IPv6
|
||||
// routing info file (which is optional), we'll just use the IPv4 route information.
|
||||
// Using all the routing info, if no default routes are found, an error is returned.
|
||||
func getAllDefaultRoutes() ([]Route, error) {
|
||||
routes, err := v4File.extract()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v6Routes, _ := v6File.extract()
|
||||
routes = append(routes, v6Routes...)
|
||||
if len(routes) == 0 {
|
||||
return nil, fmt.Errorf("No default routes.")
|
||||
}
|
||||
// TODO: append IPv6 routes for processing - currently only have IPv4 routes
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// chooseHostInterfaceFromRoute cycles through each default route provided, looking for a
|
||||
// global IP address from the interface for the route. Will first look all each IPv4 route for
|
||||
// an IPv4 IP, and then will look at each IPv6 route for an IPv6 IP.
|
||||
func chooseHostInterfaceFromRoute(routes []Route, nw networkInterfacer) (net.IP, error) {
|
||||
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
|
||||
// }
|
||||
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 {
|
||||
|
@ -18,8 +18,9 @@ package net
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@ -67,10 +68,30 @@ docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0
|
||||
virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0
|
||||
`
|
||||
|
||||
// Based on DigitalOcean COREOS
|
||||
const gatewayfirstLinkLocal = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
|
||||
eth0 00000000 0120372D 0001 0 0 0 00000000 0 0 0
|
||||
eth0 00000000 00000000 0001 0 0 2048 00000000 0 0 0
|
||||
const v6gatewayfirst = `00000000000000000000000000000000 00 00000000000000000000000000000000 00 20010001000000000000000000000001 00000064 00000000 00000000 00000003 eth3
|
||||
20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3
|
||||
00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo
|
||||
`
|
||||
const v6gatewaylast = `20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3
|
||||
00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo
|
||||
00000000000000000000000000000000 00 00000000000000000000000000000000 00 20010001000000000000000000000001 00000064 00000000 00000000 00000003 eth3
|
||||
`
|
||||
const v6gatewaymiddle = `20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3
|
||||
00000000000000000000000000000000 00 00000000000000000000000000000000 00 20010001000000000000000000000001 00000064 00000000 00000000 00000003 eth3
|
||||
00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo
|
||||
`
|
||||
const v6noDefaultRoutes = `00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo
|
||||
20010001000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00000001 docker0
|
||||
20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3
|
||||
fe800000000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3
|
||||
`
|
||||
const v6nothing = ``
|
||||
const v6badDestination = `2001000200000000 7a 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo
|
||||
`
|
||||
const v6badGateway = `00000000000000000000000000000000 00 00000000000000000000000000000000 00 200100010000000000000000000000000012 00000064 00000000 00000000 00000003 eth3
|
||||
`
|
||||
const v6route_Invalidhex = `000000000000000000000000000000000 00 00000000000000000000000000000000 00 fe80000000000000021fcafffea0ec00 00000064 00000000 00000000 00000003 enp1s0f0
|
||||
|
||||
`
|
||||
|
||||
const (
|
||||
@ -98,10 +119,18 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
ipv4Route = Route{Interface: "eth3", Gateway: net.ParseIP("10.254.0.1")}
|
||||
ipv4Route = Route{Interface: "eth3", Destination: net.ParseIP("0.0.0.0"), Gateway: net.ParseIP("10.254.0.1"), Family: familyIPv4}
|
||||
ipv6Route = Route{Interface: "eth3", Destination: net.ParseIP("::"), Gateway: net.ParseIP("2001:1::1"), Family: familyIPv6}
|
||||
)
|
||||
|
||||
func TestGetRoutes(t *testing.T) {
|
||||
var (
|
||||
noRoutes = []Route{}
|
||||
routeV4 = []Route{ipv4Route}
|
||||
routeV6 = []Route{ipv6Route}
|
||||
bothRoutes = []Route{ipv4Route, ipv6Route}
|
||||
)
|
||||
|
||||
func TestGetIPv4Routes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
tcase string
|
||||
route string
|
||||
@ -120,7 +149,7 @@ func TestGetRoutes(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
r := strings.NewReader(tc.route)
|
||||
routes, err := getRoutes(r)
|
||||
routes, err := getIPv4DefaultRoutes(r)
|
||||
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)
|
||||
@ -130,8 +159,55 @@ func TestGetRoutes(t *testing.T) {
|
||||
} 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)
|
||||
} else if tc.count == 1 {
|
||||
if !tc.expected.Gateway.Equal(routes[0].Gateway) {
|
||||
t.Errorf("case[%s]: expected %v, got %v .err : %v", tc.tcase, tc.expected, routes, err)
|
||||
}
|
||||
if !routes[0].Destination.Equal(net.IPv4zero) {
|
||||
t.Errorf("case[%s}: destination is not for default route (not zero)", tc.tcase)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIPv6Routes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
tcase string
|
||||
route string
|
||||
count int
|
||||
expected *Route
|
||||
errStrFrag string
|
||||
}{
|
||||
{"v6 gatewayfirst", v6gatewayfirst, 1, &ipv6Route, ""},
|
||||
{"v6 gatewaymiddle", v6gatewaymiddle, 1, &ipv6Route, ""},
|
||||
{"v6 gatewaylast", v6gatewaylast, 1, &ipv6Route, ""},
|
||||
{"v6 no routes", v6nothing, 0, nil, ""},
|
||||
{"v6 badDestination", v6badDestination, 0, nil, "invalid IPv6"},
|
||||
{"v6 badGateway", v6badGateway, 0, nil, "invalid IPv6"},
|
||||
{"v6 route_Invalidhex", v6route_Invalidhex, 0, nil, "odd length hex string"},
|
||||
{"v6 no default routes", v6noDefaultRoutes, 0, nil, ""},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
r := strings.NewReader(tc.route)
|
||||
routes, err := getIPv6DefaultRoutes(r)
|
||||
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 {
|
||||
if !tc.expected.Gateway.Equal(routes[0].Gateway) {
|
||||
t.Errorf("case[%s]: expected %v, got %v .err : %v", tc.tcase, tc.expected, routes, err)
|
||||
}
|
||||
if !routes[0].Destination.Equal(net.IPv6zero) {
|
||||
t.Errorf("case[%s}: destination is not for default route (not zero)", tc.tcase)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,19 +217,23 @@ func TestParseIP(t *testing.T) {
|
||||
testCases := []struct {
|
||||
tcase string
|
||||
ip string
|
||||
family AddressFamily
|
||||
success bool
|
||||
expected net.IP
|
||||
}{
|
||||
{"empty", "", false, nil},
|
||||
{"too short", "AA", false, nil},
|
||||
{"too long", "0011223344", false, nil},
|
||||
{"invalid", "invalid!", false, nil},
|
||||
{"zero", "00000000", true, net.IP{0, 0, 0, 0}},
|
||||
{"ffff", "FFFFFFFF", true, net.IP{0xff, 0xff, 0xff, 0xff}},
|
||||
{"valid", "12345678", true, net.IP{120, 86, 52, 18}},
|
||||
{"empty", "", familyIPv4, false, nil},
|
||||
{"too short", "AA", familyIPv4, false, nil},
|
||||
{"too long", "0011223344", familyIPv4, false, nil},
|
||||
{"invalid", "invalid!", familyIPv4, false, nil},
|
||||
{"zero", "00000000", familyIPv4, true, net.IP{0, 0, 0, 0}},
|
||||
{"ffff", "FFFFFFFF", familyIPv4, true, net.IP{0xff, 0xff, 0xff, 0xff}},
|
||||
{"valid v4", "12345678", familyIPv4, true, net.IP{120, 86, 52, 18}},
|
||||
{"valid v6", "fe800000000000000000000000000000", familyIPv6, true, net.IP{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
|
||||
{"v6 too short", "fe80000000000000021fcafffea0ec0", familyIPv6, false, nil},
|
||||
{"v6 too long", "fe80000000000000021fcafffea0ec002", familyIPv6, false, nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
ip, err := parseHexToIPv4(tc.ip)
|
||||
ip, err := parseIP(tc.ip, tc.family)
|
||||
if !ip.Equal(tc.expected) {
|
||||
t.Errorf("case[%v]: expected %q, got %q . err : %v", tc.tcase, tc.expected, ip, err)
|
||||
}
|
||||
@ -244,6 +324,23 @@ func (_ validNetworkInterface) Interfaces() ([]net.Interface, error) {
|
||||
return []net.Interface{upIntf}, nil
|
||||
}
|
||||
|
||||
// Both IPv4 and IPv6 addresses (expecting IPv4 to be used)
|
||||
type v4v6NetworkInterface struct {
|
||||
}
|
||||
|
||||
func (_ v4v6NetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
|
||||
return &upIntf, nil
|
||||
}
|
||||
func (_ v4v6NetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
|
||||
var ifat []net.Addr
|
||||
ifat = []net.Addr{
|
||||
addrStruct{val: "2001::10/64"}, addrStruct{val: "10.254.71.145/17"}}
|
||||
return ifat, nil
|
||||
}
|
||||
func (_ v4v6NetworkInterface) Interfaces() ([]net.Interface, error) {
|
||||
return []net.Interface{upIntf}, nil
|
||||
}
|
||||
|
||||
// Interface with only IPv6 address
|
||||
type ipv6NetworkInterface struct {
|
||||
}
|
||||
@ -436,26 +533,25 @@ func TestGetIPFromInterface(t *testing.T) {
|
||||
func TestChooseHostInterfaceFromRoute(t *testing.T) {
|
||||
testCases := []struct {
|
||||
tcase string
|
||||
inFile io.Reader
|
||||
routes []Route
|
||||
nw networkInterfacer
|
||||
expected net.IP
|
||||
}{
|
||||
{"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},
|
||||
{"fail get addrs", strings.NewReader(gatewaymiddle), networkInterfaceFailGetAddrs{}, nil},
|
||||
{"ipv4", routeV4, validNetworkInterface{}, net.ParseIP("10.254.71.145")},
|
||||
{"ipv6", routeV6, ipv6NetworkInterface{}, net.ParseIP("2001::200")},
|
||||
{"prefer ipv4", bothRoutes, v4v6NetworkInterface{}, net.ParseIP("10.254.71.145")},
|
||||
{"all LLA", routeV4, networkInterfaceWithOnlyLinkLocals{}, nil},
|
||||
{"no routes", noRoutes, validNetworkInterface{}, nil},
|
||||
{"fail get IP", routeV4, networkInterfaceFailGetAddrs{}, nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
ip, err := chooseHostInterfaceFromRoute(tc.inFile, tc.nw)
|
||||
ip, err := chooseHostInterfaceFromRoute(tc.routes, tc.nw)
|
||||
if !ip.Equal(tc.expected) {
|
||||
t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberOf(t *testing.T) {
|
||||
testCases := []struct {
|
||||
tcase string
|
||||
@ -505,3 +601,125 @@ func TestGetIPFromHostInterfaces(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeRouteFile(content string, t *testing.T) (*os.File, error) {
|
||||
routeFile, err := ioutil.TempFile("", "route")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := routeFile.Write([]byte(content)); err != nil {
|
||||
return routeFile, err
|
||||
}
|
||||
err = routeFile.Close()
|
||||
return routeFile, err
|
||||
}
|
||||
|
||||
func TestFailGettingIPv4Routes(t *testing.T) {
|
||||
defer func() { v4File.name = ipv4RouteFile }()
|
||||
|
||||
// Try failure to open file (should not occur, as caller ensures we have IPv4 route file, but being thorough)
|
||||
v4File.name = "no-such-file"
|
||||
errStrFrag := "no such file"
|
||||
_, err := v4File.extract()
|
||||
if err == nil {
|
||||
fmt.Errorf("Expected error trying to read non-existent v4 route file")
|
||||
}
|
||||
if !strings.Contains(err.Error(), errStrFrag) {
|
||||
t.Errorf("Unable to find %q in error string %q", errStrFrag, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailGettingIPv6Routes(t *testing.T) {
|
||||
defer func() { v6File.name = ipv6RouteFile }()
|
||||
|
||||
// Try failure to open file (this would be ignored by caller)
|
||||
v6File.name = "no-such-file"
|
||||
errStrFrag := "no such file"
|
||||
_, err := v6File.extract()
|
||||
if err == nil {
|
||||
fmt.Errorf("Expected error trying to read non-existent v6 route file")
|
||||
}
|
||||
if !strings.Contains(err.Error(), errStrFrag) {
|
||||
t.Errorf("Unable to find %q in error string %q", errStrFrag, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAllDefaultRoutesFailNoV4RouteFile(t *testing.T) {
|
||||
defer func() { v4File.name = ipv4RouteFile }()
|
||||
|
||||
// Should not occur, as caller ensures we have IPv4 route file, but being thorough
|
||||
v4File.name = "no-such-file"
|
||||
errStrFrag := "no such file"
|
||||
_, err := getAllDefaultRoutes()
|
||||
if err == nil {
|
||||
fmt.Errorf("Expected error trying to read non-existent v4 route file")
|
||||
}
|
||||
if !strings.Contains(err.Error(), errStrFrag) {
|
||||
t.Errorf("Unable to find %q in error string %q", errStrFrag, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAllDefaultRoutes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
tcase string
|
||||
v4Info string
|
||||
v6Info string
|
||||
count int
|
||||
expected []Route
|
||||
errStrFrag string
|
||||
}{
|
||||
{"no routes", noInternetConnection, v6noDefaultRoutes, 0, nil, "No default routes"},
|
||||
{"only v4 route", gatewayfirst, v6noDefaultRoutes, 1, routeV4, ""},
|
||||
{"only v6 route", noInternetConnection, v6gatewayfirst, 1, routeV6, ""},
|
||||
{"v4 and v6 routes", gatewayfirst, v6gatewayfirst, 2, bothRoutes, ""},
|
||||
}
|
||||
defer func() {
|
||||
v4File.name = ipv4RouteFile
|
||||
v6File.name = ipv6RouteFile
|
||||
}()
|
||||
|
||||
for _, tc := range testCases {
|
||||
routeFile, err := makeRouteFile(tc.v4Info, t)
|
||||
if routeFile != nil {
|
||||
defer os.Remove(routeFile.Name())
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("case[%s]: test setup failure for IPv4 route file: %v", tc.tcase, err)
|
||||
}
|
||||
v4File.name = routeFile.Name()
|
||||
v6routeFile, err := makeRouteFile(tc.v6Info, t)
|
||||
if v6routeFile != nil {
|
||||
defer os.Remove(v6routeFile.Name())
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("case[%s]: test setup failure for IPv6 route file: %v", tc.tcase, err)
|
||||
}
|
||||
v6File.name = v6routeFile.Name()
|
||||
|
||||
routes, err := getAllDefaultRoutes()
|
||||
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)
|
||||
}
|
||||
for i, expected := range tc.expected {
|
||||
if !expected.Gateway.Equal(routes[i].Gateway) {
|
||||
t.Errorf("case[%s]: at %d expected %v, got %v .err : %v", tc.tcase, i, tc.expected, routes, err)
|
||||
}
|
||||
zeroIP := net.IPv4zero
|
||||
if expected.Family == familyIPv6 {
|
||||
zeroIP = net.IPv6zero
|
||||
}
|
||||
if !routes[i].Destination.Equal(zeroIP) {
|
||||
t.Errorf("case[%s}: at %d destination is not for default route (not %v)", tc.tcase, i, zeroIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user