Skip to content

Commit

Permalink
IPv6 support for ChooseHostInterface (part 3 of 3)
Browse files Browse the repository at this point in the history
This is the final commit, which is based on top of PR 46044 and 46138, and provides
support for Ipv6 for the ChooseHostInterface() and ChooseBindAddress() functions.
The commit includes the following...

- Parses default routes from /proc/net/ipv6_route file.
- Collected IPv6 routes are added to the Ipv4 routes collected.
- ChooseHostInteface semantics remain the same:
    * If no Ipv4 route file, the system interfaces will be checked for a global IP.
    * Otherwise, default routes will be used to find a global IP.
    * If there is a failure getting IP from default routes, an error is reported.
    * Although IPv6 routes are also used, IPv4 routes have precedence.
- Any failure getting IPv6 routes is ignored, and will proceed with IPv4 routes.
- Scans all IPv4 routes looking for interfaces with global IP, before looking at
  IPv6 routes.
- Increased code coverage to 90.2%, covering all new and modified code (except for
  the top level API functions, which would need to be checked with integration/e2e
  tests.

This would complete IPv6 support in this area of the code.

Updated to rebased for changes from 46138/46044, and based on review comments.

Kubernetes-commit: 3d8f96f881eeaa814d2b10a99c619d08ff60e7df
  • Loading branch information
pmichali authored and k8s-publishing-bot committed Jun 2, 2017
1 parent 101ce26 commit 45c4951
Show file tree
Hide file tree
Showing 2 changed files with 364 additions and 73 deletions.
163 changes: 118 additions & 45 deletions pkg/util/net/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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
}
Expand All @@ -75,26 +100,70 @@ 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")
}
bytes, err := hex.DecodeString(str)
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
}
// Must be IPv6
if len(bytes) != net.IPv6len {
return nil, fmt.Errorf("invalid IPv6 address in route")
}
return net.IP([]byte{bytes[3], bytes[2], bytes[1], bytes[0]}), nil
return net.IP(bytes), nil
}

func isInterfaceUp(intf *net.Interface) bool {
Expand All @@ -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 {
Expand All @@ -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))
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 45c4951

Please sign in to comment.