From 5114df415e6a987bae3e40ae730c102c20ee8477 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 29 May 2020 00:43:15 +0000 Subject: [PATCH] net/netns: set the bypass socket mark on linux. This allows tailscaled's own traffic to bypass Tailscale-managed routes, so that things like tailscale-provided default routes don't break tailscaled itself. Progress on #144. Signed-off-by: David Anderson --- derp/derphttp/derphttp_test.go | 5 +++ net/netcheck/netcheck_test.go | 5 +++ net/netns/netns.go | 17 +++++----- net/netns/netns_default.go | 14 +++++++++ net/netns/netns_linux.go | 46 ++++++++++++++++++++++++++++ wgengine/magicsock/magicsock_test.go | 5 +++ wgengine/router/router_linux.go | 3 ++ wgengine/watchdog_test.go | 5 +++ 8 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 net/netns/netns_default.go create mode 100644 net/netns/netns_linux.go diff --git a/derp/derphttp/derphttp_test.go b/derp/derphttp/derphttp_test.go index ffb5348ad..2a700fba6 100644 --- a/derp/derphttp/derphttp_test.go +++ b/derp/derphttp/derphttp_test.go @@ -15,9 +15,14 @@ import ( "time" "tailscale.com/derp" + "tailscale.com/net/netns" "tailscale.com/types/key" ) +func init() { + netns.TestOnlySkipPrivilegedOps() +} + func TestSendRecv(t *testing.T) { const numClients = 3 var serverPrivateKey key.Private diff --git a/net/netcheck/netcheck_test.go b/net/netcheck/netcheck_test.go index 6cf0b8092..489697e5c 100644 --- a/net/netcheck/netcheck_test.go +++ b/net/netcheck/netcheck_test.go @@ -16,11 +16,16 @@ import ( "time" "tailscale.com/net/interfaces" + "tailscale.com/net/netns" "tailscale.com/net/stun" "tailscale.com/net/stun/stuntest" "tailscale.com/tailcfg" ) +func init() { + netns.TestOnlySkipPrivilegedOps() +} + func TestHairpinSTUN(t *testing.T) { tx := stun.NewTxID() c := &Client{ diff --git a/net/netns/netns.go b/net/netns/netns.go index 009425994..a48859a1d 100644 --- a/net/netns/netns.go +++ b/net/netns/netns.go @@ -13,9 +13,12 @@ package netns import ( "net" - "syscall" + + "tailscale.com/syncs" ) +var skipPrivileged syncs.AtomicBool + // Listener returns a new net.Listener with its Control hook func // initialized as necessary to run in logical network namespace that // doesn't route back into Tailscale. @@ -30,11 +33,9 @@ func Dialer() *net.Dialer { return &net.Dialer{Control: control} } -// 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 { - // TODO: implement - return nil +// TestOnlySkipPrivilegedOps disables any behavior in this package +// that requires root or other elevated privileges. It's used only in +// tests, and using it definitely breaks some Tailscale functionality. +func TestOnlySkipPrivilegedOps() { + skipPrivileged.Set(true) } diff --git a/net/netns/netns_default.go b/net/netns/netns_default.go new file mode 100644 index 000000000..7de7d9fe8 --- /dev/null +++ b/net/netns/netns_default.go @@ -0,0 +1,14 @@ +// 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 !linux + +package netns + +import "syscall" + +// control does nothing to c. +func control(network, address string, c syscall.RawConn) error { + return nil +} diff --git a/net/netns/netns_linux.go b/net/netns/netns_linux.go new file mode 100644 index 000000000..173f153b8 --- /dev/null +++ b/net/netns/netns_linux.go @@ -0,0 +1,46 @@ +// 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 linux + +package netns + +import ( + "fmt" + "syscall" + + "golang.org/x/sys/unix" +) + +// tailscaleBypassMark is the mark indicating that packets originating +// from a socket should bypass Tailscale-managed routes during routing +// table lookups. +// +// Keep this in sync with tailscaleBypassMark in +// wgengine/router/router_linux.go. +const tailscaleBypassMark = 0x20000 + +// 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 skipPrivileged.Get() { + // We can't set socket marks without CAP_NET_ADMIN on linux, + // skip as requested. + return nil + } + + var controlErr error + err := c.Control(func(fd uintptr) { + controlErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, tailscaleBypassMark) + }) + if err != nil { + return fmt.Errorf("setting socket mark: %w", err) + } + if controlErr != nil { + return fmt.Errorf("setting socket mark: %w", controlErr) + } + return nil +} diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 859fb5b00..37a44e77e 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -26,6 +26,7 @@ import ( "tailscale.com/derp" "tailscale.com/derp/derphttp" "tailscale.com/derp/derpmap" + "tailscale.com/net/netns" "tailscale.com/net/stun/stuntest" "tailscale.com/tailcfg" "tailscale.com/tstest" @@ -35,6 +36,10 @@ import ( "tailscale.com/wgengine/tstun" ) +func init() { + netns.TestOnlySkipPrivilegedOps() +} + // WaitReady waits until the magicsock is entirely initialized and connected // to its home DERP server. This is normally not necessary, since magicsock // is intended to be entirely asynchronous, but it helps eliminate race diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go index a8704960f..2b998326d 100644 --- a/wgengine/router/router_linux.go +++ b/wgengine/router/router_linux.go @@ -44,6 +44,9 @@ const ( tailscaleSubnetRouteMark = "0x10000" // Packet was originated by tailscaled itself, and must not be // routed over the Tailscale network. + // + // Keep this in sync with tailscaleBypassMark in + // net/netns/netns_linux.go. tailscaleBypassMark = "0x20000" ) diff --git a/wgengine/watchdog_test.go b/wgengine/watchdog_test.go index 0e45dd641..7bfdc23e1 100644 --- a/wgengine/watchdog_test.go +++ b/wgengine/watchdog_test.go @@ -11,10 +11,15 @@ import ( "testing" "time" + "tailscale.com/net/netns" "tailscale.com/wgengine/router" "tailscale.com/wgengine/tstun" ) +func init() { + netns.TestOnlySkipPrivilegedOps() +} + func TestWatchdog(t *testing.T) { t.Parallel()