net/netns, net/interfaces: move defaultRouteInterface, add Android fallback
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>pull/668/merge
parent
84dc891843
commit
25b021388b
|
@ -5,10 +5,15 @@
|
||||||
package interfaces
|
package interfaces
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
|
@ -115,3 +120,91 @@ func likelyHomeRouterIPAndroid() (ret netaddr.IP, ok bool) {
|
||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
return ret, !ret.IsZero()
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,19 +5,15 @@
|
||||||
package netns
|
package netns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
"tailscale.com/net/interfaces"
|
||||||
)
|
)
|
||||||
|
|
||||||
// tailscaleBypassMark is the mark indicating that packets originating
|
// tailscaleBypassMark is the mark indicating that packets originating
|
||||||
|
@ -43,47 +39,6 @@ func ipRuleAvailable() bool {
|
||||||
return ipRuleOnce.v
|
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
|
// ignoreErrors returns true if we should ignore setsocketopt errors in
|
||||||
// this instance.
|
// this instance.
|
||||||
func ignoreErrors() bool {
|
func ignoreErrors() bool {
|
||||||
|
@ -133,7 +88,7 @@ func setBypassMark(fd uintptr) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindToDevice(fd uintptr) error {
|
func bindToDevice(fd uintptr) error {
|
||||||
ifc, err := defaultRouteInterface()
|
ifc, err := interfaces.DefaultRouteInterface()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Make sure we bind to *some* interface,
|
// Make sure we bind to *some* interface,
|
||||||
// or we could get a routing loop.
|
// or we could get a routing loop.
|
||||||
|
|
|
@ -49,12 +49,3 @@ func TestBypassMarkInSync(t *testing.T) {
|
||||||
}
|
}
|
||||||
t.Errorf("tailscaleBypassMark not found in router_linux.go")
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue