give up on not-reinventing the linux wheel

This commit is contained in:
Will Scott
2020-03-08 15:05:17 -07:00
parent fb652347a8
commit 1305487144
4 changed files with 251 additions and 161 deletions
+150
View File
@@ -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
}
+2 -138
View File
@@ -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:
+98 -18
View File
@@ -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
}
+1 -5
View File
@@ -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)
}