net/interfaces: deduplicate route table parsing on Darwin and FreeBSD

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
This commit is contained in:
Anton Tolchanov 2022-10-14 09:29:34 +01:00 committed by Anton Tolchanov
parent 9d04ffc782
commit 9c2ad7086c
4 changed files with 37 additions and 133 deletions

View File

@ -2,11 +2,10 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// This might work on other BSDs, but only tested on FreeBSD. // Common code for FreeBSD and Darwin.
// Originally a fork of interfaces_darwin.go with slightly different flags.
//go:build freebsd //go:build darwin || freebsd
// +build freebsd // +build darwin freebsd
package interfaces package interfaces
@ -16,7 +15,6 @@
"log" "log"
"net" "net"
"net/netip" "net/netip"
"syscall"
"golang.org/x/net/route" "golang.org/x/net/route"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@ -37,11 +35,6 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
return d, nil return d, nil
} }
// fetchRoutingTable calls route.FetchRIB, fetching NET_RT_DUMP.
func fetchRoutingTable() (rib []byte, err error) {
return route.FetchRIB(syscall.AF_UNSPEC, unix.NET_RT_DUMP, 0)
}
func DefaultRouteInterfaceIndex() (int, error) { func DefaultRouteInterfaceIndex() (int, error) {
// $ netstat -nr // $ netstat -nr
// Routing tables // Routing tables
@ -61,7 +54,7 @@ func DefaultRouteInterfaceIndex() (int, error) {
if err != nil { if err != nil {
return 0, fmt.Errorf("route.FetchRIB: %w", err) return 0, fmt.Errorf("route.FetchRIB: %w", err)
} }
msgs, err := route.ParseRIB(unix.NET_RT_IFLIST, rib) msgs, err := parseRoutingTable(rib)
if err != nil { if err != nil {
return 0, fmt.Errorf("route.ParseRIB: %w", err) return 0, fmt.Errorf("route.ParseRIB: %w", err)
} }
@ -71,12 +64,10 @@ func DefaultRouteInterfaceIndex() (int, error) {
if !ok { if !ok {
continue continue
} }
const RTF_GATEWAY = 0x2 if rm.Flags&unix.RTF_GATEWAY == 0 {
const RTF_IFSCOPE = 0x1000000
if rm.Flags&RTF_GATEWAY == 0 {
continue continue
} }
if rm.Flags&RTF_IFSCOPE != 0 { if rm.Flags&unix.RTF_IFSCOPE != 0 {
continue continue
} }
indexSeen[rm.Index]++ indexSeen[rm.Index]++
@ -102,7 +93,7 @@ func likelyHomeRouterIPBSDFetchRIB() (ret netip.Addr, ok bool) {
log.Printf("routerIP/FetchRIB: %v", err) log.Printf("routerIP/FetchRIB: %v", err)
return ret, false return ret, false
} }
msgs, err := route.ParseRIB(unix.NET_RT_IFLIST, rib) msgs, err := parseRoutingTable(rib)
if err != nil { if err != nil {
log.Printf("routerIP/ParseRIB: %v", err) log.Printf("routerIP/ParseRIB: %v", err)
return ret, false return ret, false
@ -112,11 +103,10 @@ func likelyHomeRouterIPBSDFetchRIB() (ret netip.Addr, ok bool) {
if !ok { if !ok {
continue continue
} }
const RTF_IFSCOPE = 0x1000000
if rm.Flags&unix.RTF_GATEWAY == 0 { if rm.Flags&unix.RTF_GATEWAY == 0 {
continue continue
} }
if rm.Flags&RTF_IFSCOPE != 0 { if rm.Flags&unix.RTF_IFSCOPE != 0 {
continue continue
} }
if len(rm.Addrs) > unix.RTAX_GATEWAY { if len(rm.Addrs) > unix.RTAX_GATEWAY {

View File

@ -5,128 +5,16 @@
package interfaces package interfaces
import ( import (
"errors"
"fmt"
"log"
"net"
"net/netip"
"syscall" "syscall"
"golang.org/x/net/route" "golang.org/x/net/route"
"golang.org/x/sys/unix"
"tailscale.com/net/netaddr"
) )
func defaultRoute() (d DefaultRouteDetails, err error) {
idx, err := DefaultRouteInterfaceIndex()
if err != nil {
return d, err
}
iface, err := net.InterfaceByIndex(idx)
if err != nil {
return d, err
}
d.InterfaceName = iface.Name
d.InterfaceIndex = idx
return d, nil
}
// fetchRoutingTable calls route.FetchRIB, fetching NET_RT_DUMP2. // fetchRoutingTable calls route.FetchRIB, fetching NET_RT_DUMP2.
func fetchRoutingTable() (rib []byte, err error) { func fetchRoutingTable() (rib []byte, err error) {
return route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0) return route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0)
} }
func DefaultRouteInterfaceIndex() (int, error) { func parseRoutingTable(rib []byte) ([]route.Message, error) {
// $ netstat -nr return route.ParseRIB(syscall.NET_RT_IFLIST2, rib)
// Routing tables
// Internet:
// Destination Gateway Flags Netif Expire
// default 10.0.0.1 UGSc en0 <-- want this one
// default 10.0.0.1 UGScI en1
// From man netstat:
// U RTF_UP Route usable
// G RTF_GATEWAY Destination requires forwarding by intermediary
// S RTF_STATIC Manually added
// c RTF_PRCLONING Protocol-specified generate new routes on use
// I RTF_IFSCOPE Route is associated with an interface scope
rib, err := fetchRoutingTable()
if err != nil {
return 0, fmt.Errorf("route.FetchRIB: %w", err)
}
msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib)
if err != nil {
return 0, fmt.Errorf("route.ParseRIB: %w", err)
}
indexSeen := map[int]int{} // index => count
for _, m := range msgs {
rm, ok := m.(*route.RouteMessage)
if !ok {
continue
}
const RTF_GATEWAY = 0x2
const RTF_IFSCOPE = 0x1000000
if rm.Flags&RTF_GATEWAY == 0 {
continue
}
if rm.Flags&RTF_IFSCOPE != 0 {
continue
}
indexSeen[rm.Index]++
}
if len(indexSeen) == 0 {
return 0, errors.New("no gateway index found")
}
if len(indexSeen) == 1 {
for idx := range indexSeen {
return idx, nil
}
}
return 0, fmt.Errorf("ambiguous gateway interfaces found: %v", indexSeen)
}
func init() {
likelyHomeRouterIP = likelyHomeRouterIPDarwinFetchRIB
}
func likelyHomeRouterIPDarwinFetchRIB() (ret netip.Addr, ok bool) {
rib, err := fetchRoutingTable()
if err != nil {
log.Printf("routerIP/FetchRIB: %v", err)
return ret, false
}
msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib)
if err != nil {
log.Printf("routerIP/ParseRIB: %v", err)
return ret, false
}
for _, m := range msgs {
rm, ok := m.(*route.RouteMessage)
if !ok {
continue
}
const RTF_GATEWAY = 0x2
const RTF_IFSCOPE = 0x1000000
if rm.Flags&RTF_GATEWAY == 0 {
continue
}
if rm.Flags&RTF_IFSCOPE != 0 {
continue
}
if len(rm.Addrs) > unix.RTAX_GATEWAY {
dst4, ok := rm.Addrs[unix.RTAX_DST].(*route.Inet4Addr)
if !ok || dst4.IP != ([4]byte{0, 0, 0, 0}) {
// Expect 0.0.0.0 as DST field.
continue
}
gw, ok := rm.Addrs[unix.RTAX_GATEWAY].(*route.Inet4Addr)
if !ok {
continue
}
return netaddr.IPv4(gw.IP[0], gw.IP[1], gw.IP[2], gw.IP[3]), true
}
}
return ret, false
} }

View File

@ -16,7 +16,7 @@
) )
func TestLikelyHomeRouterIPSyscallExec(t *testing.T) { func TestLikelyHomeRouterIPSyscallExec(t *testing.T) {
syscallIP, syscallOK := likelyHomeRouterIPDarwinFetchRIB() syscallIP, syscallOK := likelyHomeRouterIPBSDFetchRIB()
netstatIP, netstatOK := likelyHomeRouterIPDarwinExec() netstatIP, netstatOK := likelyHomeRouterIPDarwinExec()
if syscallOK != netstatOK || syscallIP != netstatIP { if syscallOK != netstatOK || syscallIP != netstatIP {
t.Errorf("syscall() = %v, %v, netstat = %v, %v", t.Errorf("syscall() = %v, %v, netstat = %v, %v",

View File

@ -0,0 +1,26 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This might work on other BSDs, but only tested on FreeBSD.
//go:build freebsd
// +build freebsd
package interfaces
import (
"syscall"
"golang.org/x/net/route"
"golang.org/x/sys/unix"
)
// fetchRoutingTable calls route.FetchRIB, fetching NET_RT_DUMP.
func fetchRoutingTable() (rib []byte, err error) {
return route.FetchRIB(syscall.AF_UNSPEC, unix.NET_RT_DUMP, 0)
}
func parseRoutingTable(rib []byte) ([]route.Message, error) {
return route.ParseRIB(syscall.NET_RT_IFLIST, rib)
}