wgengine/netstack: deliver self-addressed packets via loopback

When a tsnet.Server dials its own Tailscale IP, TCP SYN packets are
silently dropped. In inject(), outbound packets with dst=self fail the
shouldSendToHost check and fall through to WireGuard, which has no peer
for the node's own address.

Fix this by detecting self-addressed packets in inject() using isLocalIP
and delivering them back into gVisor's network stack as inbound packets
via a new DeliverLoopback method on linkEndpoint. The outbound packet
must be re-serialized into a new PacketBuffer because outbound packets
have their headers parsed into separate views, but DeliverNetworkPacket
expects raw unparsed data.

Updates #18829

Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
James Tucker
2026-02-27 13:49:05 -08:00
committed by James Tucker
parent 30e12310f1
commit 0fb207c3d0
4 changed files with 428 additions and 0 deletions
+24
View File
@@ -1037,6 +1037,16 @@ func (ns *Impl) inject() {
return
}
} else {
// Self-addressed packet: deliver back into gVisor directly
// via the link endpoint's dispatcher, but only if the packet is not
// earmarked for the host. Neither the inbound path (fakeTUN Write is a
// no-op) nor the outbound path (WireGuard has no peer for our own IP)
// can handle these.
if ns.isSelfDst(pkt) {
ns.linkEP.DeliverLoopback(pkt)
continue
}
if err := ns.tundev.InjectOutboundPacketBuffer(pkt); err != nil {
ns.logf("netstack inject outbound: %v", err)
return
@@ -1116,6 +1126,20 @@ func (ns *Impl) shouldSendToHost(pkt *stack.PacketBuffer) bool {
return false
}
// isSelfDst reports whether pkt's destination IP is a local Tailscale IP
// assigned to this node. This is used by inject() to detect self-addressed
// packets that need loopback delivery.
func (ns *Impl) isSelfDst(pkt *stack.PacketBuffer) bool {
hdr := pkt.Network()
switch v := hdr.(type) {
case header.IPv4:
return ns.isLocalIP(netip.AddrFrom4(v.DestinationAddress().As4()))
case header.IPv6:
return ns.isLocalIP(netip.AddrFrom16(v.DestinationAddress().As16()))
}
return false
}
// isLocalIP reports whether ip is a Tailscale IP assigned to this
// node directly (but not a subnet-routed IP).
func (ns *Impl) isLocalIP(ip netip.Addr) bool {