Commit c3662820 authored by tavit ohanian's avatar tavit ohanian

Merge branch 'port-2021-07-01'

parents 51e6417a df1dae58
Pipeline #670 failed with stages
in 1 minute and 11 seconds
version: 2.1
orbs:
ci-go: ipfs/ci-go@0.2.8
workflows:
version: 2
test:
jobs:
- ci-go/build
- ci-go/lint
- ci-go/test
stages:
- build
- test
variables:
BUILD_DIR: "/tmp/$CI_CONCURRENT_PROJECT_ID"
before_script:
- mkdir -p $BUILD_DIR/src
- cd $BUILD_DIR/src
- if [ -d $CI_PROJECT_DIR ]
- then
- echo "soft link $CI_PROJECT_DIR exists"
- else
- echo "creating soft link $CI_PROJECT_DIR"
- ln -s $CI_PROJECT_DIR
- fi
- cd $CI_PROJECT_DIR
build:
stage: build
tags:
- testing
script:
- echo $CI_JOB_STAGE
- go build
test:
stage: test
tags:
- testing
script:
- echo $CI_JOB_STAGE
- go test -cover
coverage: '/coverage: \d+.\d+% of statements/'
Copyright (c) 2020 Will Scott. All rights reserved.
Copyright (c) 2012 Google, Inc. All rights reserved.
Copyright (c) 2009-2011 Andreas Krennmair. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Andreas Krennmair, Google, nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# go-netroute
Go Netroute
===
dms3 p2p go-netroute
\ No newline at end of file
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai)
[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://github.com/libp2p/libp2p)
[![Build Status](https://travis-ci.com/libp2p/go-netroute.svg?branch=master)](https://travis-ci.com/libp2p/go-netroute)
A cross-platform implementation of the [`gopacket/routing.Router`](https://godoc.org/github.com/google/gopacket/routing#Router) interface.
This library is derived from `gopacket` for linux, `x/net/route` for mac, and `iphlpapi.dll` for windows.
## Table of Contents
- [Install](#install)
- [Usage](#usage)
- [Documentation](#documentation)
- [Contribute](#contribute)
- [License](#license)
## Install
```
go get github.com/libp2p/go-netroute
```
## Usage
To be used for querying the local OS routing table.
```go
import (
netroute "github.com/libp2p/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)
}
```
## Documentation
See the [gopacket](https://github.com/google/gopacket/blob/master/routing/) interface for thoughts on design,
and [godoc](https://godoc.org/github.com/libp2p/go-netroute) for API documentation.
## Contribute
Contributions welcome. Please check out [the issues](https://github.com/libp2p/go-netroute/issues).
Check out our [contributing document](https://github.com/libp2p/community/blob/master/contributing.md) for more information on how we work, and about contributing in general. Please be aware that all interactions related to multiformats are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).
Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
## License
[BSD](LICENSE) © Will Scott, and the Gopacket authors (i.e. Google)
// 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 map[int]net.Interface
addrs map[int]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.
correspondingIface, ok := r.ifaces[ifaceIndex]
if !ok {
err = errors.New("Route refereced unknown interface")
}
iface = &correspondingIface
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 {
var candSpec, curSpec int
if rt.Dst != nil {
candSpec, _ = rt.Dst.Mask.Size()
}
if mostSpecificRt.Dst != nil {
curSpec, _ = mostSpecificRt.Dst.Mask.Size()
}
if 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
}
module gitlab.dms3.io/p2p/go-netroute
go 1.15
require (
github.com/google/gopacket v1.1.19
gitlab.dms3.io/p2p/go-sockaddr v0.0.1
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83
)
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
gitlab.dms3.io/p2p/go-sockaddr v0.0.1 h1:VupmB55WkCvJxOveddXzkKadW/jtb9JEeA0Q572AWL4=
gitlab.dms3.io/p2p/go-sockaddr v0.0.1/go.mod h1:g7iIq8MDEq2jtHLw5AlFNjEqAuer7C11HT0A77TlMik=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 h1:0PC75Fz/kyMGhL0e1QnypqK2kQMqKt9csD1GnMJR+Zk=
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83 h1:kHSDPqCtsHZOg0nVylfTo20DDhE9gG4Y0jn7hKQ0QAM=
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
// 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 BSD import for the routing structure initially found in
// https://github.com/google/gopacket/blob/master/routing/routing.go
//RIB parsing follows the BSD route format described in
// https://github.com/freebsd/freebsd/blob/master/sys/net/route.h
package netroute
import (
"fmt"
"net"
"sort"
"syscall"
"github.com/google/gopacket/routing"
"golang.org/x/net/route"
)
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: %v", 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{}
rtr.ifaces = make(map[int]net.Interface)
rtr.addrs = make(map[int]ipAddrs)
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))
}
}
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 _, iface := range ifaces {
rtr.ifaces[iface.Index] = 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[iface.Index] = addrs
}
return rtr, nil
}
// 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 (
"net"
"sort"
"syscall"
"unsafe"
"github.com/google/gopacket/routing"
)
func New() (routing.Router, error) {
rtr := &router{}
rtr.ifaces = make(map[int]net.Interface)
rtr.addrs = make(map[int]ipAddrs)
tab, err := syscall.NetlinkRIB(syscall.RTM_GETROUTE, syscall.AF_UNSPEC)
if err != nil {
return nil, err
}
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 _, iface := range ifaces {
rtr.ifaces[iface.Index] = 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[iface.Index] = addrs
}
return rtr, nil
}
// Generate a local routing table structure following the code at
// https://github.com/google/gopacket/blob/master/routing/routing.go
//
// Plan 9 networking is described here: http://9p.io/magic/man2html/3/ip
package netroute
import (
"bytes"
"fmt"
"io/ioutil"
"net"
"strconv"
"strings"
"github.com/google/gopacket/routing"
)
const netdir = "/net"
func New() (routing.Router, error) {
rtr := &router{}
rtr.ifaces = make(map[int]net.Interface)
rtr.addrs = make(map[int]ipAddrs)
ifaces, err := net.Interfaces()
if err != nil {
return nil, fmt.Errorf("could not get interfaces: %v", err)
}
for _, iface := range ifaces {
rtr.ifaces[iface.Index] = 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[iface.Index] = addrs
}
rtr.v4, rtr.v6, err = parseIPRoutes()
if err != nil {
return nil, err
}
return rtr, nil
}
func parseIPRoutes() (v4, v6 routeSlice, err error) {
buf, err := ioutil.ReadFile(netdir + "/iproute")
if err != nil {
return nil, nil, err
}
for {
i := bytes.IndexRune(buf, '\n')
if i <= 0 {
break
}
f := strings.Fields(string(buf[:i]))
buf = buf[i+1:]
if len(f) < 8 {
return nil, nil, fmt.Errorf("iproute entry contains %d fields", len(f))
}
flags, rt, err := parseRoute(f)
if err != nil {
return nil, nil, err
}
if rt.Dst != nil {
// If gateway for destination 127.0.0.1/32 is 127.0.0.1, set it to nil.
if m, n := rt.Dst.Mask.Size(); n > 0 && m == n && rt.Dst.IP.Equal(rt.Gateway) {
rt.Gateway = nil
}
}
if strings.ContainsRune(flags, '4') { // IPv4
v4 = append(v4, rt)
}
if strings.ContainsRune(flags, '6') { // IPv6
v6 = append(v6, rt)
}
}
return v4, v6, nil
}
func parseRoute(f []string) (flags string, rt *rtInfo, err error) {
rt = new(rtInfo)
isV4 := strings.ContainsRune(f[3], '4') // flags
rt.PrefSrc, rt.Src, err = parsePlan9CIDR(f[6], f[7], isV4)
if err != nil {
return "", nil, err
}
_, rt.Dst, err = parsePlan9CIDR(f[0], f[1], isV4)
if err != nil {
return "", nil, err
}
rt.Gateway = net.ParseIP(f[2])
n, err := strconv.ParseUint(f[5], 10, 32)
if err != nil {
return "", nil, err
}
rt.InputIface = 0
rt.OutputIface = uint32(n) + 1 // starts at 0, so net package adds 1
rt.Priority = 0
return f[3], rt, nil
}
func parsePlan9CIDR(addr, mask string, isV4 bool) (net.IP, *net.IPNet, error) {
if len(mask) == 0 || mask[0] != '/' {
return nil, nil, fmt.Errorf("invalid CIDR mask %v", mask)
}
n, err := strconv.ParseUint(mask[1:], 10, 32)
if err != nil {
return nil, nil, err
}
ip := net.ParseIP(addr)
iplen := net.IPv6len
if isV4 {
// Plan 9 uses IPv6 mask for IPv4 addresses
n -= 8 * (net.IPv6len - net.IPv4len)
iplen = net.IPv4len
}
if n == 0 && ip.IsUnspecified() {
return nil, nil, nil
}
m := net.CIDRMask(int(n), 8*iplen)
return ip, &net.IPNet{IP: ip.Mask(m), Mask: m}, nil
}
// A stub routing table conformant interface for js/wasm environments.
// +build js,wasm
package netroute
import (
"net"
"github.com/google/gopacket/routing"
)
func New() (routing.Router, error) {
rtr := &router{}
rtr.ifaces = make(map[int]net.Interface)
rtr.ifaces[0] = net.Interface{}
rtr.addrs = make(map[int]ipAddrs)
rtr.addrs[0] = ipAddrs{}
rtr.v4 = routeSlice{&rtInfo{}}
rtr.v6 = routeSlice{&rtInfo{}}
return rtr, nil
}
package netroute
import (
"net"
"strings"
"testing"
)
func TestRoute(t *testing.T) {
r, err := New()
if err != nil {
t.Fatal(err)
}
ifs, err := net.Interfaces()
if err != nil || len(ifs) == 0 {
t.Skip("Can't test routing without access to system interfaces")
}
var localAddr net.IP
var hasV6 bool
addrs, err := ifs[0].Addrs()
if err != nil {
t.Fatal(err)
}
for _, addr := range addrs {
if strings.HasPrefix(addr.Network(), "ip") {
localAddr, _, _ = net.ParseCIDR(addr.String())
break
}
}
for _, addr := range addrs {
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() {
hasV6 = true
break
}
}
}
_, gw, src, err := r.Route(localAddr)
if err != nil {
t.Fatal(err)
}
if gw != nil || !src.Equal(localAddr) {
t.Fatalf("Did not expect gateway for %v->%v: %v", src, localAddr, gw)
}
// Route to somewhere external should.
_, gw, _, err = r.Route(net.IPv4(8, 8, 8, 8))
if err != nil {
t.Fatal(err)
}
if gw == nil {
t.Fatalf("Did not expect direct link to 8.8.8.8. Are you Google?")
}
// Route to v4 and v6 should differ.
if !hasV6 {
return
}
_, v6gw, _, err := r.Route(net.ParseIP("2607:f8b0:400a:809::200e")) // at one point google.
if err != nil {
t.Fatal(err)
}
if v6gw.Equal(gw) {
t.Fatalf("did not expect a v4 gw for a v6 route.")
}
}
// +build windows
package netroute
// Implementation Warning: mapping of the correct interface ID and index is not
// hooked up.
// Reference:
// https://docs.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getbestroute2
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"unsafe"
"github.com/google/gopacket/routing"
sockaddrconv "gitlab.dms3.io/p2p/go-sockaddr"
sockaddrnet "gitlab.dms3.io/p2p/go-sockaddr/net"
"golang.org/x/sys/windows"
)
var (
modiphlpapi = windows.NewLazyDLL("iphlpapi.dll")
procGetBestRoute2 = modiphlpapi.NewProc("GetBestRoute2")
)
type NetLUID uint64
type AddressPrefix struct {
*windows.RawSockaddrAny
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.RawSockaddrAny
prefixLength byte
lifetime uint32
preferredLifetime uint32
metric uint32
protocol RouteProtocol
loopback byte
autoconfigured byte
publish byte
immortal byte
age uint32
origin byte
}
func callBestRoute(source, dest net.IP) (*mib_row2, net.IP, error) {
sourceAddr, _, _ := sockaddrconv.SockaddrToAny(sockaddrnet.IPAndZoneToSockaddr(source, ""))
destAddr, _, _ := sockaddrconv.SockaddrToAny(sockaddrnet.IPAndZoneToSockaddr(dest, ""))
bestRoute := make([]byte, 512)
bestSource := make([]byte, 116)
err := getBestRoute2(nil, 0, sourceAddr, destAddr, 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
}
// per https://docs.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipforward_row2
// If the route is to a local loopback address or an IP address on the local link, the next hop is unspecified (all zeros)
if isZero(route.nextHop) {
route.nextHop = nil
}
var bestSourceRaw windows.RawSockaddrAny
bestSourceRaw.Addr.Family = binary.LittleEndian.Uint16(bestSource[0:2])
copyInto(bestSourceRaw.Addr.Data[:], bestSource[2:16])
copyInto(bestSourceRaw.Pad[:], bestSource[16:])
addr, _ := bestSourceRaw.Sockaddr()
bestSrc, _ := sockaddrnet.SockaddrToIPAndZone(addr)
return route, bestSrc, nil
}
func copyInto(dst []int8, src []byte) {
for i, b := range src {
dst[i] = int8(b)
}
}
func isZero(addr *windows.RawSockaddrAny) bool {
for _, b := range addr.Addr.Data {
if b != 0 {
return false
}
}
for _, b := range addr.Pad {
if b != 0 {
return false
}
}
return true
}
func parseRoute(mib []byte) (*mib_row2, error) {
var route mib_row2
var err error
route.luid = NetLUID(binary.LittleEndian.Uint64(mib[0:]))
route.index = binary.LittleEndian.Uint32(mib[8:])
idx := 0
route.destinationPrefix, idx, err = readDestPrefix(mib, 12)
if err != nil {
return nil, err
}
route.nextHop, idx, err = readSockAddr(mib, idx)
if err != nil {
return nil, err
}
route.prefixLength = mib[idx]
idx += 1
route.lifetime = binary.LittleEndian.Uint32(mib[idx : idx+4])
idx += 4
route.preferredLifetime = binary.LittleEndian.Uint32(mib[idx : idx+4])
idx += 4
route.metric = binary.LittleEndian.Uint32(mib[idx : idx+4])
idx += 4
route.protocol = RouteProtocol(binary.LittleEndian.Uint32(mib[idx : idx+4]))
idx += 4
route.loopback = mib[idx]
idx += 1
route.autoconfigured = mib[idx]
idx += 1
route.publish = mib[idx]
idx += 1
route.immortal = mib[idx]
idx += 1
route.age = binary.LittleEndian.Uint32(mib[idx : idx+4])
idx += 4
route.origin = mib[idx]
return &route, err
}
func readDestPrefix(buffer []byte, idx int) (*AddressPrefix, int, error) {
sock, idx2, err := readSockAddr(buffer, idx)
if err != nil {
return nil, 0, err
}
pfixLen := buffer[idx2]
if idx2-idx > 32 {
return nil, idx, fmt.Errorf("Unexpectedly large internal sockaddr struct")
}
return &AddressPrefix{sock, pfixLen}, idx + 32, nil
}
func readSockAddr(buffer []byte, idx int) (*windows.RawSockaddrAny, int, error) {
var rsa windows.RawSockaddrAny
rsa.Addr.Family = binary.LittleEndian.Uint16(buffer[idx : idx+2])
if rsa.Addr.Family == windows.AF_INET || rsa.Addr.Family == windows.AF_UNSPEC {
copyInto(rsa.Addr.Data[:], buffer[idx+2:idx+16])
return &rsa, idx + 16, nil
} else if rsa.Addr.Family == windows.AF_INET6 {
copyInto(rsa.Addr.Data[:], buffer[idx+2:idx+16])
copyInto(rsa.Pad[:], buffer[idx+16:idx+28])
return &rsa, idx + 28, nil
} else {
return nil, 0, fmt.Errorf("Unknown windows addr family %d", rsa.Addr.Family)
}
}
func getBestRoute2(interfaceLuid *NetLUID, interfaceIndex uint32, sourceAddress, destinationAddress *windows.RawSockaddrAny, addressSortOptions uint32, bestRoute []byte, bestSourceAddress []byte) (errcode error) {
r0, _, _ := procGetBestRoute2.Call(
uintptr(unsafe.Pointer(interfaceLuid)),
uintptr(interfaceIndex),
uintptr(unsafe.Pointer(sourceAddress)),
uintptr(unsafe.Pointer(destinationAddress)),
uintptr(addressSortOptions),
uintptr(unsafe.Pointer(&bestRoute[0])),
uintptr(unsafe.Pointer(&bestSourceAddress[0])))
if r0 != 0 {
errcode = windows.Errno(r0)
}
return
}
func getIface(index uint32) *net.Interface {
var ifRow windows.MibIfRow
ifRow.Index = index
err := windows.GetIfEntry(&ifRow)
if err != nil {
return nil
}
ifaces, err := net.Interfaces()
if err != nil {
return nil
}
for _, iface := range ifaces {
if bytes.Equal(iface.HardwareAddr, ifRow.PhysAddr[:]) {
return &iface
}
}
return nil
}
type winRouter struct{}
func (r *winRouter) Route(dst net.IP) (iface *net.Interface, gateway, preferredSrc net.IP, err error) {
return r.RouteWithSrc(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, nil, err
}
iface = getIface(route.index)
if route.nextHop == nil || route.nextHop.Addr.Family == 0 /* AF_UNDEF */ {
return iface, nil, pref, nil
}
addr, err := route.nextHop.Sockaddr()
if err != nil {
return nil, nil, nil, err
}
nextHop, _ := sockaddrnet.SockaddrToIPAndZone(addr)
return iface, nextHop, pref, nil
}
func New() (routing.Router, error) {
rtr := &winRouter{}
return rtr, nil
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment