mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-16 11:41:39 +00:00
net/interfaces: get Linux default route from netlink as fallback
If it's in a non-standard table, as it is on Unifi UDM Pro, apparently. Updates #4038 (probably fixes, but don't have hardware to verify) Change-Id: I2cb9a098d8bb07d1a97a6045b686aca31763a937 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com> (cherry picked from commit 55095df6445f15be35d64dc36c23b719be62be5e)
This commit is contained in:
parent
4e0b00ad83
commit
fca3592c1c
@ -4,8 +4,14 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
|
||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
|
||||
L github.com/klauspost/compress/flate from nhooyr.io/websocket
|
||||
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
|
||||
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
|
||||
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
||||
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli+
|
||||
github.com/peterbourgon/ff/v3 from github.com/peterbourgon/ff/v3/ffcli
|
||||
github.com/peterbourgon/ff/v3/ffcli from tailscale.com/cmd/tailscale/cli
|
||||
@ -97,6 +103,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305
|
||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
||||
golang.org/x/net/dns/dnsmessage from net+
|
||||
golang.org/x/net/http/httpguts from net/http+
|
||||
golang.org/x/net/http/httpproxy from net/http
|
||||
|
@ -74,7 +74,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/insomniacslk/dhcp/rfc1035label from github.com/insomniacslk/dhcp/dhcpv4
|
||||
L github.com/jmespath/go-jmespath from github.com/aws/aws-sdk-go-v2/service/ssm
|
||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor+
|
||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||
github.com/klauspost/compress from github.com/klauspost/compress/zstd
|
||||
L github.com/klauspost/compress/flate from nhooyr.io/websocket
|
||||
|
@ -687,7 +687,8 @@ func netInterfaces() ([]Interface, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// DefaultRouteDetails are the
|
||||
// DefaultRouteDetails are the details about a default route returned
|
||||
// by DefaultRoute.
|
||||
type DefaultRouteDetails struct {
|
||||
// InterfaceName is the interface name. It must always be populated.
|
||||
// It's like "eth0" (Linux), "Ethernet 2" (Windows), "en0" (macOS).
|
||||
|
@ -11,12 +11,16 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/jsimonetti/rtnetlink"
|
||||
"github.com/mdlayher/netlink"
|
||||
"go4.org/mem"
|
||||
"golang.org/x/sys/unix"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/util/lineread"
|
||||
@ -70,9 +74,7 @@ func likelyHomeRouterIPLinux() (ret netaddr.IP, ok bool) {
|
||||
if err != nil {
|
||||
return nil // ignore error, skip line and keep going
|
||||
}
|
||||
const RTF_UP = 0x0001
|
||||
const RTF_GATEWAY = 0x0002
|
||||
if flags&(RTF_UP|RTF_GATEWAY) != RTF_UP|RTF_GATEWAY {
|
||||
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
|
||||
return nil
|
||||
}
|
||||
ipu32, err := mem.ParseUint(gwHex, 16, 32)
|
||||
@ -145,7 +147,62 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
|
||||
d.InterfaceName = v
|
||||
return d, err
|
||||
}
|
||||
return d, err
|
||||
// Issue 4038: the default route (such as on Unifi UDM Pro)
|
||||
// might be in a non-default table, so it won't show up in
|
||||
// /proc/net/route. Use netlink to find the default route.
|
||||
//
|
||||
// TODO(bradfitz): this allocates a fair bit. We should track
|
||||
// this in wgengine/monitor instead and have
|
||||
// interfaces.GetState take a link monitor or similar so the
|
||||
// routing table can be cached and the monitor's existing
|
||||
// subscription to route changes can update the cached state,
|
||||
// rather than querying the whole thing every time like
|
||||
// defaultRouteFromNetlink does.
|
||||
//
|
||||
// Then we should just always try to use the cached route
|
||||
// table from netlink every time, and only use /proc/net/route
|
||||
// as a fallback for weird environments where netlink might be
|
||||
// banned but /proc/net/route is emulated (e.g. stuff like
|
||||
// Cloud Run?).
|
||||
return defaultRouteFromNetlink()
|
||||
}
|
||||
|
||||
func defaultRouteFromNetlink() (d DefaultRouteDetails, err error) {
|
||||
c, err := rtnetlink.Dial(&netlink.Config{Strict: true})
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("defaultRouteFromNetlink: Dial: %w", err)
|
||||
}
|
||||
defer c.Close()
|
||||
rms, err := c.Route.List()
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("defaultRouteFromNetlink: List: %w", err)
|
||||
}
|
||||
for _, rm := range rms {
|
||||
if rm.Attributes.Gateway == nil {
|
||||
// A default route has a gateway. If it doesn't, skip it.
|
||||
continue
|
||||
}
|
||||
if rm.Attributes.Dst != nil {
|
||||
// A default route has a nil destination to mean anything
|
||||
// so ignore any route for a specific destination.
|
||||
// TODO(bradfitz): better heuristic?
|
||||
// empirically this seems like enough.
|
||||
continue
|
||||
}
|
||||
// TODO(bradfitz): care about address family, if
|
||||
// callers ever start caring about v4-vs-v6 default
|
||||
// route differences.
|
||||
idx := int(rm.Attributes.OutIface)
|
||||
if idx == 0 {
|
||||
continue
|
||||
}
|
||||
if iface, err := net.InterfaceByIndex(idx); err == nil {
|
||||
d.InterfaceName = iface.Name
|
||||
d.InterfaceIndex = idx
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
return d, errNoDefaultRoute
|
||||
}
|
||||
|
||||
var zeroRouteBytes = []byte("00000000")
|
||||
@ -155,6 +212,8 @@ var procNetRoutePath = "/proc/net/route"
|
||||
// /proc/net/route looking for a default route.
|
||||
const maxProcNetRouteRead = 1000
|
||||
|
||||
var errNoDefaultRoute = errors.New("no default route found")
|
||||
|
||||
func defaultRouteInterfaceProcNetInternal(bufsize int) (string, error) {
|
||||
f, err := os.Open(procNetRoutePath)
|
||||
if err != nil {
|
||||
@ -168,7 +227,7 @@ func defaultRouteInterfaceProcNetInternal(bufsize int) (string, error) {
|
||||
lineNum++
|
||||
line, err := br.ReadSlice('\n')
|
||||
if err == io.EOF || lineNum > maxProcNetRouteRead {
|
||||
return "", fmt.Errorf("no default routes found: %w", err)
|
||||
return "", errNoDefaultRoute
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -5,7 +5,9 @@
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -107,3 +109,14 @@ func BenchmarkDefaultRouteInterface(b *testing.B) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouteLinuxNetlink(t *testing.T) {
|
||||
d, err := defaultRouteFromNetlink()
|
||||
if errors.Is(err, fs.ErrPermission) {
|
||||
t.Skip(err)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("Got: %+v", d)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user