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
+}