diff --git a/net/tstun/wrap.go b/net/tstun/wrap.go index ec7bc94ea..dbaabdc0b 100644 --- a/net/tstun/wrap.go +++ b/net/tstun/wrap.go @@ -1154,6 +1154,26 @@ func (t *Wrapper) injectedRead(res tunInjectedRead, outBuffs [][]byte, sizes []i } invertGSOChecksum(pkt, gso) + // Check if this is a packet for conn25-style app connectors, + // and perform the necessary NAT. The main case that requires + // NAT from netstack toward WireGuard is an SNAT on return traffic + // from the target application on the internet, translating + // the original server's source IP to the TransitIP. + // The hook can also perform DNAT for client-originated traffic, + // translating the destination MagicIP to a TransitIP, and rejects + // MagicIPs that have not been approved for the client. + // + // Normal non-connector traffic is forwarded unmodified. + // + // Cross-tailnet conn25 app connector connections are not supported, + // so at most one of this hook and the following pc.snat should modify the packet. + if t.PreFilterPacketOutboundToWireGuardAppConnectorIntercept != nil { + if r := t.PreFilterPacketOutboundToWireGuardAppConnectorIntercept(p, t); r.IsDrop() { + metricPacketOut.Add(1) + metricPacketOutDrop.Add(1) + return 0, nil + } + } pc.snat(p) invertGSOChecksum(pkt, gso) diff --git a/net/tstun/wrap_test.go b/net/tstun/wrap_test.go index 644bb9f53..631dc083e 100644 --- a/net/tstun/wrap_test.go +++ b/net/tstun/wrap_test.go @@ -28,6 +28,7 @@ "tailscale.com/disco" "tailscale.com/net/netaddr" "tailscale.com/net/packet" + "tailscale.com/net/packet/checksum" "tailscale.com/tstest" "tailscale.com/tstime/mono" "tailscale.com/types/ipproto" @@ -1120,3 +1121,36 @@ func TestInterceptOrdering(t *testing.T) { t.Errorf("got number of intercepts run in Read(): %d; want: %d", seq, numOutboundIntercepts) } } + +func TestInjectedReadCallsAppConnectorHook(t *testing.T) { + var called bool + hook := func(p *packet.Parsed, _ *Wrapper) filter.Response { + called = true + checksum.UpdateSrcAddr(p, netip.MustParseAddr("169.254.0.1")) + return filter.Accept + } + + bus := eventbustest.NewBus(t) + _, tun := newFakeTUN(t.Logf, bus, false) + tun.PreFilterPacketOutboundToWireGuardAppConnectorIntercept = hook + tun.Start() + defer tun.Close() + + if err := tun.InjectOutbound(udp4("145.53.32.10", "100.25.63.57", 80, 12345)); err != nil { + t.Fatalf("InjectOutbound error: %v", err) + } + + var buf [MaxPacketSize]byte + sizes := make([]int, 1) + tun.Read([][]byte{buf[:]}, sizes, 0) + + if !called { + t.Error("app connector hook was not called in InjectOutbound") + } + + wantPkt := udp4("169.254.0.1", "100.25.63.57", 80, 12345) + gotPkt := buf[:sizes[0]] + if !bytes.Equal(wantPkt, gotPkt) { + t.Errorf("packet mismatch\nwant:\t% x\ngot:\t% x", wantPkt, gotPkt) + } +}