diff --git a/internal/web/service/xray.go b/internal/web/service/xray.go index d0099dee1..5d22629fa 100644 --- a/internal/web/service/xray.go +++ b/internal/web/service/xray.go @@ -645,6 +645,19 @@ const amneziawgEgressDokodemoSettings = `{"allowedNetwork":"tcp,udp","followRedi // socket. const amneziawgEgressStreamSettings = `{"sockopt":{"tproxy":"tproxy"}}` +// amneziawgEgressSniffingSettings enables sniffing on the bridge, matching +// this fork's own normal per-inbound default (see default.json's "mixed" +// inbound). Without this, domain-based Routing rules can never match a +// single byte of RouteThroughXray traffic: an AmneziaWG peer resolves DNS +// itself, through the tunnel, before ever sending a packet — by the time +// TPROXY hands the decapsulated traffic to this bridge, the destination is +// already a bare IP, with no domain name attached at the network layer at +// all. Sniffing recovers it from the payload itself (TLS SNI / HTTP Host / +// QUIC) the same way it already does for every other inbound; without it, +// only tag/IP/network-based rules can ever match this bridge's traffic, +// and any domain rule above it in the list is silently unreachable. +const amneziawgEgressSniffingSettings = `{"enabled":true,"destOverride":["http","tls","quic","fakedns"]}` + // injectAmneziawgEgress gives every enabled, RouteThroughXray-opted-in // AmneziaWG inbound with at least one qualifying peer its own loopback // dokodemo-door bridge — tagged with that inbound's own real tag, so it's @@ -707,6 +720,7 @@ func injectAmneziawgEgress(cfg *xray.Config, inbounds []*model.Inbound) { Protocol: "dokodemo-door", Settings: json_util.RawMessage(amneziawgEgressDokodemoSettings), StreamSettings: json_util.RawMessage(amneziawgEgressStreamSettings), + Sniffing: json_util.RawMessage(amneziawgEgressSniffingSettings), Tag: inbound.Tag, }) } diff --git a/internal/web/service/xray_config_inject_test.go b/internal/web/service/xray_config_inject_test.go index 3e7efce34..6d930ee24 100644 --- a/internal/web/service/xray_config_inject_test.go +++ b/internal/web/service/xray_config_inject_test.go @@ -590,6 +590,9 @@ func TestInjectAmneziawgEgress_CreatesBridgeTaggedWithInboundsOwnTag(t *testing. if !strings.Contains(string(ib.Settings), `"followRedirect":true`) { t.Fatalf("bridge must set followRedirect, got %s", ib.Settings) } + if !strings.Contains(string(ib.Sniffing), `"enabled":true`) { + t.Fatalf("bridge must enable sniffing -- a peer's own DNS resolution means the decapsulated traffic never carries a domain at the network layer, so domain-based Routing rules can only ever match via sniffing the payload, got %s", ib.Sniffing) + } // No auto-generated routing rule: it's entirely up to the admin's own // Routing-page rules, same as any other protocol's inbound tag. if string(cfg.RouterConfig) != before {