From 9c2ad7086c0daef9c914ef1e59d0142b50f789f3 Mon Sep 17 00:00:00 2001
From: Anton Tolchanov <anton@tailscale.com>
Date: Fri, 14 Oct 2022 09:29:34 +0100
Subject: [PATCH] net/interfaces: deduplicate route table parsing on Darwin and
 FreeBSD

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
---
 net/interfaces/interfaces_bsd.go         |  26 ++---
 net/interfaces/interfaces_darwin.go      | 116 +----------------------
 net/interfaces/interfaces_darwin_test.go |   2 +-
 net/interfaces/interfaces_freebsd.go     |  26 +++++
 4 files changed, 37 insertions(+), 133 deletions(-)
 create mode 100644 net/interfaces/interfaces_freebsd.go

diff --git a/net/interfaces/interfaces_bsd.go b/net/interfaces/interfaces_bsd.go
index 60bba1d95..8efda37f5 100644
--- a/net/interfaces/interfaces_bsd.go
+++ b/net/interfaces/interfaces_bsd.go
@@ -2,11 +2,10 @@
 // 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.
-// Originally a fork of interfaces_darwin.go with slightly different flags.
+// Common code for FreeBSD and Darwin.
 
-//go:build freebsd
-// +build freebsd
+//go:build darwin || freebsd
+// +build darwin freebsd
 
 package interfaces
 
@@ -16,7 +15,6 @@ import (
 	"log"
 	"net"
 	"net/netip"
-	"syscall"
 
 	"golang.org/x/net/route"
 	"golang.org/x/sys/unix"
@@ -37,11 +35,6 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
 	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) {
 	// $ netstat -nr
 	// Routing tables
@@ -61,7 +54,7 @@ func DefaultRouteInterfaceIndex() (int, error) {
 	if err != nil {
 		return 0, fmt.Errorf("route.FetchRIB: %w", err)
 	}
-	msgs, err := route.ParseRIB(unix.NET_RT_IFLIST, rib)
+	msgs, err := parseRoutingTable(rib)
 	if err != nil {
 		return 0, fmt.Errorf("route.ParseRIB: %w", err)
 	}
@@ -71,12 +64,10 @@ func DefaultRouteInterfaceIndex() (int, error) {
 		if !ok {
 			continue
 		}
-		const RTF_GATEWAY = 0x2
-		const RTF_IFSCOPE = 0x1000000
-		if rm.Flags&RTF_GATEWAY == 0 {
+		if rm.Flags&unix.RTF_GATEWAY == 0 {
 			continue
 		}
-		if rm.Flags&RTF_IFSCOPE != 0 {
+		if rm.Flags&unix.RTF_IFSCOPE != 0 {
 			continue
 		}
 		indexSeen[rm.Index]++
@@ -102,7 +93,7 @@ func likelyHomeRouterIPBSDFetchRIB() (ret netip.Addr, ok bool) {
 		log.Printf("routerIP/FetchRIB: %v", err)
 		return ret, false
 	}
-	msgs, err := route.ParseRIB(unix.NET_RT_IFLIST, rib)
+	msgs, err := parseRoutingTable(rib)
 	if err != nil {
 		log.Printf("routerIP/ParseRIB: %v", err)
 		return ret, false
@@ -112,11 +103,10 @@ func likelyHomeRouterIPBSDFetchRIB() (ret netip.Addr, ok bool) {
 		if !ok {
 			continue
 		}
-		const RTF_IFSCOPE = 0x1000000
 		if rm.Flags&unix.RTF_GATEWAY == 0 {
 			continue
 		}
-		if rm.Flags&RTF_IFSCOPE != 0 {
+		if rm.Flags&unix.RTF_IFSCOPE != 0 {
 			continue
 		}
 		if len(rm.Addrs) > unix.RTAX_GATEWAY {
diff --git a/net/interfaces/interfaces_darwin.go b/net/interfaces/interfaces_darwin.go
index 60335bf80..d0a7e8ef7 100644
--- a/net/interfaces/interfaces_darwin.go
+++ b/net/interfaces/interfaces_darwin.go
@@ -5,128 +5,16 @@
 package interfaces
 
 import (
-	"errors"
-	"fmt"
-	"log"
-	"net"
-	"net/netip"
 	"syscall"
 
 	"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.
 func fetchRoutingTable() (rib []byte, err error) {
 	return route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0)
 }
 
-func DefaultRouteInterfaceIndex() (int, error) {
-	// $ netstat -nr
-	// 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
+func parseRoutingTable(rib []byte) ([]route.Message, error) {
+	return route.ParseRIB(syscall.NET_RT_IFLIST2, rib)
 }
diff --git a/net/interfaces/interfaces_darwin_test.go b/net/interfaces/interfaces_darwin_test.go
index 245ea8211..3aff070ae 100644
--- a/net/interfaces/interfaces_darwin_test.go
+++ b/net/interfaces/interfaces_darwin_test.go
@@ -16,7 +16,7 @@ import (
 )
 
 func TestLikelyHomeRouterIPSyscallExec(t *testing.T) {
-	syscallIP, syscallOK := likelyHomeRouterIPDarwinFetchRIB()
+	syscallIP, syscallOK := likelyHomeRouterIPBSDFetchRIB()
 	netstatIP, netstatOK := likelyHomeRouterIPDarwinExec()
 	if syscallOK != netstatOK || syscallIP != netstatIP {
 		t.Errorf("syscall() = %v, %v, netstat = %v, %v",
diff --git a/net/interfaces/interfaces_freebsd.go b/net/interfaces/interfaces_freebsd.go
new file mode 100644
index 000000000..8a7270071
--- /dev/null
+++ b/net/interfaces/interfaces_freebsd.go
@@ -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)
+}