From 3bd382f3693a63d16be2c73ba5880b4baef5c274 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 15 Nov 2023 15:10:45 -0800 Subject: [PATCH] wgengine/magicsock: add DERP homeless debug mode for testing In DERP homeless mode, a DERP home connection is not sought or maintained and the local node is not reachable. Updates #3363 Updates tailscale/corp#396 Change-Id: Ibc30488ac2e3cfe4810733b96c2c9f10a51b8331 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/debug.go | 10 ++++++++++ ipn/ipnlocal/local.go | 27 ++++++++++++++------------- ipn/ipnlocal/peerapi.go | 2 +- ipn/localapi/localapi.go | 4 ++++ wgengine/magicsock/derp.go | 4 ++++ wgengine/magicsock/magicsock.go | 21 ++++++++++++++++++++- 6 files changed, 53 insertions(+), 15 deletions(-) diff --git a/cmd/tailscale/cli/debug.go b/cmd/tailscale/cli/debug.go index e7988cbd4..5ecb3c7bd 100644 --- a/cmd/tailscale/cli/debug.go +++ b/cmd/tailscale/cli/debug.go @@ -129,6 +129,16 @@ var debugCmd = &ffcli.Command{ Exec: localAPIAction("rebind"), ShortHelp: "force a magicsock rebind", }, + { + Name: "derp-set-homeless", + Exec: localAPIAction("derp-set-homeless"), + ShortHelp: "enable DERP homeless mode (breaks reachablility)", + }, + { + Name: "derp-unset-homeless", + Exec: localAPIAction("derp-unset-homeless"), + ShortHelp: "disable DERP homeless mode", + }, { Name: "break-tcp-conns", Exec: localAPIAction("break-tcp-conns"), diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index bfeb314d8..c1f516533 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -449,7 +449,7 @@ func (b *LocalBackend) SetComponentDebugLogging(component string, until time.Tim var setEnabled func(bool) switch component { case "magicsock": - setEnabled = b.magicConn().SetDebugLoggingEnabled + setEnabled = b.MagicConn().SetDebugLoggingEnabled case "sockstats": if b.sockstatLogger != nil { setEnabled = func(v bool) { @@ -1181,7 +1181,7 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control } b.e.SetNetworkMap(st.NetMap) - b.magicConn().SetDERPMap(st.NetMap.DERPMap) + b.MagicConn().SetDERPMap(st.NetMap.DERPMap) // Update our cached DERP map dnsfallback.UpdateCache(st.NetMap.DERPMap, b.logf) @@ -1204,7 +1204,7 @@ var _ controlclient.NetmapDeltaUpdater = (*LocalBackend)(nil) // UpdateNetmapDelta implements controlclient.NetmapDeltaUpdater. func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bool) { - if !b.magicConn().UpdateNetmapDelta(muts) { + if !b.MagicConn().UpdateNetmapDelta(muts) { return false } @@ -1624,7 +1624,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error { }) } - discoPublic := b.magicConn().DiscoPublicKey() + discoPublic := b.MagicConn().DiscoPublicKey() var err error @@ -1703,7 +1703,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error { } cc.SetTKAHead(tkaHead) - b.magicConn().SetNetInfoCallback(b.setNetInfo) + b.MagicConn().SetNetInfoCallback(b.setNetInfo) blid := b.backendLogID.String() b.logf("Backend: logs: be:%v fe:%v", blid, opts.FrontendLogID) @@ -2241,7 +2241,7 @@ func (b *LocalBackend) DebugForceNetmapUpdate() { nm := b.netMap b.e.SetNetworkMap(nm) if nm != nil { - b.magicConn().SetDERPMap(nm.DERPMap) + b.MagicConn().SetDERPMap(nm.DERPMap) } b.setNetMapLocked(nm) } @@ -3071,7 +3071,7 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) ipn } if netMap != nil { - b.magicConn().SetDERPMap(netMap.DERPMap) + b.MagicConn().SetDERPMap(netMap.DERPMap) } if !oldp.WantRunning() && newp.WantRunning { @@ -4378,7 +4378,7 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) { } b.capFileSharing = fs - b.magicConn().SetSilentDisco(b.ControlKnobs().SilentDisco.Load()) + b.MagicConn().SetSilentDisco(b.ControlKnobs().SilentDisco.Load()) b.setDebugLogsByCapabilityLocked(nm) @@ -5073,12 +5073,12 @@ func peerCanProxyDNS(p tailcfg.NodeView) bool { } func (b *LocalBackend) DebugRebind() error { - b.magicConn().Rebind() + b.MagicConn().Rebind() return nil } func (b *LocalBackend) DebugReSTUN() error { - b.magicConn().ReSTUN("explicit-debug") + b.MagicConn().ReSTUN("explicit-debug") return nil } @@ -5087,7 +5087,8 @@ func (b *LocalBackend) ControlKnobs() *controlknobs.Knobs { return b.sys.ControlKnobs() } -func (b *LocalBackend) magicConn() *magicsock.Conn { +// MagicConn returns the backend's *magicsock.Conn. +func (b *LocalBackend) MagicConn() *magicsock.Conn { return b.sys.MagicSock.Get() } @@ -5532,7 +5533,7 @@ func (b *LocalBackend) GetPeerEndpointChanges(ctx context.Context, ip netip.Addr } peer := pip.Node - chs, err := b.magicConn().GetEndpointChanges(peer) + chs, err := b.MagicConn().GetEndpointChanges(peer) if err != nil { return nil, fmt.Errorf("getting endpoint changes: %w", err) } @@ -5549,7 +5550,7 @@ func (b *LocalBackend) DebugBreakTCPConns() error { } func (b *LocalBackend) DebugBreakDERPConns() error { - return b.magicConn().DebugBreakDERPConns() + return b.MagicConn().DebugBreakDERPConns() } func (b *LocalBackend) pushSelfUpdateProgress(up ipnstate.UpdateProgress) { diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index d7bd88fff..d1d511462 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -778,7 +778,7 @@ func (h *peerAPIHandler) handleServeMagicsock(w http.ResponseWriter, r *http.Req http.Error(w, "denied; no debug access", http.StatusForbidden) return } - h.ps.b.magicConn().ServeHTTPDebug(w, r) + h.ps.b.MagicConn().ServeHTTPDebug(w, r) } func (h *peerAPIHandler) handleServeMetrics(w http.ResponseWriter, r *http.Request) { diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index b44cf8d4c..f4bcc4143 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -564,6 +564,10 @@ func (h *Handler) serveDebug(w http.ResponseWriter, r *http.Request) { } var err error switch action { + case "derp-set-homeless": + h.b.MagicConn().SetHomeless(true) + case "derp-unset-homeless": + h.b.MagicConn().SetHomeless(false) case "rebind": err = h.b.DebugRebind() case "restun": diff --git a/wgengine/magicsock/derp.go b/wgengine/magicsock/derp.go index fa23d1d55..1f03a9039 100644 --- a/wgengine/magicsock/derp.go +++ b/wgengine/magicsock/derp.go @@ -144,6 +144,10 @@ func (c *Conn) setNearestDERP(derpNum int) (wantDERP bool) { health.SetMagicSockDERPHome(0) return false } + if c.homeless { + c.myDerp = 0 + return false + } if derpNum == c.myDerp { // No change. return true diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 2bf807973..1387f935d 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -270,6 +270,7 @@ type Conn struct { privateKey key.NodePrivate // WireGuard private key for this node everHadKey bool // whether we ever had a non-zero private key myDerp int // nearest DERP region ID; 0 means none/unknown + homeless bool // if true, don't try to find & stay conneted to a DERP home (myDerp will stay 0) derpStarted chan struct{} // closed on first connection to DERP; for tests & cleaner Close activeDerp map[int]activeDerp // DERP regionID -> connection to a node in that region prevDerp map[int]*syncs.WaitGroupChan @@ -2202,7 +2203,7 @@ func (c *Conn) goroutinesRunningLocked() bool { } func (c *Conn) shouldDoPeriodicReSTUNLocked() bool { - if c.networkDown() { + if c.networkDown() || c.homeless { return false } if len(c.peerSet) == 0 || c.privateKey.IsZero() { @@ -2686,6 +2687,24 @@ func (c *Conn) SetStatistics(stats *connstats.Statistics) { c.stats.Store(stats) } +// SetHomeless sets whether magicsock should idle harder and not have a DERP +// home connection active and not search for its nearest DERP home. In this +// homeless mode, the node is unreachable by others. +func (c *Conn) SetHomeless(v bool) { + c.mu.Lock() + defer c.mu.Unlock() + c.homeless = v + + if v && c.myDerp != 0 { + oldHome := c.myDerp + c.myDerp = 0 + c.closeDerpLocked(oldHome, "set-homeless") + } + if !v { + go c.updateEndpoints("set-homeless-disabled") + } +} + const ( // sessionActiveTimeout is how long since the last activity we // try to keep an established endpoint peering alive.