mirror of
https://github.com/netbirdio/go-netroute.git
synced 2026-05-22 17:11:07 -07:00
Partial build and structure of Router abstraction
This commit is contained in:
@@ -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)
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// +build linux
|
||||
|
||||
package netroute
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/google/gopacket/routing"
|
||||
)
|
||||
|
||||
func New() (routing.Router, error) {
|
||||
return routing.New()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user