diff --git a/common.go b/common.go new file mode 100644 index 0000000..fda18f5 --- /dev/null +++ b/common.go @@ -0,0 +1,150 @@ +// Copyright 2012 Google, Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. + +// Originally found in +// https://github.com/google/gopacket/blob/master/routing/routing.go +// * Route selection modified to choose most selective route +// to break ties when route priority is insufficient. +package netroute + +import ( + "bytes" + "errors" + "fmt" + "net" + "strings" +) + +// Pulled from http://man7.org/linux/man-pages/man7/rtnetlink.7.html +// See the section on RTM_NEWROUTE, specifically 'struct rtmsg'. +type routeInfoInMemory struct { + Family byte + DstLen byte + SrcLen byte + TOS byte + + Table byte + Protocol byte + Scope byte + Type byte + + Flags uint32 +} + +// rtInfo contains information on a single route. +type rtInfo struct { + Src, Dst *net.IPNet + Gateway, PrefSrc net.IP + // We currently ignore the InputIface. + InputIface, OutputIface uint32 + Priority uint32 +} + +// routeSlice implements sort.Interface to sort routes by Priority. +type routeSlice []*rtInfo + +func (r routeSlice) Len() int { + return len(r) +} +func (r routeSlice) Less(i, j int) bool { + return r[i].Priority < r[j].Priority +} +func (r routeSlice) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} + +type router struct { + ifaces []net.Interface + addrs []ipAddrs + v4, v6 routeSlice +} + +func (r *router) String() string { + strs := []string{"ROUTER", "--- V4 ---"} + for _, route := range r.v4 { + strs = append(strs, fmt.Sprintf("%+v", *route)) + } + strs = append(strs, "--- V6 ---") + for _, route := range r.v6 { + strs = append(strs, fmt.Sprintf("%+v", *route)) + } + return strings.Join(strs, "\n") +} + +type ipAddrs struct { + v4, v6 net.IP +} + +func (r *router) Route(dst net.IP) (iface *net.Interface, gateway, preferredSrc net.IP, err error) { + return r.RouteWithSrc(nil, nil, dst) +} + +func (r *router) RouteWithSrc(input net.HardwareAddr, src, dst net.IP) (iface *net.Interface, gateway, preferredSrc net.IP, err error) { + var ifaceIndex int + switch { + case dst.To4() != nil: + ifaceIndex, gateway, preferredSrc, err = r.route(r.v4, input, src, dst) + case dst.To16() != nil: + ifaceIndex, gateway, preferredSrc, err = r.route(r.v6, input, src, dst) + default: + err = errors.New("IP is not valid as IPv4 or IPv6") + return + } + if err != nil { + return + } + + // Interfaces are 1-indexed, but we store them in a 0-indexed array. + ifaceIndex-- + + iface = &r.ifaces[ifaceIndex] + if preferredSrc == nil { + switch { + case dst.To4() != nil: + preferredSrc = r.addrs[ifaceIndex].v4 + case dst.To16() != nil: + preferredSrc = r.addrs[ifaceIndex].v6 + } + } + return +} + +func (r *router) route(routes routeSlice, input net.HardwareAddr, src, dst net.IP) (iface int, gateway, preferredSrc net.IP, err error) { + var inputIndex uint32 + if input != nil { + for i, iface := range r.ifaces { + if bytes.Equal(input, iface.HardwareAddr) { + // Convert from zero- to one-indexed. + inputIndex = uint32(i + 1) + break + } + } + } + var mostSpecificRt *rtInfo + for _, rt := range routes { + if rt.InputIface != 0 && rt.InputIface != inputIndex { + continue + } + if src != nil && rt.Src != nil && !rt.Src.Contains(src) { + continue + } + if rt.Dst != nil && !rt.Dst.Contains(dst) { + continue + } + if mostSpecificRt != nil { + candSpec, _ := rt.Dst.Mask.Size() + if curSpec, _ := mostSpecificRt.Dst.Mask.Size(); candSpec < curSpec { + continue + } + } + mostSpecificRt = rt + } + if mostSpecificRt != nil { + return int(mostSpecificRt.OutputIface), mostSpecificRt.Gateway, mostSpecificRt.PrefSrc, nil + } + err = fmt.Errorf("no route found for %v", dst) + return +} diff --git a/netroute_darwin.go b/netroute_darwin.go index 3cb17f8..c438db4 100644 --- a/netroute_darwin.go +++ b/netroute_darwin.go @@ -6,158 +6,22 @@ // +build darwin dragonfly freebsd netbsd openbsd -// This is a copy of +// This is a BSD import for the routing structure initially found in // https://github.com/google/gopacket/blob/master/routing/routing.go -// but with RIB parsing following the route format described in +//RIB parsing follows the BSD route format described in // https://github.com/freebsd/freebsd/blob/master/sys/net/route.h package netroute import ( - "bytes" - "errors" "fmt" "net" "sort" - "strings" "syscall" "github.com/google/gopacket/routing" "golang.org/x/net/route" ) -// Pulled from http://man7.org/linux/man-pages/man7/rtnetlink.7.html -// See the section on RTM_NEWROUTE, specifically 'struct rtmsg'. -type routeInfoInMemory struct { - Family byte - DstLen byte - SrcLen byte - TOS byte - - Table byte - Protocol byte - Scope byte - Type byte - - Flags uint32 -} - -// rtInfo contains information on a single route. -type rtInfo struct { - Src, Dst *net.IPNet - Gateway, PrefSrc net.IP - // We currently ignore the InputIface. - InputIface, OutputIface uint32 - Priority uint32 -} - -// routeSlice implements sort.Interface to sort routes by Priority. -type routeSlice []*rtInfo - -func (r routeSlice) Len() int { - return len(r) -} -func (r routeSlice) Less(i, j int) bool { - return r[i].Priority < r[j].Priority -} -func (r routeSlice) Swap(i, j int) { - r[i], r[j] = r[j], r[i] -} - -type router struct { - ifaces []net.Interface - addrs []ipAddrs - v4, v6 routeSlice -} - -func (r *router) String() string { - strs := []string{"ROUTER", "--- V4 ---"} - for _, route := range r.v4 { - strs = append(strs, fmt.Sprintf("%+v", *route)) - } - strs = append(strs, "--- V6 ---") - for _, route := range r.v6 { - strs = append(strs, fmt.Sprintf("%+v", *route)) - } - return strings.Join(strs, "\n") -} - -type ipAddrs struct { - v4, v6 net.IP -} - -func (r *router) Route(dst net.IP) (iface *net.Interface, gateway, preferredSrc net.IP, err error) { - return r.RouteWithSrc(nil, nil, dst) -} - -func (r *router) RouteWithSrc(input net.HardwareAddr, src, dst net.IP) (iface *net.Interface, gateway, preferredSrc net.IP, err error) { - var ifaceIndex int - switch { - case dst.To4() != nil: - ifaceIndex, gateway, preferredSrc, err = r.route(r.v4, input, src, dst) - case dst.To16() != nil: - ifaceIndex, gateway, preferredSrc, err = r.route(r.v6, input, src, dst) - default: - err = errors.New("IP is not valid as IPv4 or IPv6") - return - } - if err != nil { - return - } - - // Interfaces are 1-indexed, but we store them in a 0-indexed array. - ifaceIndex-- - - iface = &r.ifaces[ifaceIndex] - if preferredSrc == nil { - switch { - case dst.To4() != nil: - preferredSrc = r.addrs[ifaceIndex].v4 - case dst.To16() != nil: - preferredSrc = r.addrs[ifaceIndex].v6 - } - } - return -} - -func (r *router) route(routes routeSlice, input net.HardwareAddr, src, dst net.IP) (iface int, gateway, preferredSrc net.IP, err error) { - var inputIndex uint32 - if input != nil { - for i, iface := range r.ifaces { - if bytes.Equal(input, iface.HardwareAddr) { - // Convert from zero- to one-indexed. - inputIndex = uint32(i + 1) - break - } - } - } - var mostSpecificRt *rtInfo - for _, rt := range routes { - if rt.InputIface != 0 && rt.InputIface != inputIndex { - continue - } - if src != nil && rt.Src != nil && !rt.Src.Contains(src) { - continue - } - if rt.Dst != nil && !rt.Dst.Contains(dst) { - continue - } - if mostSpecificRt != nil { - candSpec, _ := rt.Dst.Mask.Size() - if curSpec, _ := mostSpecificRt.Dst.Mask.Size(); candSpec < curSpec { - continue - } - } - mostSpecificRt = rt - } - if mostSpecificRt != nil { - return int(mostSpecificRt.OutputIface), mostSpecificRt.Gateway, mostSpecificRt.PrefSrc, nil - } - err = fmt.Errorf("no route found for %v", dst) - return -} - -// Begin modifications - func toIPAddr(a route.Addr) (net.IP, error) { switch t := a.(type) { case *route.Inet4Addr: diff --git a/netroute_linux.go b/netroute_linux.go index 203efb8..0547dd8 100644 --- a/netroute_linux.go +++ b/netroute_linux.go @@ -1,33 +1,113 @@ +// Copyright 2012 Google, Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. + // +build linux +// Generate a local routing table structure following the code at +// https://github.com/google/gopacket/blob/master/routing/routing.go + package netroute import ( + "fmt" "net" + "sort" + "syscall" + "unsafe" "github.com/google/gopacket/routing" ) -type router struct { - routing.Router -} - -func (r *router) Route(dst net.IP) (iface *net.Interface, gateway, preferredSrc net.IP, err error) { - return r.RouteWithSrc(nil, nil, dst) -} - -func (r *router) RouteWithSrc(input net.HardwareAddr, src, dst net.IP) (iface *net.Interface, gateway, preferredSrc net.IP, err error) { - iface, gateway, preferredSrc, err = r.Router.RouteWithSrc(input, src, dst) - if dst.Equal(preferredSrc) { - gateway = nil - } - return iface, gateway, preferredSrc, err -} - func New() (routing.Router, error) { - r, err := routing.New() + rtr := &router{} + tab, err := syscall.NetlinkRIB(syscall.RTM_GETROUTE, syscall.AF_UNSPEC) if err != nil { return nil, err } - return &router{r}, nil + msgs, err := syscall.ParseNetlinkMessage(tab) + if err != nil { + return nil, err + } +loop: + for _, m := range msgs { + switch m.Header.Type { + case syscall.NLMSG_DONE: + break loop + case syscall.RTM_NEWROUTE: + rt := (*routeInfoInMemory)(unsafe.Pointer(&m.Data[0])) + routeInfo := rtInfo{} + attrs, err := syscall.ParseNetlinkRouteAttr(&m) + if err != nil { + return nil, err + } + switch rt.Family { + case syscall.AF_INET: + rtr.v4 = append(rtr.v4, &routeInfo) + case syscall.AF_INET6: + rtr.v6 = append(rtr.v6, &routeInfo) + default: + continue loop + } + for _, attr := range attrs { + switch attr.Attr.Type { + case syscall.RTA_DST: + routeInfo.Dst = &net.IPNet{ + IP: net.IP(attr.Value), + Mask: net.CIDRMask(int(rt.DstLen), len(attr.Value)*8), + } + case syscall.RTA_SRC: + routeInfo.Src = &net.IPNet{ + IP: net.IP(attr.Value), + Mask: net.CIDRMask(int(rt.SrcLen), len(attr.Value)*8), + } + case syscall.RTA_GATEWAY: + routeInfo.Gateway = net.IP(attr.Value) + case syscall.RTA_PREFSRC: + routeInfo.PrefSrc = net.IP(attr.Value) + case syscall.RTA_IIF: + routeInfo.InputIface = *(*uint32)(unsafe.Pointer(&attr.Value[0])) + case syscall.RTA_OIF: + routeInfo.OutputIface = *(*uint32)(unsafe.Pointer(&attr.Value[0])) + case syscall.RTA_PRIORITY: + routeInfo.Priority = *(*uint32)(unsafe.Pointer(&attr.Value[0])) + } + } + } + } + sort.Sort(rtr.v4) + sort.Sort(rtr.v6) + ifaces, err := net.Interfaces() + if err != nil { + return nil, err + } + for i, iface := range ifaces { + if i != iface.Index-1 { + return nil, fmt.Errorf("out of order iface %d = %v", i, iface) + } + rtr.ifaces = append(rtr.ifaces, iface) + var addrs ipAddrs + ifaceAddrs, err := iface.Addrs() + if err != nil { + return nil, err + } + for _, addr := range ifaceAddrs { + if inet, ok := addr.(*net.IPNet); ok { + // Go has a nasty habit of giving you IPv4s as ::ffff:1.2.3.4 instead of 1.2.3.4. + // We want to use mapped v4 addresses as v4 preferred addresses, never as v6 + // preferred addresses. + if v4 := inet.IP.To4(); v4 != nil { + if addrs.v4 == nil { + addrs.v4 = v4 + } + } else if addrs.v6 == nil { + addrs.v6 = inet.IP + } + } + } + rtr.addrs = append(rtr.addrs, addrs) + } + return rtr, nil } diff --git a/netroute_test.go b/netroute_test.go index a9144b7..59eced0 100644 --- a/netroute_test.go +++ b/netroute_test.go @@ -1,7 +1,6 @@ package netroute import ( - "fmt" "net" "strings" "testing" @@ -31,20 +30,17 @@ func TestRoute(t *testing.T) { if strings.HasPrefix(addr.Network(), "ip") { _, ipn, _ := net.ParseCIDR(addr.String()) if ipn.IP.To4() == nil && !ipn.IP.IsInterfaceLocalMulticast() && !ipn.IP.IsLinkLocalUnicast() && !ipn.IP.IsLinkLocalMulticast() { - fmt.Printf("See IPv6 Interface: %v\n", addr) hasV6 = true break } } } - iface, gw, src, err := r.Route(localAddr) + _, gw, src, err := r.Route(localAddr) if err != nil { t.Fatal(err) } if gw != nil || !src.Equal(localAddr) { - fmt.Printf("iface for localhost is %v\n", iface) - fmt.Printf("when routing to %v, saw %v\n", localAddr, src) t.Fatalf("Did not expect gateway to localhost: %v", gw) }