From bb6a874dc5db39b2730e2807cb85179692b1fa1a Mon Sep 17 00:00:00 2001 From: Kuzz007 Date: Tue, 28 Jul 2026 00:27:45 +0300 Subject: [PATCH] fix(amneziawg): enable sniffing on the TPROXY bridge Domain-based Routing rules could never match RouteThroughXray traffic: an AmneziaWG peer resolves DNS itself, through the tunnel, before ever sending a packet, so the decapsulated traffic TPROXY hands to the bridge is already a bare destination IP with no domain name attached at the network layer. Every other inbound recovers this via sniffing (confirmed working for the stock wireguard inbound, which does have it configured); the bridge never got a sniffing block at all, so only tag/IP/network-based rules could ever match it -- any domain rule above it in the list was silently unreachable. --- internal/web/service/xray.go | 14 ++++++++++++++ internal/web/service/xray_config_inject_test.go | 3 +++ 2 files changed, 17 insertions(+) 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 {