From 044e2926a0fd45a1b876872982344f24520b16e1 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 27 Sep 2026 16:14:45 +0200 Subject: [PATCH] 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. --- internal/amneziawgnet/resolving_bind.go | 6 +-- internal/amneziawgnet/resolving_bind_test.go | 41 ++++++++++++++++---- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/internal/amneziawgnet/resolving_bind.go b/internal/amneziawgnet/resolving_bind.go index 1f92c16da..d8bb57d4b 100644 --- a/internal/amneziawgnet/resolving_bind.go +++ b/internal/amneziawgnet/resolving_bind.go @@ -39,8 +39,8 @@ func newResolvingBind(listen string) *resolvingBind { return &resolvingBind{Bind: newListenBind(listen)} } -// ParseEndpoint resolves hostnames before handing the address to amneziawg-go -// (whose own implementation accepts literal IPs only). +// ParseEndpoint resolves hostnames, then lets the wrapped bind build the endpoint: +// its own parser takes literal IPs only, and WinRingBind sends to its own type only. func (b *resolvingBind) ParseEndpoint(s string) (awgconn.Endpoint, error) { host, portStr, err := net.SplitHostPort(strings.TrimSpace(s)) if err != nil { @@ -63,5 +63,5 @@ func (b *resolvingBind) ParseEndpoint(s string) (awgconn.Endpoint, error) { } addr = addrs[0] } - return &awgconn.StdNetEndpoint{AddrPort: netip.AddrPortFrom(addr.Unmap(), uint16(port64))}, nil + return b.Bind.ParseEndpoint(netip.AddrPortFrom(addr.Unmap(), uint16(port64)).String()) } diff --git a/internal/amneziawgnet/resolving_bind_test.go b/internal/amneziawgnet/resolving_bind_test.go index d17b596f6..d81d5e38a 100644 --- a/internal/amneziawgnet/resolving_bind_test.go +++ b/internal/amneziawgnet/resolving_bind_test.go @@ -14,12 +14,39 @@ func mustResolvingBind(t *testing.T) *resolvingBind { return newResolvingBind("") } -func endpointAddrPort(ep awgconn.Endpoint) netip.AddrPort { - std, ok := ep.(*awgconn.StdNetEndpoint) - if !ok { - panic("unexpected endpoint type") +// 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) } - return std.AddrPort } func TestResolvingBind_ParseEndpointIPLiteral(t *testing.T) { @@ -28,7 +55,7 @@ func TestResolvingBind_ParseEndpointIPLiteral(t *testing.T) { if err != nil { t.Fatalf("IP endpoint rejected: %v", err) } - got := endpointAddrPort(ep) + 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) } @@ -49,7 +76,7 @@ func TestResolvingBind_ParseEndpointHostnameResolves(t *testing.T) { if err != nil { t.Fatalf("hostname endpoint rejected: %v", err) } - if got := endpointAddrPort(ep); got.Addr().String() != "198.51.100.9" || got.Port() != 443 { + 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) } }