From 52e24aa966ffaae20afbf0b5561bc1d34c102d33 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 14 Feb 2021 07:48:38 -0800 Subject: [PATCH] net/{interfaces,ns}: add tailscaled-mode darwin routing looping prevention Fixes #1331 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/depaware.txt | 2 +- cmd/tailscaled/depaware.txt | 2 +- .../interfaces_darwin_tailscaled.go | 81 +++++++++++++++++++ .../interfaces_defaultrouteif_todo.go | 2 +- net/netns/netns_darwin_tailscaled.go | 52 ++++++++++++ net/netns/netns_default.go | 2 +- 6 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 net/interfaces/interfaces_darwin_tailscaled.go create mode 100644 net/netns/netns_darwin_tailscaled.go diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 07ea30a8a..7bd833cec 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -72,7 +72,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/net/http2/hpack from net/http golang.org/x/net/idna from golang.org/x/net/http/httpguts+ golang.org/x/net/proxy from tailscale.com/net/netns - D golang.org/x/net/route from net + D golang.org/x/net/route from net+ golang.org/x/oauth2 from tailscale.com/ipn+ golang.org/x/oauth2/internal from golang.org/x/oauth2 golang.org/x/sync/errgroup from tailscale.com/derp diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index e2fa68aab..c19b50860 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -157,7 +157,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/net/ipv4 from github.com/tailscale/wireguard-go/device golang.org/x/net/ipv6 from github.com/tailscale/wireguard-go/device+ golang.org/x/net/proxy from tailscale.com/net/netns - D golang.org/x/net/route from net + D golang.org/x/net/route from net+ golang.org/x/oauth2 from tailscale.com/control/controlclient+ golang.org/x/oauth2/internal from golang.org/x/oauth2 golang.org/x/sync/errgroup from tailscale.com/derp diff --git a/net/interfaces/interfaces_darwin_tailscaled.go b/net/interfaces/interfaces_darwin_tailscaled.go new file mode 100644 index 000000000..da9eb3dbe --- /dev/null +++ b/net/interfaces/interfaces_darwin_tailscaled.go @@ -0,0 +1,81 @@ +// 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. + +// +build darwin,!redo,!ios +// (Exclude redo, because we don't want this code in the App Store +// version's sandbox, where it won't work, and also don't want it on +// iOS. This is just for utun-using non-sandboxed cmd/tailscaled on macOS. + +package interfaces + +import ( + "errors" + "fmt" + "net" + "syscall" + + "golang.org/x/net/route" +) + +func DefaultRouteInterface() (string, error) { + idx, err := DefaultRouteInterfaceIndex() + if err != nil { + return "", err + } + iface, err := net.InterfaceByIndex(idx) + if err != nil { + return "", err + } + return iface.Name, nil +} + +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 := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0) + if err != nil { + return 0, fmt.Errorf("FetchRIB: %w", err) + } + msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib) + if err != nil { + return 0, fmt.Errorf("Parse: %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) +} diff --git a/net/interfaces/interfaces_defaultrouteif_todo.go b/net/interfaces/interfaces_defaultrouteif_todo.go index a5067151a..255543336 100644 --- a/net/interfaces/interfaces_defaultrouteif_todo.go +++ b/net/interfaces/interfaces_defaultrouteif_todo.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !linux,!windows +// +build !linux,!windows,!darwin darwin,redo package interfaces diff --git a/net/netns/netns_darwin_tailscaled.go b/net/netns/netns_darwin_tailscaled.go new file mode 100644 index 000000000..a5a323fd2 --- /dev/null +++ b/net/netns/netns_darwin_tailscaled.go @@ -0,0 +1,52 @@ +// Copyright (c) 2021 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. + +// +build darwin,!redo + +package netns + +import ( + "fmt" + "log" + "strings" + "syscall" + + "golang.org/x/sys/unix" + "tailscale.com/net/interfaces" +) + +// control marks c as necessary to dial in a separate network namespace. +// +// It's intentionally the same signature as net.Dialer.Control +// and net.ListenConfig.Control. +func control(network, address string, c syscall.RawConn) error { + if strings.HasPrefix(address, "127.") || address == "::1" { + // Don't bind to an interface for localhost connections. + return nil + } + idx, err := interfaces.DefaultRouteInterfaceIndex() + if err != nil { + log.Printf("netns: DefaultRouteInterfaceIndex: %v", err) + return nil + } + v6 := strings.Contains(address, "]:") || strings.HasSuffix(network, "6") // hacky test for v6 + proto := unix.IPPROTO_IP + opt := unix.IP_BOUND_IF + if v6 { + proto = unix.IPPROTO_IPV6 + opt = unix.IPV6_BOUND_IF + } + + var sockErr error + err = c.Control(func(fd uintptr) { + sockErr = unix.SetsockoptInt(int(fd), proto, opt, idx) + }) + if err != nil { + return fmt.Errorf("RawConn.Control on %T: %w", c, err) + } + if sockErr != nil { + log.Printf("netns: control(%q, %q), v6=%v, index=%v: %v", network, address, v6, idx, sockErr) + } + return sockErr +} diff --git a/net/netns/netns_default.go b/net/netns/netns_default.go index e794fccb7..0a0e0179b 100644 --- a/net/netns/netns_default.go +++ b/net/netns/netns_default.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !linux,!windows +// +build !linux,!windows,!darwin darwin,redo package netns