fix(ipnlocal): allow Funnel ingress from unsigned peer relays

Upstream 0eb38dc2e denies peer capabilities to any peer with
UnsignedPeerAPIOnly set. Funnel ingress relays are precisely that: per the
docs on tailcfg.Node.UnsignedPeerAPIOnly they get no network access and exist
only to reach this node's peerapi. Since canIngress() resolves
PeerCapabilityIngress through the capability map, the relay can no longer
reach the one endpoint it is allowed to use, and Funnel connections are
refused after the client's ClientHello.

Split peerCapsLocked so the unsigned-peer denial can be skipped for the
ingress path alone. Every other caller keeps upstream's stricter behaviour.

Fork-local patch, tracked in webnet/tailscale#16 for reverting once upstream
restores Funnel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 20:10:02 +00:00
co-authored by Claude
parent cf52316095
commit 15a70243ed
3 changed files with 36 additions and 1 deletions
+22
View File
@@ -432,10 +432,32 @@ func (nb *nodeBackend) srcIsUnsignedPeerLocked(src netip.Addr) bool {
return ok && n.UnsignedPeerAPIOnly()
}
// PeerCapsIncludingUnsigned is like [nodeBackend.PeerCaps] but does not deny
// capabilities to peers with UnsignedPeerAPIOnly set.
//
// Funnel ingress relays are delivered as UnsignedPeerAPIOnly nodes: per the
// docs on [tailcfg.Node.UnsignedPeerAPIOnly] they get no network access at all
// and exist solely to reach this node's peerapi. The ingress endpoint they need
// is gated on [tailcfg.PeerCapabilityIngress], so denying them capabilities
// wholesale — as peerCapsLocked does upstream as of 0eb38dc2e — makes Funnel
// impossible. Callers must therefore be limited to the ingress path.
//
// This is a fork-local patch; drop it once upstream restores Funnel.
// See webnet/tailscale#16.
func (nb *nodeBackend) PeerCapsIncludingUnsigned(src netip.Addr) tailcfg.PeerCapMap {
nb.mu.Lock()
defer nb.mu.Unlock()
return nb.peerCapsIgnoringSignatureLocked(src)
}
func (nb *nodeBackend) peerCapsLocked(src netip.Addr) tailcfg.PeerCapMap {
if nb.srcIsUnsignedPeerLocked(src) {
return nil
}
return nb.peerCapsIgnoringSignatureLocked(src)
}
func (nb *nodeBackend) peerCapsIgnoringSignatureLocked(src netip.Addr) tailcfg.PeerCapMap {
if nb.netMap == nil {
return nil
}