Procházet zdrojové kódy

wgengine, proxymap: split out port mapping from Engine to new type

(Continuing quest to remove rando stuff from the "Engine")

Updates #cleanup

Change-Id: I77f39902c2194410c10c054b545d70c9744250b0
Signed-off-by: Brad Fitzpatrick <[email protected]>
Brad Fitzpatrick před 2 roky
rodič
revize
3d37328af6

+ 1 - 0
cmd/tailscaled/depaware.txt

@@ -289,6 +289,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         tailscale.com/net/wsconn                                     from tailscale.com/control/controlhttp+
         tailscale.com/paths                                          from tailscale.com/ipn/ipnlocal+
      💣 tailscale.com/portlist                                       from tailscale.com/ipn/ipnlocal
+        tailscale.com/proxymap                                       from tailscale.com/tsd+
         tailscale.com/safesocket                                     from tailscale.com/client/tailscale+
         tailscale.com/smallzstd                                      from tailscale.com/control/controlclient+
   LD 💣 tailscale.com/ssh/tailssh                                    from tailscale.com/cmd/tailscaled

+ 8 - 1
cmd/tailscaled/tailscaled.go

@@ -711,7 +711,14 @@ func runDebugServer(mux *http.ServeMux, addr string) {
 }
 
 func newNetstack(logf logger.Logf, sys *tsd.System) (*netstack.Impl, error) {
-	return netstack.Create(logf, sys.Tun.Get(), sys.Engine.Get(), sys.MagicSock.Get(), sys.Dialer.Get(), sys.DNSManager.Get())
+	return netstack.Create(logf,
+		sys.Tun.Get(),
+		sys.Engine.Get(),
+		sys.MagicSock.Get(),
+		sys.Dialer.Get(),
+		sys.DNSManager.Get(),
+		sys.ProxyMapper(),
+	)
 }
 
 // mustStartProxyListeners creates listeners for local SOCKS and HTTP

+ 1 - 1
cmd/tsconnect/wasm/wasm_js.go

@@ -109,7 +109,7 @@ func newIPN(jsConfig js.Value) map[string]any {
 	}
 	sys.Set(eng)
 
-	ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get())
+	ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get(), sys.ProxyMapper())
 	if err != nil {
 		log.Fatalf("netstack.Create: %v", err)
 	}

+ 1 - 1
ipn/ipnlocal/local.go

