diff --git a/net/netcheck/netcheck.go b/net/netcheck/netcheck.go index 402cfea72..3cfa2d375 100644 --- a/net/netcheck/netcheck.go +++ b/net/netcheck/netcheck.go @@ -23,6 +23,7 @@ "inet.af/netaddr" "tailscale.com/net/dnscache" "tailscale.com/net/interfaces" + "tailscale.com/net/netns" "tailscale.com/net/stun" "tailscale.com/syncs" "tailscale.com/tailcfg" @@ -656,7 +657,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e } // Create a UDP4 socket used for sending to our discovered IPv4 address. - rs.pc4Hair, err = net.ListenPacket("udp4", ":0") + rs.pc4Hair, err = netns.Listener().ListenPacket(ctx, "udp4", ":0") if err != nil { c.logf("udp4: %v", err) return nil, err @@ -666,7 +667,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e if f := c.GetSTUNConn4; f != nil { rs.pc4 = f() } else { - u4, err := net.ListenPacket("udp4", ":0") + u4, err := netns.Listener().ListenPacket(ctx, "udp4", ":0") if err != nil { c.logf("udp4: %v", err) return nil, err @@ -679,7 +680,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e if f := c.GetSTUNConn6; f != nil { rs.pc6 = f() } else { - u6, err := net.ListenPacket("udp6", ":0") + u6, err := netns.Listener().ListenPacket(ctx, "udp6", ":0") if err != nil { c.logf("udp6: %v", err) } else { diff --git a/net/netns/netns.go b/net/netns/netns.go new file mode 100644 index 000000000..e204a0d1b --- /dev/null +++ b/net/netns/netns.go @@ -0,0 +1,33 @@ +// 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 netns contains the common code for using the Go net package +// in a logical "network namespace" to avoid routing loops where +// Tailscale-created packets would otherwise loop back through +// Tailscale routes. +// +// Despite the name netns, the exact mechanism used differs by +// operating system, and perhaps even by version of the OS. +package netns + +import ( + "net" + "syscall" +) + +// 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. +func Listener() *net.ListenConfig { + return &net.ListenConfig{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 +} diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 230ee3a2b..e820b4c3a 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -36,6 +36,7 @@ "tailscale.com/net/dnscache" "tailscale.com/net/interfaces" "tailscale.com/net/netcheck" + "tailscale.com/net/netns" "tailscale.com/net/stun" "tailscale.com/syncs" "tailscale.com/tailcfg" @@ -1536,14 +1537,15 @@ func (c *Conn) bind1(ruc **RebindingUDPConn, which string) error { } var pc net.PacketConn var err error + listenCtx := context.Background() // unused without DNS name to resolve if c.pconnPort == 0 && DefaultPort != 0 { - pc, err = net.ListenPacket(which, fmt.Sprintf("%s:%d", host, DefaultPort)) + pc, err = netns.Listener().ListenPacket(listenCtx, which, fmt.Sprintf("%s:%d", host, DefaultPort)) if err != nil { c.logf("magicsock: bind: default port %s/%v unavailable; picking random", which, DefaultPort) } } if pc == nil { - pc, err = net.ListenPacket(which, fmt.Sprintf("%s:%d", host, c.pconnPort)) + pc, err = netns.Listener().ListenPacket(listenCtx, which, fmt.Sprintf("%s:%d", host, c.pconnPort)) } if err != nil { c.logf("magicsock: bind(%s/%v): %v", which, c.pconnPort, err) @@ -1563,12 +1565,13 @@ func (c *Conn) Rebind() { if v, _ := strconv.ParseBool(os.Getenv("IN_TS_TEST")); v { host = "127.0.0.1" } + listenCtx := context.Background() // unused without DNS name to resolve if c.pconnPort != 0 { c.pconn4.mu.Lock() if err := c.pconn4.pconn.Close(); err != nil { c.logf("magicsock: link change close failed: %v", err) } - packetConn, err := net.ListenPacket("udp4", fmt.Sprintf("%s:%d", host, c.pconnPort)) + packetConn, err := netns.Listener().ListenPacket(listenCtx, "udp4", fmt.Sprintf("%s:%d", host, c.pconnPort)) if err == nil { c.logf("magicsock: link change rebound port: %d", c.pconnPort) c.pconn4.pconn = packetConn.(*net.UDPConn) @@ -1579,7 +1582,7 @@ func (c *Conn) Rebind() { c.pconn4.mu.Unlock() } c.logf("magicsock: link change, binding new port") - packetConn, err := net.ListenPacket("udp4", host+":0") + packetConn, err := netns.Listener().ListenPacket(listenCtx, "udp4", host+":0") if err != nil { c.logf("magicsock: link change failed to bind new port: %v", err) return