Files
3x-ui/internal/amneziawgnet/resolving_bind_test.go
T
MHSanaei 044e2926a0 fix(amneziawg): let the wrapped bind build peer endpoints
resolvingBind.ParseEndpoint resolved a hostname and then built a
StdNetEndpoint itself. That matches StdNetBind, the default bind on
Linux, but on Windows the default is WinRingBind, whose Send refuses any
endpoint it did not parse ("endpoint type does not correspond with bind
type"). Every handshake initiation failed there, so no AmneziaWG tunnel,
inbound or outbound, could come up on the Windows builds. ParseEndpoint
now hands the resolved literal to the wrapped bind's own parser, which
returns the endpoint type that bind sends to; StdNetBind and pinnedBind
build the same StdNetEndpoint as before.

The resolvingBind tests now read endpoints through the Endpoint
interface instead of asserting StdNetEndpoint, which had pinned the bug.
2026-09-27 16:14:45 +02:00

103 lines
3.1 KiB
Go

package amneziawgnet
import (
"context"
"errors"
"net/netip"
"testing"
awgconn "github.com/amnezia-vpn/amneziawg-go/v3/conn"
)
func mustResolvingBind(t *testing.T) *resolvingBind {
t.Helper()
return newResolvingBind("")
}
// endpointAddrPort reads any bind's endpoint; Windows' default bind has its own type.
func endpointAddrPort(t *testing.T, ep awgconn.Endpoint) netip.AddrPort {
t.Helper()
ap, err := netip.ParseAddrPort(ep.DstToString())
if err != nil {
t.Fatalf("endpoint %q: %v", ep.DstToString(), err)
}
return ap
}
// ownEndpointBind accepts only endpoints it parsed itself, as WinRingBind does.
type ownEndpointBind struct{ awgconn.Bind }
type ownEndpoint struct{ awgconn.StdNetEndpoint }
func (ownEndpointBind) ParseEndpoint(s string) (awgconn.Endpoint, error) {
ap, err := netip.ParseAddrPort(s)
if err != nil {
return nil, err
}
return &ownEndpoint{awgconn.StdNetEndpoint{AddrPort: ap}}, nil
}
// WinRingBind, the default bind on Windows, refuses to send to an endpoint of any
// other type, so a hand-built StdNetEndpoint killed every handshake there.
func TestResolvingBind_ParseEndpointComesFromTheWrappedBind(t *testing.T) {
ep, err := (&resolvingBind{Bind: ownEndpointBind{}}).ParseEndpoint("203.0.113.7:51820")
if err != nil {
t.Fatalf("ParseEndpoint: %v", err)
}
if _, ok := ep.(*ownEndpoint); !ok {
t.Fatalf("endpoint is %T, not the wrapped bind's own type", ep)
}
}
func TestResolvingBind_ParseEndpointIPLiteral(t *testing.T) {
b := mustResolvingBind(t)
ep, err := b.ParseEndpoint("203.0.113.7:51820")
if err != nil {
t.Fatalf("IP endpoint rejected: %v", err)
}
got := endpointAddrPort(t, 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 := mustResolvingBind(t)
ep, err := b.ParseEndpoint("peer.example.test:443")
if err != nil {
t.Fatalf("hostname endpoint rejected: %v", err)
}
if got := endpointAddrPort(t, 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 := mustResolvingBind(t)
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 := mustResolvingBind(t)
if _, err := b.ParseEndpoint("203.0.113.7:none"); err == nil {
t.Fatal("expected bad port to be rejected")
}
}