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:
Kubernetes Submit Queue 2017-07-21 17:00:18 -07:00 committed by GitHub
commit c90fda8260
2 changed files with 364 additions and 73 deletions

View File

@ -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 {

View File

@ -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)
}
}
}
}
}