From 41e4e02e57736599603ab92ef77de6a763f5985e Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 29 Mar 2021 15:17:05 -0700 Subject: [PATCH] net/{packet,tstun}: send peerapi port in TSMP pongs For discovery when an explicit hostname/IP is known. We'll still also send it via control for finding peers by a list. Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/ping.go | 6 +++++- cmd/tailscaled/tailscaled.go | 5 ++++- ipn/ipnlocal/local.go | 27 +++++++++++++++++++++++++-- ipn/ipnstate/ipnstate.go | 5 +++++ net/packet/tsmp.go | 19 +++++++++++++------ net/tstun/wrap.go | 9 ++++++++- wgengine/userspace.go | 25 +++++++++++++------------ wgengine/watchdog.go | 8 ++++++++ 8 files changed, 81 insertions(+), 23 deletions(-) diff --git a/cmd/tailscale/cli/ping.go b/cmd/tailscale/cli/ping.go index c82eb7902..4d2a6f502 100644 --- a/cmd/tailscale/cli/ping.go +++ b/cmd/tailscale/cli/ping.go @@ -143,7 +143,11 @@ func runPing(ctx context.Context, args []string) error { via = "TSMP" } anyPong = true - fmt.Printf("pong from %s (%s) via %v in %v\n", pr.NodeName, pr.NodeIP, via, latency) + extra := "" + if pr.PeerAPIPort != 0 { + extra = fmt.Sprintf(", %d", pr.PeerAPIPort) + } + fmt.Printf("pong from %s (%s%s) via %v in %v\n", pr.NodeName, pr.NodeIP, extra, via, latency) if pingArgs.tsmp { return nil } diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 931072b55..2dfe53cfc 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -229,7 +229,10 @@ func run() error { var ns *netstack.Impl if useNetstack { - tunDev, magicConn := e.(wgengine.InternalsGetter).GetInternals() + tunDev, magicConn, ok := e.(wgengine.InternalsGetter).GetInternals() + if !ok { + log.Fatalf("%T is not a wgengine.InternalsGetter", e) + } ns, err = netstack.Create(logf, tunDev, e, magicConn) if err != nil { log.Fatalf("netstack.Create: %v", err) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index f225ecfe4..505d15bb3 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -155,6 +155,17 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, e wge b.unregisterHealthWatch = health.RegisterWatcher(b.onHealthChange) + wiredPeerAPIPort := false + if ig, ok := e.(wgengine.InternalsGetter); ok { + if tunWrap, _, ok := ig.GetInternals(); ok { + tunWrap.PeerAPIPort = b.getPeerAPIPortForTSMPPing + wiredPeerAPIPort = true + } + } + if !wiredPeerAPIPort { + b.logf("[unexpected] failed to wire up peer API port for engine %T", e) + } + return b, nil } @@ -1330,6 +1341,17 @@ func (b *LocalBackend) SetPrefs(newp *ipn.Prefs) { b.send(ipn.Notify{Prefs: newp}) } +func (b *LocalBackend) getPeerAPIPortForTSMPPing(ip netaddr.IP) (port uint16, ok bool) { + b.mu.Lock() + defer b.mu.Unlock() + for _, pln := range b.peerAPIListeners { + if pln.ip.BitLen() == ip.BitLen() { + return uint16(pln.Port()), true + } + } + return 0, false +} + func (b *LocalBackend) peerAPIServicesLocked() (ret []tailcfg.Service) { for _, pln := range b.peerAPIListeners { proto := tailcfg.ServiceProto("peerapi4") @@ -1493,8 +1515,9 @@ func (b *LocalBackend) initPeerAPIListener() { var tunName string if ge, ok := b.e.(wgengine.InternalsGetter); ok { - tunDev, _ := ge.GetInternals() - tunName, _ = tunDev.Name() + if tunWrap, _, ok := ge.GetInternals(); ok { + tunName, _ = tunWrap.Name() + } } ps := &peerAPIServer{ diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index de9d208f7..c781718ae 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -418,6 +418,11 @@ type PingResult struct { // It is not currently set for TSMP pings. DERPRegionCode string + // PeerAPIPort is set by TSMP ping responses for peers that + // are running a peerapi server. This is the port they're + // running the server on. + PeerAPIPort uint16 `json:",omitempty"` + // TODO(bradfitz): details like whether port mapping was used on either side? (Once supported) } diff --git a/net/packet/tsmp.go b/net/packet/tsmp.go index fb257556c..87475d033 100644 --- a/net/packet/tsmp.go +++ b/net/packet/tsmp.go @@ -224,11 +224,14 @@ func (pp *Parsed) AsTSMPPing() (h TSMPPingRequest, ok bool) { } type TSMPPongReply struct { - IPHeader Header - Data [8]byte + IPHeader Header + Data [8]byte + PeerAPIPort uint16 } -func (pp *Parsed) AsTSMPPong() (data [8]byte, ok bool) { +// AsTSMPPong returns pp as a TSMPPongReply and whether it is one. +// The pong.IPHeader field is not populated. +func (pp *Parsed) AsTSMPPong() (pong TSMPPongReply, ok bool) { if pp.IPProto != ipproto.TSMP { return } @@ -236,12 +239,15 @@ func (pp *Parsed) AsTSMPPong() (data [8]byte, ok bool) { if len(p) < 9 || p[0] != byte(TSMPTypePong) { return } - copy(data[:], p[1:]) - return data, true + copy(pong.Data[:], p[1:]) + if len(p) >= 11 { + pong.PeerAPIPort = binary.BigEndian.Uint16(p[9:]) + } + return pong, true } func (h TSMPPongReply) Len() int { - return h.IPHeader.Len() + 9 + return h.IPHeader.Len() + 11 } func (h TSMPPongReply) Marshal(buf []byte) error { @@ -254,5 +260,6 @@ func (h TSMPPongReply) Marshal(buf []byte) error { buf = buf[h.IPHeader.Len():] buf[0] = byte(TSMPTypePong) copy(buf[1:], h.Data[:]) + binary.BigEndian.PutUint16(buf[9:11], h.PeerAPIPort) return nil } diff --git a/net/tstun/wrap.go b/net/tstun/wrap.go index 70225c52e..503438042 100644 --- a/net/tstun/wrap.go +++ b/net/tstun/wrap.go @@ -107,7 +107,11 @@ type Wrapper struct { PostFilterOut FilterFunc // OnTSMPPongReceived, if non-nil, is called whenever a TSMP pong arrives. - OnTSMPPongReceived func(data [8]byte) + OnTSMPPongReceived func(packet.TSMPPongReply) + + // PeerAPIPort, if non-nil, returns the peerapi port that's + // running for the given IP address. + PeerAPIPort func(netaddr.IP) (port uint16, ok bool) // disableFilter disables all filtering when set. This should only be used in tests. disableFilter bool @@ -456,6 +460,9 @@ func (t *Wrapper) injectOutboundPong(pp *packet.Parsed, req packet.TSMPPingReque pong := packet.TSMPPongReply{ Data: req.Data, } + if t.PeerAPIPort != nil { + pong.PeerAPIPort, _ = t.PeerAPIPort(pp.Dst.IP) + } switch pp.IPVersion { case 4: h4 := pp.IP4Header() diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 9202e4c7a..b26483d4c 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -116,19 +116,19 @@ type userspaceEngine struct { pingers map[wgkey.Key]*pinger // legacy pingers for pre-discovery peers pendOpen map[flowtrack.Tuple]*pendingOpenFlow // see pendopen.go networkMapCallbacks map[*someHandle]NetworkMapCallback - tsIPByIPPort map[netaddr.IPPort]netaddr.IP // allows registration of IP:ports as belonging to a certain Tailscale IP for whois lookups - pongCallback map[[8]byte]func() // for TSMP pong responses + tsIPByIPPort map[netaddr.IPPort]netaddr.IP // allows registration of IP:ports as belonging to a certain Tailscale IP for whois lookups + pongCallback map[[8]byte]func(packet.TSMPPongReply) // for TSMP pong responses // Lock ordering: magicsock.Conn.mu, wgLock, then mu. } // InternalsGetter is implemented by Engines that can export their internals. type InternalsGetter interface { - GetInternals() (*tstun.Wrapper, *magicsock.Conn) + GetInternals() (_ *tstun.Wrapper, _ *magicsock.Conn, ok bool) } -func (e *userspaceEngine) GetInternals() (*tstun.Wrapper, *magicsock.Conn) { - return e.tundev, e.magicConn +func (e *userspaceEngine) GetInternals() (_ *tstun.Wrapper, _ *magicsock.Conn, ok bool) { + return e.tundev, e.magicConn, true } // Config is the engine configuration. @@ -310,13 +310,13 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) SkipBindUpdate: true, } - e.tundev.OnTSMPPongReceived = func(data [8]byte) { + e.tundev.OnTSMPPongReceived = func(pong packet.TSMPPongReply) { e.mu.Lock() defer e.mu.Unlock() - cb := e.pongCallback[data] - e.logf("wgengine: got TSMP pong %02x; cb=%v", data, cb != nil) + cb := e.pongCallback[pong.Data] + e.logf("wgengine: got TSMP pong %02x, peerAPIPort=%v; cb=%v", pong.Data, pong.PeerAPIPort, cb != nil) if cb != nil { - go cb() + go cb(pong) } } @@ -1389,12 +1389,13 @@ func (e *userspaceEngine) sendTSMPPing(ip netaddr.IP, peer *tailcfg.Node, res *i e.setTSMPPongCallback(data, nil) }) t0 := time.Now() - e.setTSMPPongCallback(data, func() { + e.setTSMPPongCallback(data, func(pong packet.TSMPPongReply) { expireTimer.Stop() d := time.Since(t0) res.LatencySeconds = d.Seconds() res.NodeIP = ip.String() res.NodeName = peer.ComputedName + res.PeerAPIPort = pong.PeerAPIPort cb(res) }) @@ -1406,11 +1407,11 @@ func (e *userspaceEngine) sendTSMPPing(ip netaddr.IP, peer *tailcfg.Node, res *i e.tundev.InjectOutbound(tsmpPing) } -func (e *userspaceEngine) setTSMPPongCallback(data [8]byte, cb func()) { +func (e *userspaceEngine) setTSMPPongCallback(data [8]byte, cb func(packet.TSMPPongReply)) { e.mu.Lock() defer e.mu.Unlock() if e.pongCallback == nil { - e.pongCallback = map[[8]byte]func(){} + e.pongCallback = map[[8]byte]func(packet.TSMPPongReply){} } if cb == nil { delete(e.pongCallback, data) diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index f3248d6fc..460e8fffe 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -15,9 +15,11 @@ import ( "inet.af/netaddr" "tailscale.com/ipn/ipnstate" "tailscale.com/net/dns" + "tailscale.com/net/tstun" "tailscale.com/tailcfg" "tailscale.com/types/netmap" "tailscale.com/wgengine/filter" + "tailscale.com/wgengine/magicsock" "tailscale.com/wgengine/monitor" "tailscale.com/wgengine/router" "tailscale.com/wgengine/wgcfg" @@ -133,6 +135,12 @@ func (e *watchdogEngine) WhoIsIPPort(ipp netaddr.IPPort) (tsIP netaddr.IP, ok bo func (e *watchdogEngine) Close() { e.watchdog("Close", e.wrap.Close) } +func (e *watchdogEngine) GetInternals() (tw *tstun.Wrapper, c *magicsock.Conn, ok bool) { + if ig, ok := e.wrap.(InternalsGetter); ok { + return ig.GetInternals() + } + return +} func (e *watchdogEngine) Wait() { e.wrap.Wait() }