Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions pickregion.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (

"tailscale.com/net/netcheck"
"tailscale.com/net/netmon"
"tailscale.com/net/netns"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
)
Expand All @@ -23,14 +22,10 @@ import (
// nil error) if the netcheck report contained no usable region
// latencies.
func PickBestRegion(ctx context.Context, dm *tailcfg.DERPMap) (regionID tailcfg.DERPRegionID, err error) {
// Disable netns before netcheck binds its STUN sockets, not just
// at engine creation as createEngine does, which runs after this.
// tailcat has no TUN device whose routes netns would need to
// avoid, and netns's unprivileged fallback (SO_BINDTODEVICE to
// the default interface) blackholes loopback UDP, making every
// netcheck against a loopback DERP map burn the full 3-second
// STUN timeout before falling back to HTTPS probes.
netns.SetEnabled(false)
// Set the netns policy before netcheck binds its STUN sockets, not
// just at engine creation as createEngine does, which runs after
// this. See configureNetNS for why it depends on the OS.
configureNetNS()
nc := &netcheck.Client{
NetMon: netmon.NewStatic(),
Verbose: Verbose,
Expand Down
22 changes: 21 additions & 1 deletion tailcat.go
Original file line number Diff line number Diff line change
Expand Up @@ -1905,7 +1905,7 @@ func createEngine(logf logger.Logf, lb *locoBackend) (err error) {
// call-me-maybe messages that advertise our UDP endpoints (see
// locoBackend.advertiseEndpoints).
conf.ForceDiscoKey = discoPrivateForNode(lb.priv)
netns.SetEnabled(false)
configureNetNS()
e, err := wgengine.NewUserspaceEngine(logf, conf)
if err != nil {
logf("wgengine.NewUserspaceEngine(tun %q) error: %v", "userspace-networking", err)
Expand All @@ -1916,6 +1916,26 @@ func createEngine(logf logger.Logf, lb *locoBackend) (err error) {
return nil
}

// configureNetNS sets the process-wide netns policy before tailcat opens
// any sockets.
//
// On macOS netns stays on. It binds tailcat's UDP sockets and DERP
// connections to the default physical interface, as tailscaled does, so
// a co-located VPN that takes the default route, such as the Tailscale
// app with an exit node selected, does not carry tailcat's WireGuard
// packets inside its own tunnel. Loopback traffic still works from
// sockets bound this way.
//
// Other platforms turn netns off. On Linux, netns's unprivileged fallback
// (SO_BINDTODEVICE to the default interface) blackholes loopback UDP,
// making every netcheck against a loopback DERP map burn the full
// 3-second STUN timeout before falling back to HTTPS probes.
func configureNetNS() {
if runtime.GOOS != "darwin" {
netns.SetEnabled(false)
}
}

// Client connects to a [Server] over a WireGuard tunnel relayed through DERP.
// Populate Server (the only required field, or use the [NewClient]
// shorthand), then just dial: [Client.Dial], the DialTCP methods, and the
Expand Down
21 changes: 21 additions & 0 deletions tailcat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"net/http/httptest"
"net/netip"
"os"
"runtime"
"strings"
"sync"
"sync/atomic"
Expand All @@ -35,6 +36,8 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"tailscale.com/envknob"
"tailscale.com/net/netcheck"
"tailscale.com/net/netmon"
"tailscale.com/net/netns"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/tstest/integration"
Expand Down Expand Up @@ -1682,3 +1685,21 @@ func TestPeerKey(t *testing.T) {
}
}
}

// TestNetNSPolicy checks the netns policy that configureNetNS sets when a
// Server starts. On macOS netns stays on, so tailcat's sockets bind to the
// default physical interface instead of following a co-located VPN's
// default route. Elsewhere it is off.
func TestNetNSPolicy(t *testing.T) {
dm := integration.RunDERPAndSTUN(t, mkLogger(t, "derpstun"), "127.0.0.1")
s := &Server{Key: key.NewNode(), Logf: mkLogger(t, "server"), Region: dm.Regions[1]}
t.Cleanup(func() { s.Close() })
if err := s.Start(); err != nil {
t.Fatalf("server Start: %v", err)
}

bound := netns.Listener(t.Logf, netmon.NewStatic()).Control != nil
if want := runtime.GOOS == "darwin"; bound != want {
t.Errorf("on %s after Server.Start, netns binds sockets = %v; want %v", runtime.GOOS, bound, want)
}
}