netconfig: added GetIPRoutes(defaultOnly bool) and IsDefaultIPRoute() that works for
BSD, Mac, and Linux.

Change-Id: Ic46435045948ab9db756dec82ae3bb9c5a37c7d0
diff --git a/lib/netconfig/ipaux_bsd.go b/lib/netconfig/ipaux_bsd.go
index 2b729f8..4a8c5f7 100644
--- a/lib/netconfig/ipaux_bsd.go
+++ b/lib/netconfig/ipaux_bsd.go
@@ -9,6 +9,8 @@
 // we need to do is look for message types.
 
 import (
+	"errors"
+	"net"
 	"sync"
 	"syscall"
 	"time"
@@ -118,3 +120,71 @@
 	}
 	w.Unlock()
 }
+
+func toIP(sa syscall.Sockaddr) (net.IP, error) {
+	switch v := sa.(type) {
+	case *syscall.SockaddrInet4:
+		return net.IPv4(v.Addr[0], v.Addr[1], v.Addr[2], v.Addr[3]), nil
+	case *syscall.SockaddrInet6:
+		return net.IP(v.Addr[:]), nil
+	}
+	return net.IPv6zero, errors.New("unknown sockaddr ip")
+}
+
+func toIPNet(sa syscall.Sockaddr, msa syscall.Sockaddr) (net.IPNet, error) {
+	var x net.IPNet
+	var err error
+	x.IP, err = toIP(sa)
+	if err != nil {
+		return x, err
+	}
+	switch v := msa.(type) {
+	case *syscall.SockaddrInet4:
+		x.Mask = net.IPv4Mask(v.Addr[0], v.Addr[1], v.Addr[2], v.Addr[3])
+		return x, nil
+	case *syscall.SockaddrInet6:
+		x.Mask = net.IPMask(v.Addr[:])
+		return x, nil
+	}
+	return x, errors.New("unknown sockaddr ipnet")
+}
+
+// IPRoutes returns all kernel known routes.  If defaultOnly is set, only default routes
+// are returned.
+func GetIPRoutes(defaultOnly bool) []*IPRoute {
+	var x []*IPRoute
+	rib, err := syscall.RouteRIB(syscall.NET_RT_DUMP, 0)
+	if err != nil {
+		vlog.Infof("Couldn't read: %s", err)
+		return x
+	}
+	msgs, err := syscall.ParseRoutingMessage(rib)
+	if err != nil {
+		vlog.Infof("Couldn't parse: %s", err)
+		return x
+	}
+	for _, m := range msgs {
+		switch v := m.(type) {
+		case *syscall.RouteMessage:
+			addrs, err := syscall.ParseRoutingSockaddr(m)
+			if err != nil {
+				return x
+			}
+			if addrs[0] == nil || addrs[1] == nil || addrs[2] == nil {
+				continue
+			}
+			r := new(IPRoute)
+			if r.Gateway, err = toIP(addrs[1]); err != nil {
+				continue
+			}
+			if r.Net, err = toIPNet(addrs[0], addrs[2]); err != nil {
+				continue
+			}
+			r.IfcIndex = int(v.Header.Index)
+			if !defaultOnly || IsDefaultIPRoute(r) {
+				x = append(x, r)
+			}
+		}
+	}
+	return x
+}
diff --git a/lib/netconfig/ipaux_linux.go b/lib/netconfig/ipaux_linux.go
index c598da3..49945d1 100644
--- a/lib/netconfig/ipaux_linux.go
+++ b/lib/netconfig/ipaux_linux.go
@@ -369,3 +369,72 @@
 	}
 	w.Unlock()
 }
