diff --git a/control/controlknobs/controlknobs.go b/control/controlknobs/controlknobs.go index bdbe5bdd2..7315a10f7 100644 --- a/control/controlknobs/controlknobs.go +++ b/control/controlknobs/controlknobs.go @@ -95,6 +95,10 @@ type Knobs struct { // We began creating this rule on 2024-06-14, and this knob // allows us to disable the new behavior remotely if needed. DisableLocalDNSOverrideViaNRPT atomic.Bool + + // DisableCryptorouting indicates that the node should not use the + // magicsock crypto routing feature. + DisableCryptorouting atomic.Bool } // UpdateFromNodeAttributes updates k (if non-nil) based on the provided self @@ -122,6 +126,7 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) { userDialUseRoutes = has(tailcfg.NodeAttrUserDialUseRoutes) disableSplitDNSWhenNoCustomResolvers = has(tailcfg.NodeAttrDisableSplitDNSWhenNoCustomResolvers) disableLocalDNSOverrideViaNRPT = has(tailcfg.NodeAttrDisableLocalDNSOverrideViaNRPT) + disableCryptorouting = has(tailcfg.NodeAttrDisableMagicSockCryptoRouting) ) if has(tailcfg.NodeAttrOneCGNATEnable) { @@ -147,6 +152,7 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) { k.UserDialUseRoutes.Store(userDialUseRoutes) k.DisableSplitDNSWhenNoCustomResolvers.Store(disableSplitDNSWhenNoCustomResolvers) k.DisableLocalDNSOverrideViaNRPT.Store(disableLocalDNSOverrideViaNRPT) + k.DisableCryptorouting.Store(disableCryptorouting) } // AsDebugJSON returns k as something that can be marshalled with json.Marshal @@ -173,5 +179,6 @@ func (k *Knobs) AsDebugJSON() map[string]any { "UserDialUseRoutes": k.UserDialUseRoutes.Load(), "DisableSplitDNSWhenNoCustomResolvers": k.DisableSplitDNSWhenNoCustomResolvers.Load(), "DisableLocalDNSOverrideViaNRPT": k.DisableLocalDNSOverrideViaNRPT.Load(), + "DisableCryptorouting": k.DisableCryptorouting.Load(), } } diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index bf2fc0153..60a2244dd 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -144,7 +144,8 @@ type CapabilityVersion int // - 99: 2024-06-14: Client understands NodeAttrDisableLocalDNSOverrideViaNRPT // - 100: 2024-06-18: Client supports filtertype.Match.SrcCaps (issue #12542) // - 101: 2024-07-01: Client supports SSH agent forwarding when handling connections with /bin/su -const CurrentCapabilityVersion CapabilityVersion = 101 +// - 102: 2024-07-12: NodeAttrDisableMagicSockCryptoRouting support +const CurrentCapabilityVersion CapabilityVersion = 102 type StableID string @@ -2322,6 +2323,10 @@ const ( // We began creating this rule on 2024-06-14, and this node attribute // allows us to disable the new behavior remotely if needed. NodeAttrDisableLocalDNSOverrideViaNRPT NodeCapability = "disable-local-dns-override-via-nrpt" + + // NodeAttrDisableMagicSockCryptoRouting disables the use of the + // magicsock cryptorouting hook. See tailscale/corp#20732. + NodeAttrDisableMagicSockCryptoRouting NodeCapability = "disable-magicsock-crypto-routing" ) // SetDNSRequest is a request to add a DNS record. diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 34c0da742..66264de32 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -1276,7 +1276,8 @@ func (c *Conn) mkReceiveFunc(ruc *RebindingUDPConn, healthItem *health.ReceiveFu // // ok is whether this read should be reported up to wireguard-go (our // caller). -func (c *Conn) receiveIP(b []byte, ipp netip.AddrPort, cache *ippEndpointCache) (ep *endpoint, ok bool) { +func (c *Conn) receiveIP(b []byte, ipp netip.AddrPort, cache *ippEndpointCache) (_ conn.Endpoint, ok bool) { + var ep *endpoint if stun.Is(b) { c.netChecker.ReceiveSTUNPacket(b, ipp) return nil, false @@ -1297,7 +1298,10 @@ func (c *Conn) receiveIP(b []byte, ipp netip.AddrPort, cache *ippEndpointCache) de, ok := c.peerMap.endpointForIPPort(ipp) c.mu.Unlock() if !ok { - return nil, false + if c.controlKnobs != nil && c.controlKnobs.DisableCryptorouting.Load() { + return nil, false + } + return &lazyEndpoint{c: c, src: ipp}, true } cache.ipp = ipp cache.de = de @@ -3114,3 +3118,35 @@ func (c *Conn) GetLastNetcheckReport(ctx context.Context) *netcheck.Report { func (c *Conn) SetLastNetcheckReportForTest(ctx context.Context, report *netcheck.Report) { c.lastNetCheckReport.Store(report) } + +// lazyEndpoint is a wireguard conn.Endpoint for when magicsock received a +// non-disco (presumably WireGuard) packet from a UDP address from which we +// can't map to a Tailscale peer. But Wireguard most likely can, once it +// decrypts it. So we implement the conn.PeerAwareEndpoint interface +// from https://github.com/tailscale/wireguard-go/pull/27 to allow WireGuard +// to tell us who it is later and get the correct conn.Endpoint. +type lazyEndpoint struct { + c *Conn + src netip.AddrPort +} + +var _ conn.PeerAwareEndpoint = (*lazyEndpoint)(nil) +var _ conn.Endpoint = (*lazyEndpoint)(nil) + +func (le *lazyEndpoint) ClearSrc() {} +func (le *lazyEndpoint) SrcIP() netip.Addr { return le.src.Addr() } +func (le *lazyEndpoint) DstIP() netip.Addr { return netip.Addr{} } +func (le *lazyEndpoint) SrcToString() string { return le.src.String() } +func (le *lazyEndpoint) DstToString() string { return "dst" } +func (le *lazyEndpoint) DstToBytes() []byte { return nil } +func (le *lazyEndpoint) GetPeerEndpoint(peerPublicKey [32]byte) conn.Endpoint { + pubKey := key.NodePublicFromRaw32(mem.B(peerPublicKey[:])) + le.c.mu.Lock() + defer le.c.mu.Unlock() + ep, ok := le.c.peerMap.endpointForNodeKey(pubKey) + if !ok { + return nil + } + le.c.logf("magicsock: lazyEndpoint.GetPeerEndpoint(%v) found: %v", pubKey.ShortString(), ep.nodeAddr) + return ep +}