@@ -851,7 +851,7 @@ func (b *LocalBackend) WhoIs(ipp netip.AddrPort) (n tailcfg.NodeView, u tailcfg.
 	if !ok {
 		var ip netip.Addr
 		if ipp.Port() != 0 {
-			ip, ok = b.e.WhoIsIPPort(ipp)
+			ip, ok = b.sys.ProxyMapper().WhoIsIPPort(ipp)
 		}
 		if !ok {
 			return zero, u, false

+ 72 - 0
proxymap/proxymap.go

@@ -0,0 +1,72 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package proxymap contains a mapping table for ephemeral localhost ports used
+// by tailscaled on behalf of remote Tailscale IPs for proxied connections.
+package proxymap
+
+import (
+	"net/netip"
+	"sync"
+	"time"
+
+	"tailscale.com/util/mak"
+)
+
+// Mapper tracks which localhost ip:ports correspond to which remote Tailscale
+// IPs for connections proxied by tailscaled.
+//
+// This is then used (via the WhoIsIPPort method) by localhost applications to
+// ask tailscaled (via the LocalAPI WhoIs method) the Tailscale identity that a
+// given localhost:port corresponds to.
+type Mapper struct {
+	mu sync.Mutex
+	m  map[netip.AddrPort]netip.Addr
+}
+
+// RegisterIPPortIdentity registers a given node (identified by its
+// Tailscale IP) as temporarily having the given IP:port for whois lookups.
+// The IP:port is generally a localhost IP and an ephemeral port, used
+// while proxying connections to localhost when tailscaled is running
+// in netstack mode.
+func (m *Mapper) RegisterIPPortIdentity(ipport netip.AddrPort, tsIP netip.Addr) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	mak.Set(&m.m, ipport, tsIP)
+}
+
+// UnregisterIPPortIdentity removes a temporary IP:port registration
+// made previously by RegisterIPPortIdentity.
+func (m *Mapper) UnregisterIPPortIdentity(ipport netip.AddrPort) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	delete(m.m, ipport)
+}
+
+var whoIsSleeps = [...]time.Duration{
+	0,
+	10 * time.Millisecond,
+	20 * time.Millisecond,
+	50 * time.Millisecond,
+	100 * time.Millisecond,
+}
+
+// WhoIsIPPort looks up an IP:port in the temporary registrations,
+// and returns a matching Tailscale IP, if it exists.
+func (m *Mapper) WhoIsIPPort(ipport netip.AddrPort) (tsIP netip.Addr, ok bool) {
+	// We currently have a registration race,
+	// https://github.com/tailscale/tailscale/issues/1616,
+	// so loop a few times for now waiting for the registration
+	// to appear.
+	// TODO(bradfitz,namansood): remove this once #1616 is fixed.
+	for _, d := range whoIsSleeps {
+		time.Sleep(d)
+		m.mu.Lock()
+		tsIP, ok = m.m[ipport]
+		m.mu.Unlock()
+		if ok {
+			return tsIP, true
+		}
+	}
+	return tsIP, false
+}

+ 9 - 1
tsd/tsd.go

@@ -27,6 +27,7 @@ import (
 	"tailscale.com/net/netmon"
 	"tailscale.com/net/tsdial"
 	"tailscale.com/net/tstun"
+	"tailscale.com/proxymap"
 	"tailscale.com/types/netmap"
 	"tailscale.com/wgengine"
 	"tailscale.com/wgengine/magicsock"
@@ -45,7 +46,9 @@ type System struct {
 	Tun            SubSystem[*tstun.Wrapper]
 	StateStore     SubSystem[ipn.StateStore]
 	Netstack       SubSystem[NetstackImpl] // actually a *netstack.Impl
-	controlKnobs   controlknobs.Knobs
+
+	controlKnobs controlknobs.Knobs
+	proxyMap     proxymap.Mapper
 }
 
 // NetstackImpl is the interface that *netstack.Impl implements.
@@ -103,6 +106,11 @@ func (s *System) ControlKnobs() *controlknobs.Knobs {
 	return &s.controlKnobs
 }
 
+// ProxyMapper returns the ephemeral ip:port mapper.
+func (s *System) ProxyMapper() *proxymap.Mapper {
+	return &s.proxyMap
+}
+
 // SubSystem represents some subsystem of the Tailscale node daemon.
 //
 // A subsystem can be set to a value, and then later retrieved. A subsystem

+ 1 - 1
tsnet/tsnet.go

@@ -511,7 +511,7 @@ func (s *Server) start() (reterr error) {
 	closePool.add(s.dialer)
 	sys.Set(eng)
 
-	ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), s.dialer, sys.DNSManager.Get())
+	ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), s.dialer, sys.DNSManager.Get(), sys.ProxyMapper())
 	if err != nil {
 		return fmt.Errorf("netstack.Create: %w", err)
 	}

+ 11 - 5
wgengine/netstack/netstack.go

