diff --git a/net/interfaces/interfaces_linux.go b/net/interfaces/interfaces_linux.go index 0290c067d..4b4dd08a3 100644 --- a/net/interfaces/interfaces_linux.go +++ b/net/interfaces/interfaces_linux.go @@ -5,10 +5,15 @@ package interfaces import ( + "bufio" "bytes" + "errors" + "io" "log" + "os" "os/exec" "runtime" + "strings" "go4.org/mem" "inet.af/netaddr" @@ -115,3 +120,91 @@ func likelyHomeRouterIPAndroid() (ret netaddr.IP, ok bool) { cmd.Wait() return ret, !ret.IsZero() } + +// DefaultRouteInterface returns the name of the network interface that owns +// the default route, not including any tailscale interfaces. +func DefaultRouteInterface() (string, error) { + v, err := defaultRouteInterfaceProcNet() + if err == nil { + return v, nil + } + if runtime.GOOS == "android" { + return defaultRouteInterfaceAndroidIPRoute() + } + return v, err +} + +var zeroRouteBytes = []byte("00000000") + +func defaultRouteInterfaceProcNet() (string, error) { + f, err := os.Open("/proc/net/route") + if err != nil { + return "", err + } + defer f.Close() + br := bufio.NewReaderSize(f, 128) + for { + line, err := br.ReadSlice('\n') + if err == io.EOF { + break + } + if err != nil { + return "", err + } + if !bytes.Contains(line, zeroRouteBytes) { + continue + } + fields := strings.Fields(string(line)) + ifc := fields[0] + ip := fields[1] + netmask := fields[7] + + if strings.HasPrefix(ifc, "tailscale") || + strings.HasPrefix(ifc, "wg") { + continue + } + if ip == "00000000" && netmask == "00000000" { + // default route + return ifc, nil // interface name + } + } + + return "", errors.New("no default routes found") + +} + +// defaultRouteInterfaceAndroidIPRoute tries to find the machine's default route interface name +// by parsing the "ip route" command output. We use this on Android where /proc/net/route +// can be missing entries or have locked-down permissions. +// See also comments in https://github.com/tailscale/tailscale/pull/666. +func defaultRouteInterfaceAndroidIPRoute() (ifname string, err error) { + cmd := exec.Command("/system/bin/ip", "route", "show", "table", "0") + out, err := cmd.StdoutPipe() + if err != nil { + return "", err + } + if err := cmd.Start(); err != nil { + log.Printf("interfaces: running /system/bin/ip: %v", err) + return "", err + } + // Search for line like "default via 10.0.2.2 dev radio0 table 1016 proto static mtu 1500 " + lineread.Reader(out, func(line []byte) error { + const pfx = "default via " + if !mem.HasPrefix(mem.B(line), mem.S(pfx)) { + return nil + } + ff := strings.Fields(string(line)) + for i, v := range ff { + if i > 0 && ff[i-1] == "dev" && ifname == "" { + ifname = v + } + } + return nil + }) + cmd.Process.Kill() + cmd.Wait() + if ifname == "" { + return "", errors.New("no default routes found") + } + return ifname, nil +} diff --git a/net/interfaces/interfaces_linux_test.go b/net/interfaces/interfaces_linux_test.go new file mode 100644 index 000000000..3aa69d2f6 --- /dev/null +++ b/net/interfaces/interfaces_linux_test.go @@ -0,0 +1,16 @@ +// Copyright (c) 2020 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. + +package interfaces + +import "testing" + +func BenchmarkDefaultRouteInterface(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := DefaultRouteInterface(); err != nil { + b.Fatal(err) + } + } +} diff --git a/net/netns/netns_linux.go b/net/netns/netns_linux.go index 5a607066b..d24b5adfc 100644 --- a/net/netns/netns_linux.go +++ b/net/netns/netns_linux.go @@ -5,19 +5,15 @@ package netns import ( - "bufio" - "bytes" - "errors" "flag" "fmt" - "io" "os" "os/exec" - "strings" "sync" "syscall" "golang.org/x/sys/unix" + "tailscale.com/net/interfaces" ) // tailscaleBypassMark is the mark indicating that packets originating @@ -43,47 +39,6 @@ func ipRuleAvailable() bool { return ipRuleOnce.v } -var zeroRouteBytes = []byte("00000000") - -// defaultRouteInterface returns the name of the network interface that owns -// the default route, not including any tailscale interfaces. We only use -// this in SO_BINDTODEVICE mode. -func defaultRouteInterface() (string, error) { - f, err := os.Open("/proc/net/route") - if err != nil { - return "", err - } - defer f.Close() - br := bufio.NewReaderSize(f, 128) - for { - line, err := br.ReadSlice('\n') - if err == io.EOF { - break - } - if err != nil { - return "", err - } - if !bytes.Contains(line, zeroRouteBytes) { - continue - } - fields := strings.Fields(string(line)) - ifc := fields[0] - ip := fields[1] - netmask := fields[7] - - if strings.HasPrefix(ifc, "tailscale") || - strings.HasPrefix(ifc, "wg") { - continue - } - if ip == "00000000" && netmask == "00000000" { - // default route - return ifc, nil // interface name - } - } - - return "", errors.New("no default routes found") -} - // ignoreErrors returns true if we should ignore setsocketopt errors in // this instance. func ignoreErrors() bool { @@ -133,7 +88,7 @@ func setBypassMark(fd uintptr) error { } func bindToDevice(fd uintptr) error { - ifc, err := defaultRouteInterface() + ifc, err := interfaces.DefaultRouteInterface() if err != nil { // Make sure we bind to *some* interface, // or we could get a routing loop. diff --git a/net/netns/netns_linux_test.go b/net/netns/netns_linux_test.go index 5afe647ef..8b050f7f5 100644 --- a/net/netns/netns_linux_test.go +++ b/net/netns/netns_linux_test.go @@ -49,12 +49,3 @@ func TestBypassMarkInSync(t *testing.T) { } t.Errorf("tailscaleBypassMark not found in router_linux.go") } - -func BenchmarkDefaultRouteInterface(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - if _, err := defaultRouteInterface(); err != nil { - b.Fatal(err) - } - } -}