feat(amneziawg): add AmneziaWG as an outbound protocol (#6320)

* feat(amneziawg): add AmneziaWG as an outbound protocol

- AmneziaWG outbound protocol end-to-end: config schema, socks bridge, netstack, panel UI
- Route amneziawg outbounds to HTTP probe in TCP mode (backend + frontend classifiers) with pinning test
- Add 2-minute idle read deadline to pumpUDPEgress to reap idle egress sessions
- Require SOCKS5 username/password auth on the egress server (reject NO-AUTH with 0xFF) with test
- Bound the egress TCP tunnel dial with portForwardDialTimeout (10s), matching portfwd.go
- Resolve UDP domain targets off the association's reader loop via deliverUDPDatagram; race-safe getOrDial starts the reply pump at session creation; client passed by value into resolver goroutines (pinned by TestEgressUDPDatagramDomainInterleavedClients)
- Reconcile early-returns on an empty desired set and closes the egress listener; EgressBasePort (64900) is reserved against local inbound port conflicts like the internal API port, with pinning tests for both the port reservation (TestCheckPortConflict_EgressPortBlockedLocal) and the Reconcile empty-desired Close/Listen lifecycle (TestOutboundManagerReconcileEmptyDesiredClosesEgress)
- Eliminate acceptLoop shutdown race by validating listener != nil and registering to tracked under s.mu before wg.Add; bound pre-auth handshake with deadline (pinned by TestEgressServerCloseDuringConcurrentAccepts)
- Support AAAA and dual-stack domain resolution in tunnel DNS resolver with v6 default fallback (DefaultTunnelDNSServerV6); add DNS field to frontend protocol form; avoid unneeded cache flushes on unchanged SetStack ticks

* fix(amneziawg): resolve IPv6-only DNS default fallback and validate required keys

- Default to IPv6 tunnel DNS on IPv6-only outbounds with blank dns
- Require non-empty secretKey and peer publicKey in ValidateAmneziaWGOutbound
- Add end-to-end IPv6 tunnel domain resolution test and test empty key rejection
- Trim comment blocks exceeding 2 lines across modified files
- Fix Storybook test execution on environments with POSIX locale

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: rqzbeh <rqzbeh@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
Rouzbeh†
2026-09-10 16:20:48 +03:30
committed by GitHub
parent 876497db6e
commit d5ab84e8d5
50 changed files with 4169 additions and 33 deletions
+27
View File
@@ -600,3 +600,30 @@ only - it changes no code), `claude-issue-analyst.yml` (issue triage).
- **Tests live next to code** (`foo.go``foo_test.go`), plus golden snapshots in
`frontend/src/test/golden/fixtures/` for config generation — update fixtures intentionally,
not blindly, when output changes.
## AmneziaWG outbound pseudo-protocol
The template stores `protocol: "amneziawg"` rows verbatim; Xray-core has no
such proxy. At config generation (`GetXrayConfig` and the outbound latency
probe's batch config) each row is swapped by `amneziawgnet.BuildSocksBridge`
into a loopback socks outbound pointed at the panel's egress server (port
`EgressBasePort`), authenticating with the row's tag as username. Sibling keys
(`mux`, `sendThrough`, `targetStrategy`, `streamSettings.sockopt`) survive the
swap. The embedded amneziawg-go client device lives in the panel process; an
unbridgeable entry (unreadable settings, empty/non-string tag) fails config
generation instead of skipping, because a skipped entry leaves
`protocol: "amneziawg"` behind -- which makes Xray refuse the whole config.
Traffic flow: Xray socks client -> egress SOCKS5 server (tag = username) ->
per-tag device netstack -> amneziawg-go tunnel. Domain targets are resolved by
a DNS exchange through that same netstack (`resolveTunnelVia`, default server
`DefaultTunnelDNSServer`), so names never leak to the panel host's resolver and
answers are valid at the tunnel's location; results cache for 60s. UDP flows
key sessions on the resolved address:port. Peer endpoints may be hostnames:
`resolvingBind.ParseEndpoint` resolves once at configure time (kernel
`wg setconf` semantics); a hostname whose DNS dies later needs a template
re-save or job restart to re-resolve.
`randomTrailers` defaults to false wherever the panel does not control the
peer (outbound form/schema): a receiver without 3.1 trailers silently drops
oversized packets from a sender with it enabled.