Files
3x-ui/internal/amneziawgnet/resolving_bind_test.go
T
Rouzbeh† d5ab84e8d5 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>
2026-09-10 14:50:48 +02:00

71 lines
2.0 KiB
Go

package amneziawgnet
import (
"context"
"errors"
"net/netip"
"testing"
awgconn "github.com/amnezia-vpn/amneziawg-go/v3/conn"
)
func endpointAddrPort(ep awgconn.Endpoint) netip.AddrPort {
std, ok := ep.(*awgconn.StdNetEndpoint)
if !ok {
panic("unexpected endpoint type")
}
return std.AddrPort
}
func TestResolvingBind_ParseEndpointIPLiteral(t *testing.T) {
b := newResolvingBind()
ep, err := b.ParseEndpoint("203.0.113.7:51820")
if err != nil {
t.Fatalf("IP endpoint rejected: %v", err)
}
got := endpointAddrPort(ep)
if got.Addr().String() != "203.0.113.7" || got.Port() != 51820 {
t.Fatalf("endpoint = %v, want 203.0.113.7:51820", got)
}
}
func TestResolvingBind_ParseEndpointHostnameResolves(t *testing.T) {
orig := lookupEndpointHost
lookupEndpointHost = func(ctx context.Context, host string) ([]netip.Addr, error) {
if host != "peer.example.test" {
t.Errorf("unexpected lookup host %q", host)
}
return []netip.Addr{netip.MustParseAddr("198.51.100.9")}, nil
}
defer func() { lookupEndpointHost = orig }()
b := newResolvingBind()
ep, err := b.ParseEndpoint("peer.example.test:443")
if err != nil {
t.Fatalf("hostname endpoint rejected: %v", err)
}
if got := endpointAddrPort(ep); got.Addr().String() != "198.51.100.9" || got.Port() != 443 {
t.Fatalf("endpoint = %v, want 198.51.100.9:443", got)
}
}
func TestResolvingBind_ParseEndpointResolveFailureIsAnError(t *testing.T) {
orig := lookupEndpointHost
lookupEndpointHost = func(ctx context.Context, host string) ([]netip.Addr, error) {
return nil, errors.New("no such host")
}
defer func() { lookupEndpointHost = orig }()
b := newResolvingBind()
if _, err := b.ParseEndpoint("missing.example.test:80"); err == nil {
t.Fatal("expected resolve failure to surface as an error")
}
}
func TestResolvingBind_ParseEndpointBadPortRejected(t *testing.T) {
b := newResolvingBind()
if _, err := b.ParseEndpoint("203.0.113.7:none"); err == nil {
t.Fatal("expected bad port to be rejected")
}
}