+func toIP(a []byte) (net.IP, error) {
+	switch len(a) {
+	case 4:
+		return net.IPv4(a[0], a[1], a[2], a[3]), nil
+	case 16:
+		return net.IP(a), nil
+	}
+	return net.IPv6unspecified, errors.New("unknown ip address len")
+}
+
+// IPRoutes returns all kernel known routes.  If defaultOnly is set, only default routes
+// are returned.
+func GetIPRoutes(defaultOnly bool) []*IPRoute {
+	var x []*IPRoute
+	rib, err := syscall.NetlinkRIB(syscall.RTM_GETROUTE, syscall.AF_UNSPEC)
+	if err != nil {
+		vlog.Infof("Couldn't read: %s", err)
+		return x
+	}
+	msgs, err := syscall.ParseNetlinkMessage(rib)
+	if err != nil {
+		vlog.Infof("Couldn't parse: %s", err)
+		return x
+	}
+L:
+	for _, m := range msgs {
+		if m.Header.Type != syscall.RTM_NEWROUTE {
+			continue
+		}
+		attrs, err := syscall.ParseNetlinkRouteAttr(&m)
+		if err != nil {
+			continue
+		}
+		r := new(IPRoute)
+		r.Net.IP = net.IPv4zero
+		for _, a := range attrs {
+			switch a.Attr.Type {
+			case syscall.RTA_DST:
+				if r.Net.IP, err = toIP(a.Value[:]); err != nil {
+					continue L
+				}
+			case syscall.RTA_GATEWAY:
+				if r.Gateway, err = toIP(a.Value[:]); err != nil {
+					continue L
+				}
+			case syscall.RTA_OIF:
+				r.IfcIndex = int(a.Value[0])
+			case syscall.RTA_PREFSRC:
+				if r.PreferredSource, err = toIP(a.Value[:]); err != nil {
+					continue L
+				}
+			}
+		}
+		b := m.Data[:syscall.SizeofRtMsg]
+		a := (*syscall.RtMsg)(unsafe.Pointer(&b[0]))
+		len := 128
+		if r.Net.IP.To4() != nil {
+			len = 32
+		}
+		if int(a.Dst_len) > len {
+			continue
+		}
+		r.Net.Mask = net.CIDRMask(int(a.Dst_len), len)
+		if !defaultOnly || IsDefaultIPRoute(r) {
+			x = append(x, r)
+		}
+	}
+	return x
+}
diff --git a/lib/netconfig/iproute.go b/lib/netconfig/iproute.go
new file mode 100644
index 0000000..fba673e
--- /dev/null
+++ b/lib/netconfig/iproute.go
@@ -0,0 +1,26 @@
+// package netconfig implements a network configuration watcher.
+// NOTE(p): This is also where we should put any code that changes
+//          network configuration.
+
+package netconfig
+
+import (
+	"net"
+)
+
+func isZeroSlice(a []byte) bool {
+	for _, i := range a {
+		if i != 0 {
+			return false
+		}
+	}
+	return true
+}
+
+// IsDefaultRoute returns true if r is a default route, i.e., that it matches any destination address.
+func IsDefaultIPRoute(r *IPRoute) bool {
+	if !r.Net.IP.Equal(net.IPv4zero) && !r.Net.IP.Equal(net.IPv6zero) {
+		return false
+	}
+	return isZeroSlice(r.Net.Mask[:])
+}
diff --git a/lib/netconfig/model.go b/lib/netconfig/model.go
index c65afb6..27898cc 100644
--- a/lib/netconfig/model.go
+++ b/lib/netconfig/model.go
@@ -4,6 +4,10 @@
 
 package netconfig
 
+import (
+	"net"
+)
+
 // NetConfigWatcher sends on channel whenever an interface or interface address
 // is added or deleted.
 type NetConfigWatcher interface {
@@ -15,3 +19,12 @@
 	// network configuration in such cases.
 	Channel() chan struct{}
 }
+
+// IPRoute represents a route in the kernel's routing table.
+// Any route with a nil Gateway is a directly connected network.
+type IPRoute struct {
+	Net             net.IPNet
+	Gateway         net.IP
+	PreferredSource net.IP
+	IfcIndex        int
+}