From 3d37328af60b50a335b7c470dcb33035afffe356 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 13 Sep 2023 11:38:05 -0700 Subject: [PATCH] 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 --- cmd/tailscaled/depaware.txt | 1 + cmd/tailscaled/tailscaled.go | 9 +++- cmd/tsconnect/wasm/wasm_js.go | 2 +- ipn/ipnlocal/local.go | 2 +- proxymap/proxymap.go | 72 ++++++++++++++++++++++++++++++ tsd/tsd.go | 10 ++++- tsnet/tsnet.go | 2 +- wgengine/netstack/netstack.go | 16 ++++--- wgengine/netstack/netstack_test.go | 4 +- wgengine/userspace.go | 45 ------------------- wgengine/watchdog.go | 10 ----- wgengine/wgengine.go | 15 ------- 12 files changed, 106 insertions(+), 82 deletions(-) create mode 100644 proxymap/proxymap.go diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index f56dbf600..8e2cebf21 100644 --- a/cmd/tailscaled/depaware.txt +++ b/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 diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index a585e5dc6..6b6ba5324 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/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 diff --git a/cmd/tsconnect/wasm/wasm_js.go b/cmd/tsconnect/wasm/wasm_js.go index b58242c42..9556fe043 100644 --- a/cmd/tsconnect/wasm/wasm_js.go +++ b/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) } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index a413e00bf..e4ccc6235 100644 --- a/ipn/ipnlocal/local.go +++ b/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 diff --git a/proxymap/proxymap.go b/proxymap/proxymap.go new file mode 100644 index 000000000..a1c1bb898 --- /dev/null +++ b/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 +} diff --git a/tsd/tsd.go b/tsd/tsd.go index ffef20222..2a233e51d 100644 --- a/tsd/tsd.go +++ b/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 diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index d92b4ede3..f49f2bd93 100644 --- a/tsnet/tsnet.go +++ b/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) } diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index 8e6e8cf55..d0fe23169 100644 --- a/wgengine/netstack/netstack.go +++ b/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() diff --git a/wgengine/netstack/netstack_test.go b/wgengine/netstack/netstack_test.go index f08308e2d..c1948dec0 100644 --- a/wgengine/netstack/netstack_test.go +++ b/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) } diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 8de1cfca2..8f59b2faf 100644 --- a/wgengine/userspace.go +++ b/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. // diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index e1aa222eb..75bc1c0ea 100644 --- a/wgengine/watchdog.go +++ b/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) } diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index f9f113390..e21987f93 100644 --- a/wgengine/wgengine.go +++ b/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.