Skip to content

Commit

Permalink
Merge pull request #46874 from pmichali/issue44848c
Browse files Browse the repository at this point in the history
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
```

Kubernetes-commit: c90fda82601e91c36a43975e28d064481a454a04
  • Loading branch information
k8s-publishing-bot committed Jul 22, 2017
2 parents 101ce26 + 45c4951 commit 7ed3e0a
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 7ed3e0a

Please sign in to comment.