@@ -43,6 +43,7 @@ import (
 	"tailscale.com/net/tsaddr"
 	"tailscale.com/net/tsdial"
 	"tailscale.com/net/tstun"
+	"tailscale.com/proxymap"
 	"tailscale.com/syncs"
 	"tailscale.com/tailcfg"
 	"tailscale.com/types/ipproto"
@@ -121,6 +122,7 @@ type Impl struct {
 	linkEP    *channel.Endpoint
 	tundev    *tstun.Wrapper
 	e         wgengine.Engine
+	pm        *proxymap.Mapper
 	mc        *magicsock.Conn
 	logf      logger.Logf
 	dialer    *tsdial.Dialer
@@ -154,7 +156,7 @@ const nicID = 1
 const maxUDPPacketSize = 1500
 
 // Create creates and populates a new Impl.
-func Create(logf logger.Logf, tundev *tstun.Wrapper, e wgengine.Engine, mc *magicsock.Conn, dialer *tsdial.Dialer, dns *dns.Manager) (*Impl, error) {
+func Create(logf logger.Logf, tundev *tstun.Wrapper, e wgengine.Engine, mc *magicsock.Conn, dialer *tsdial.Dialer, dns *dns.Manager, pm *proxymap.Mapper) (*Impl, error) {
 	if mc == nil {
 		return nil, errors.New("nil magicsock.Conn")
 	}
@@ -167,6 +169,9 @@ func Create(logf logger.Logf, tundev *tstun.Wrapper, e wgengine.Engine, mc *magi
 	if e == nil {
 		return nil, errors.New("nil Engine")
 	}
+	if pm == nil {
+		return nil, errors.New("nil proxymap.Mapper")
+	}
 	if dialer == nil {
 		return nil, errors.New("nil Dialer")
 	}
@@ -209,6 +214,7 @@ func Create(logf logger.Logf, tundev *tstun.Wrapper, e wgengine.Engine, mc *magi
 		linkEP:              linkEP,
 		tundev:              tundev,
 		e:                   e,
+		pm:                  pm,
 		mc:                  mc,
 		dialer:              dialer,
 		connsOpenBySubnetIP: make(map[netip.Addr]int),
@@ -984,8 +990,8 @@ func (ns *Impl) forwardTCP(getClient func(...tcpip.SettableSocketOption) *gonet.
 
 	backendLocalAddr := server.LocalAddr().(*net.TCPAddr)
 	backendLocalIPPort := netaddr.Unmap(backendLocalAddr.AddrPort())
-	ns.e.RegisterIPPortIdentity(backendLocalIPPort, clientRemoteIP)
-	defer ns.e.UnregisterIPPortIdentity(backendLocalIPPort)
+	ns.pm.RegisterIPPortIdentity(backendLocalIPPort, clientRemoteIP)
+	defer ns.pm.UnregisterIPPortIdentity(backendLocalIPPort)
 	connClosed := make(chan error, 2)
 	go func() {
 		_, err := io.Copy(server, client)
@@ -1135,7 +1141,7 @@ func (ns *Impl) forwardUDP(client *gonet.UDPConn, clientAddr, dstAddr netip.Addr
 		ns.logf("could not get backend local IP:port from %v:%v", backendLocalAddr.IP, backendLocalAddr.Port)
 	}
 	if isLocal {
-		ns.e.RegisterIPPortIdentity(backendLocalIPPort, dstAddr.Addr())
+		ns.pm.RegisterIPPortIdentity(backendLocalIPPort, dstAddr.Addr())
 	}
 	ctx, cancel := context.WithCancel(context.Background())
 
@@ -1151,7 +1157,7 @@ func (ns *Impl) forwardUDP(client *gonet.UDPConn, clientAddr, dstAddr netip.Addr
 	}
 	timer := time.AfterFunc(idleTimeout, func() {
 		if isLocal {
-			ns.e.UnregisterIPPortIdentity(backendLocalIPPort)
+			ns.pm.UnregisterIPPortIdentity(backendLocalIPPort)
 		}
 		ns.logf("netstack: UDP session between %s and %s timed out", backendListenAddr, backendRemoteAddr)
 		cancel()

+ 2 - 2
wgengine/netstack/netstack_test.go

@@ -53,7 +53,7 @@ func TestInjectInboundLeak(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	ns, err := Create(logf, tunWrap, eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get())
+	ns, err := Create(logf, tunWrap, eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get(), sys.ProxyMapper())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -102,7 +102,7 @@ func makeNetstack(t *testing.T, config func(*Impl)) *Impl {
 	t.Cleanup(func() { eng.Close() })
 	sys.Set(eng)
 
-	ns, err := Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get())
+	ns, err := Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get(), sys.ProxyMapper())
 	if err != nil {
 		t.Fatal(err)
 	}

+ 0 - 45
wgengine/userspace.go

@@ -133,7 +133,6 @@ type userspaceEngine struct {
 	peerSequence   []key.NodePublic
 	endpoints      []tailcfg.Endpoint
 	pendOpen       map[flowtrack.Tuple]*pendingOpenFlow // see pendopen.go
-	tsIPByIPPort   map[netip.AddrPort]netip.Addr        // allows registration of IP:ports as belonging to a certain Tailscale IP for whois lookups
 
 	// pongCallback is the map of response handlers waiting for disco or TSMP
 	// pong callbacks. The map key is a random slice of bytes.
@@ -1341,50 +1340,6 @@ func (e *userspaceEngine) setICMPEchoResponseCallback(idSeq uint32, cb func()) {
 	}
 }
 
-func (e *userspaceEngine) RegisterIPPortIdentity(ipport netip.AddrPort, tsIP netip.Addr) {
-	e.mu.Lock()
-	defer e.mu.Unlock()
-	if e.tsIPByIPPort == nil {
-		e.tsIPByIPPort = make(map[netip.AddrPort]netip.Addr)
-	}
-	e.tsIPByIPPort[ipport] = tsIP
-}
-
-func (e *userspaceEngine) UnregisterIPPortIdentity(ipport netip.AddrPort) {
-	e.mu.Lock()
-	defer e.mu.Unlock()
-	if e.tsIPByIPPort == nil {
-		return
-	}
-	delete(e.tsIPByIPPort, ipport)
-}
-
-var whoIsSleeps = [...]time.Duration{
-	0,
-	10 * time.Millisecond,
-	20 * time.Millisecond,
-	50 * time.Millisecond,
-	100 * time.Millisecond,
-}
-
-func (e *userspaceEngine) WhoIsIPPort(ipport netip.AddrPort) (tsIP netip.Addr, ok bool) {
-	// We currently have a registration race,
-	// https://github.com/tailscale/tailscale/issues/1616,
-	// so loop a few times for now waiting for the registration
-	// to appear.
-	// TODO(bradfitz,namansood): remove this once #1616 is fixed.
-	for _, d := range whoIsSleeps {
-		time.Sleep(d)
-		e.mu.Lock()
-		tsIP, ok = e.tsIPByIPPort[ipport]
-		e.mu.Unlock()
-		if ok {
-			return tsIP, true
-		}
-	}
-	return tsIP, false
-}
-
 // PeerForIP returns the Node in the wireguard config
 // that's responsible for handling the given IP address.
 //

+ 0 - 10
wgengine/watchdog.go

@@ -142,16 +142,6 @@ func (e *watchdogEngine) SetNetworkMap(nm *netmap.NetworkMap) {
 func (e *watchdogEngine) Ping(ip netip.Addr, pingType tailcfg.PingType, size int, cb func(*ipnstate.PingResult)) {
 	e.watchdog("Ping", func() { e.wrap.Ping(ip, pingType, size, cb) })
 }
-func (e *watchdogEngine) RegisterIPPortIdentity(ipp netip.AddrPort, tsIP netip.Addr) {
-	e.watchdog("RegisterIPPortIdentity", func() { e.wrap.RegisterIPPortIdentity(ipp, tsIP) })
-}
-func (e *watchdogEngine) UnregisterIPPortIdentity(ipp netip.AddrPort) {
-	e.watchdog("UnregisterIPPortIdentity", func() { e.wrap.UnregisterIPPortIdentity(ipp) })
-}
-func (e *watchdogEngine) WhoIsIPPort(ipp netip.AddrPort) (tsIP netip.Addr, ok bool) {
-	e.watchdog("UnregisterIPPortIdentity", func() { tsIP, ok = e.wrap.WhoIsIPPort(ipp) })
-	return tsIP, ok
-}
 func (e *watchdogEngine) Close() {
 	e.watchdog("Close", e.wrap.Close)
 }

+ 0 - 15
wgengine/wgengine.go

@@ -111,21 +111,6 @@ type Engine interface {
 	// If size is zero too small, it is ignored. See tailscale.PingOpts for details.
 	Ping(ip netip.Addr, pingType tailcfg.PingType, size int, cb func(*ipnstate.PingResult))
 
-	// RegisterIPPortIdentity registers a given node (identified by its
-	// Tailscale IP) as temporarily having the given IP:port for whois lookups.
-	// The IP:port is generally a localhost IP and an ephemeral port, used
-	// while proxying connections to localhost when tailscaled is running
-	// in netstack mode.
-	RegisterIPPortIdentity(netip.AddrPort, netip.Addr)
-
-	// UnregisterIPPortIdentity removes a temporary IP:port registration
-	// made previously by RegisterIPPortIdentity.
-	UnregisterIPPortIdentity(netip.AddrPort)
-
-	// WhoIsIPPort looks up an IP:port in the temporary registrations,
-	// and returns a matching Tailscale IP, if it exists.
-	WhoIsIPPort(netip.AddrPort) (netip.Addr, bool)
-
 	// InstallCaptureHook registers a function to be called to capture
 	// packets traversing the data path. The hook can be uninstalled by
 	// calling this function with a nil value.