Partial build and structure of Router abstraction

This commit is contained in:
Will Scott
2020-03-05 16:44:46 -08:00
commit 1a8d05ad0f
7 changed files with 504 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
Go Netroute
===
A cross-platform implementation of the `gopacket/routing.Router` interface.
This library uses `gopacket` for linux, `x/net/route`
for mac, and `iphlpapi.dll` for windows.
Usage
---
```
import (
netroute "github.com/willscott/go-netroute"
)
func main() {
r, err := netroute.New()
if err != nil {
panic(err)
}
iface, gw, src, err := r.Route(net.IPv4(127, 0, 0, 1))
fmt.Printf("%v, %v, %v, %v\n", iface, gw, src, err)
}
```
+9
View File
@@ -0,0 +1,9 @@
module github.com/willscott/go-netroute
go 1.13
require (
github.com/google/gopacket v1.1.17
github.com/libp2p/go-sockaddr v0.0.1 // indirect
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
)
+12
View File
@@ -0,0 +1,12 @@
github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY=
github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM=
github.com/libp2p/go-sockaddr v0.0.1 h1:yM3fy0n5oommws7FmCeOh+IoyEFpmlO77+2eXiaWIko=
github.com/libp2p/go-sockaddr v0.0.1/go.mod h1:EBeYKMYs5LFgoaBSN2nA2jPQVmnA4gv7WkY64CBqYqQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+285
View File
@@ -0,0 +1,285 @@
// 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 darwin dragonfly freebsd netbsd openbsd
// This is a copy of
// https://github.com/google/gopacket/blob/master/routing/routing.go
// but with RIB parsing following the 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
}
}
}
for _, rt := range routes {
if rt.InputIface != 0 && rt.InputIface != inputIndex {
continue
}
if src != nil && rt.Src != nil && !rt.Src.Contains(src) {
fmt.Printf("ignoring %v b/c src %v\n", rt, src)
continue
}
if rt.Dst != nil && !rt.Dst.Contains(dst) {
fmt.Printf("ignoring %v b/c dst\n", rt)
continue
}
fmt.Printf("Using route: %v\n", rt)
return int(rt.OutputIface), rt.Gateway, rt.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:
ip := net.IPv4(t.IP[0], t.IP[1], t.IP[2], t.IP[3])
return ip, nil
case *route.Inet6Addr:
ip := make(net.IP, net.IPv6len)
copy(ip, t.IP[:])
return ip, nil
default:
return net.IP{}, fmt.Errorf("unknown family: %T", t)
}
}
// selected BSD Route flags.
const (
RTF_UP = 0x1
RTF_GATEWAY = 0x2
RTF_HOST = 0x4
RTF_REJECT = 0x8
RTF_DYNAMIC = 0x10
RTF_MODIFIED = 0x20
RTF_STATIC = 0x800
RTF_BLACKHOLE = 0x1000
RTF_LOCAL = 0x200000
RTF_BROADCAST = 0x400000
RTF_MULTICAST = 0x800000
)
func New() (routing.Router, error) {
rtr := &router{}
tab, err := route.FetchRIB(syscall.AF_UNSPEC, route.RIBTypeRoute, 0)
if err != nil {
return nil, err
}
msgs, err := route.ParseRIB(route.RIBTypeRoute, tab)
if err != nil {
return nil, err
}
var ipn *net.IPNet
for _, msg := range msgs {
m := msg.(*route.RouteMessage)
routeInfo := new(rtInfo)
if m.Version < 3 || m.Version > 5 {
return nil, fmt.Errorf("Unexpected RIB message version: %d", m.Version)
}
if m.Type != 4 /* RTM_GET */ {
return nil, fmt.Errorf("Unexpected RIB message type: %d", m.Type)
}
if m.Flags&RTF_UP == 0 ||
m.Flags&(RTF_REJECT|RTF_BLACKHOLE) != 0 {
continue
}
if m.Err != nil {
continue
}
dst, err := toIPAddr(m.Addrs[0])
if err == nil {
mask, _ := toIPAddr(m.Addrs[2])
if mask == nil {
mask = net.IP(net.CIDRMask(0, 8*len(dst)))
}
ipn = &net.IPNet{IP: dst, Mask: net.IPMask(mask)}
if m.Flags&RTF_HOST != 0 {
ipn.Mask = net.CIDRMask(8*len(ipn.IP), 8*len(ipn.IP))
}
routeInfo.Dst = ipn
} else {
return nil, fmt.Errorf("Unexpected RIB destination: %v", err)
}
if m.Flags&RTF_GATEWAY != 0 {
if gw, err := toIPAddr(m.Addrs[1]); err == nil {
routeInfo.Gateway = gw
}
}
if src, err := toIPAddr(m.Addrs[5]); err == nil {
ipn = &net.IPNet{IP: src, Mask: net.CIDRMask(8*len(src), 8*len(src))}
routeInfo.Src = ipn
routeInfo.PrefSrc = src
if m.Flags&0x2 != 0 /* RTF_GATEWAY */ {
routeInfo.Src.Mask = net.CIDRMask(0, 8*len(routeInfo.Src.IP))
}
} else {
return nil, fmt.Errorf("Unexpected RIB src: %v", err)
}
routeInfo.OutputIface = uint32(m.Index)
switch m.Addrs[0].(type) {
case *route.Inet4Addr:
rtr.v4 = append(rtr.v4, routeInfo)
case *route.Inet6Addr:
rtr.v6 = append(rtr.v6, routeInfo)
}
}
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
}
+13
View File
@@ -0,0 +1,13 @@
// +build linux
package netroute
import (
"net"
"github.com/google/gopacket/routing"
)
func New() (routing.Router, error) {
return routing.New()
}
+14
View File
@@ -0,0 +1,14 @@
package netroute
import (
"net"
"testing"
)
func TestRoute(t *testing.T) {
_, gw, src, err := Route(net.IPv4(127, 0, 0, 1))
if err != nil {
t.Fatal(err)
}
t.Logf("Default route is via %v from %v", gw, src)
}
+146
View File
@@ -0,0 +1,146 @@
// +build windows
package netroute
// Reference:
// https://docs.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getbestroute2
import (
"encoding/binary"
"fmt"
"net"
"syscall"
"unsafe"
"github.com/google/gopacket/routing"
sockaddrnet "github.com/libp2p/go-sockaddr/net"
"golang.org/x/sys/windows"
)
var (
modiphlpapi = syscall.NewLazyDLL("iphlpapi.dll")
procGetBestRoute2 = modiphlpapi.NewProc("GetBestRoute2")
)
type NetLUID uint64
type AddressPrefix struct {
windows.Sockaddr
PrefixLength byte
}
type RouteProtocol uint32 // MIB_IPFORWARD_PROTO
// https://docs.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipforward_row2
type mib_row2 struct {
luid NetLUID
index uint32
destinationPrefix AddressPrefix
nextHop windows.Sockaddr
prefixLength byte
lifetime uint32
preferredLIfetime uint32
metric uint32
protocol RouteProtocol
loopback byte
autoconfigured byte
publich byte
immortal byte
age uint32
origin byte
}
func callBestRoute(source, dest net.IP) (*mib_row2, net.IP, error) {
sourceAddr := sockaddrnet.IPAndZoneToSockaddr(source, "")
destAddr := sockaddrnet.IPAndZoneToSockaddr(dest, "")
bestRoute := make([]byte, 64)
var bestSource windows.RawSockaddrAny
err := getBestRoute2(nil, 0, source, dest, 0, bestRoute, bestSource)
if err != nil {
return nil, nil, err
}
// interpret best route and best source.
route, err := parseRoute(bestRoute)
if err != nil {
return nil, nil, err
}
bestSrc, _ := sockaddrnet.SockaddrToIPAndZone(bestSource.Sockaddr())
return &route, bestSrc, nil
}
func parseRoute(mib []byte) (*mib_row2, error) {
var route mib_row2
var err error
route.luid = binary.LittleEndian.Uint64(mib[0:])
route.index = binary.LittleEndian.Uint32(mib[8:])
pfix, idx, err := readDestPrefix(mib, 12)
if err != nil {
return nil, err
}
route.destinationPrefix = pfix
route.nextHop, idx, err = readSockAddr(mib, idx)
if err != nil {
return nil, err
}
return route, err
}
func readDestPrefix(buffer []byte, idx int) (*AddressPrefix, int, error) {
sock, idx, err := readSockAddr(buffer, idx)
if err != nil {
return nil, 0, err
}
pfixLen := buffer[idx]
return &AddressPrefix{sock, pfixLen}, idx + 1, nil
}
func readSockAddr(buffer []byte, idx int) (*windows.Sockaddr, int, error) {
family := binary.LittleEndian.Uint16(buffer[idx:])
if family == AF_INET {
//14 bytes?
} else if family == AF_INET6 {
//24 bytes?
} else {
return nil, 0, fmt.Errorf("Unknown windows addr family %d", family)
}
}
func getBestRoute2(interfaceLuid *NetLUID, interfaceIndex uint32, sourceAddress, destinationAddress []byte, addressSortOptions uint32, bestRoute []byte, bestSourceAddress []byte) (errcode error) {
r0, _, _ := syscall.Syscall9(procGetBestRoute2.Addr(), 7,
uintptr(unsafe.Pointer(interfaceLuid)),
uintptr(interfaceIndex),
uintptr(unsafe.Pointer(&sourceAddress[0])),
uintptr(unsafe.Pointer(&destinationAddress[0])),
uintptr(addressSortOptions),
uintptr(unsafe.Pointer(&bestRoute[0])),
uintptr(unsafe.Pointer(&bestSourceAddress[0])),
0, 0)
if r0 != 0 {
errcode = syscall.Errno(r0)
}
return
}
type winRouter struct{}
func (r *winRouter) Route(dst net.IP) (iface *net.Interface, gateway, preferredSrc net.IP, err error) {
return RouteWithSource(nil, nil, dst)
}
func (r *winRouter) RouteWithSrc(input net.HardwareAddr, src, dst net.IP) (iface *net.Interface, gateway, preferredSrc net.IP, err error) {
route, pref, err := callBestRoute(src, dst)
if err != nil {
return nil, nil, err
}
return route, pref, nil
}
func New() (routing.Router, error) {
rtr := &winRouter{}
return rtr, nil
}