fix(amneziawgnet): stop tests binding fixed ports in the ephemeral range

The race job failed TestEnsureRebuildsWhenS4ChangesTheDerivedMTU with
"bind 127.0.0.1:58720: address already in use". The package hardcoded
WireGuard and port-forward ports 58712-58922, all inside Linux's ephemeral
range (32768-60999), so any concurrent :0 socket could already hold one;
several were also reused across tests (58713 x3, 58715, 58717, 58720).

Tests that never dial the server now use ListenPort 0; tests that need the
number take an OS-assigned one via freeUDPPort / freeTCPUDPPort (the latter
for port forwards, which bind TCP and UDP on the same port).
This commit is contained in:
MHSanaei
2026-11-01 15:14:49 +01:00
parent 757444d505
commit 6f37433d43
9 changed files with 58 additions and 37 deletions
+1 -1
View File
@@ -210,7 +210,7 @@ func (t *benchTunnel) dial(b *testing.B, dest netip.AddrPort) *gonet.TCPConn {
func BenchmarkTunnelThroughput(b *testing.B) { func BenchmarkTunnelThroughput(b *testing.B) {
const chunkSize = 64 << 10 const chunkSize = 64 << 10
const serverAddr = "10.203.0.1" const serverAddr = "10.203.0.1"
tun := newBenchTunnel(b, 58714, serverAddr, "10.203.0.2") tun := newBenchTunnel(b, freeUDPPort(b), serverAddr, "10.203.0.2")
defer tun.closeFn() defer tun.closeFn()
b.Run("upload", func(b *testing.B) { b.Run("upload", func(b *testing.B) {
+5 -5
View File
@@ -39,7 +39,7 @@ func TestNewDeviceHandshakeForwarderAndIdentity(t *testing.T) {
t.Fatalf("generate client keypair: %v", err) t.Fatalf("generate client keypair: %v", err)
} }
const listenPort = 58712 // fixed loopback test port, matches the validated Phase 0 spike approach listenPort := freeUDPPort(t)
const wantEmail = "test-peer@example.com" const wantEmail = "test-peer@example.com"
inst := amneziawg.Instance{ inst := amneziawg.Instance{
@@ -243,7 +243,7 @@ func TestNewDeviceHeaderProtectionAndContentPaddingRoundTrip(t *testing.T) {
t.Fatalf("generate header protection key: %v", err) t.Fatalf("generate header protection key: %v", err)
} }
const listenPort = 58713 // fixed loopback test port, distinct from the handshake test above listenPort := freeUDPPort(t)
const contentPaddingAddition = "20-40" const contentPaddingAddition = "20-40"
inst := amneziawg.Instance{ inst := amneziawg.Instance{
@@ -429,7 +429,7 @@ func TestNewDeviceRandomTrailersAndDisableCookiesRoundTrip(t *testing.T) {
t.Fatalf("generate client keypair: %v", err) t.Fatalf("generate client keypair: %v", err)
} }
const listenPort = 58721 // fixed loopback test port, distinct from every other test in this package listenPort := freeUDPPort(t)
inst := amneziawg.Instance{ inst := amneziawg.Instance{
Id: 3, Id: 3,
@@ -591,7 +591,7 @@ func TestValidatedObfuscationAlwaysApplies(t *testing.T) {
}}, }},
} }
for i, tc := range cases { for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
o := base o := base
tc.mut(&o) tc.mut(&o)
@@ -599,7 +599,7 @@ func TestValidatedObfuscationAlwaysApplies(t *testing.T) {
return // rejected before saving, which is the whole point return // rejected before saving, which is the whole point
} }
inst := amneziawg.Instance{ inst := amneziawg.Instance{
Id: 88, InterfaceName: "awgcontract", ListenPort: 58900 + i, Id: 88, InterfaceName: "awgcontract", ListenPort: 0,
PrivateKey: priv, PublicKey: pub, PrivateKey: priv, PublicKey: pub,
Address: []string{"10.198.0.1/24"}, MTU: 1420, Address: []string{"10.198.0.1/24"}, MTU: 1420,
Obfuscation: o, Obfuscation: o,
+1 -1
View File
@@ -45,7 +45,7 @@ func TestDiagnoseDeviceReportsListenPortAndPeerState(t *testing.T) {
t.Fatalf("generate idle-peer keypair: %v", err) t.Fatalf("generate idle-peer keypair: %v", err)
} }
const listenPort = 58713 // distinct from device_test.go's fixed port listenPort := freeUDPPort(t)
const activeEmail = "active@example.com" const activeEmail = "active@example.com"
const idleEmail = "idle@example.com" const idleEmail = "idle@example.com"
+2 -12
View File
@@ -57,12 +57,7 @@ func newPairedTunnelForTest(t *testing.T) *pairedTunnel {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
pc, err := net.ListenPacket("udp", "127.0.0.1:0") listenPort := freeUDPPort(t)
if err != nil {
t.Fatal(err)
}
listenPort := pc.LocalAddr().(*net.UDPAddr).Port
pc.Close()
obf := amneziawg.Obfuscation31{Jc: 4, Jmin: 40, Jmax: 70, S1: 20, S2: 30, S3: 20, S4: 20} obf := amneziawg.Obfuscation31{Jc: 4, Jmin: 40, Jmax: 70, S1: 20, S2: 30, S3: 20, S4: 20}
serverInst := amneziawg.Instance{ serverInst := amneziawg.Instance{
@@ -133,12 +128,7 @@ func newPairedTunnelV6ForTest(t *testing.T) *pairedTunnel {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
pc, err := net.ListenPacket("udp", "127.0.0.1:0") listenPort := freeUDPPort(t)
if err != nil {
t.Fatal(err)
}
listenPort := pc.LocalAddr().(*net.UDPAddr).Port
pc.Close()
obf := amneziawg.Obfuscation31{Jc: 4, Jmin: 40, Jmax: 70, S1: 20, S2: 30, S3: 20, S4: 20} obf := amneziawg.Obfuscation31{Jc: 4, Jmin: 40, Jmax: 70, S1: 20, S2: 30, S3: 20, S4: 20}
serverInst := amneziawg.Instance{ serverInst := amneziawg.Instance{
+6 -6
View File
@@ -29,7 +29,7 @@ func TestManagerLifecycle(t *testing.T) {
inst := amneziawg.Instance{ inst := amneziawg.Instance{
Id: 3, Id: 3,
InterfaceName: "awgtest3", InterfaceName: "awgtest3",
ListenPort: 58714, ListenPort: 0,
PrivateKey: priv, PrivateKey: priv,
PublicKey: pub, PublicKey: pub,
Address: []string{"10.203.0.1/24"}, Address: []string{"10.203.0.1/24"},
@@ -117,7 +117,7 @@ func TestEnsureRebuildsWhenS4ChangesTheDerivedMTU(t *testing.T) {
inst := amneziawg.Instance{ inst := amneziawg.Instance{
Id: 9 + i, Id: 9 + i,
InterfaceName: fmt.Sprintf("awgtest%d", 9+i), InterfaceName: fmt.Sprintf("awgtest%d", 9+i),
ListenPort: 58719 + i, ListenPort: 0,
PrivateKey: priv, PrivateKey: priv,
PublicKey: pub, PublicKey: pub,
Address: []string{"10.209.0.1/24"}, Address: []string{"10.209.0.1/24"},
@@ -214,7 +214,7 @@ func TestEnsureUnchangedInstanceDoesNotResetLivePeers(t *testing.T) {
inst := amneziawg.Instance{ inst := amneziawg.Instance{
Id: 4, Id: 4,
InterfaceName: "awgtest4", InterfaceName: "awgtest4",
ListenPort: 58715, ListenPort: 0,
PrivateKey: priv, PrivateKey: priv,
PublicKey: pub, PublicKey: pub,
Address: []string{"10.204.0.1/24"}, Address: []string{"10.204.0.1/24"},
@@ -351,7 +351,7 @@ func TestEnsureDropsFlowsOfReassignedTunnelAddress(t *testing.T) {
inst := amneziawg.Instance{ inst := amneziawg.Instance{
Id: 9, Id: 9,
InterfaceName: "awgtest9", InterfaceName: "awgtest9",
ListenPort: 58720, ListenPort: 0,
PrivateKey: priv, PrivateKey: priv,
PublicKey: pub, PublicKey: pub,
Address: []string{"10.206.0.1/24"}, Address: []string{"10.206.0.1/24"},
@@ -465,7 +465,7 @@ func TestEnsureHeaderProtectionKeyChangeReconfiguresInPlace(t *testing.T) {
inst := amneziawg.Instance{ inst := amneziawg.Instance{
Id: 7, Id: 7,
InterfaceName: "awgtest7", InterfaceName: "awgtest7",
ListenPort: 58717, ListenPort: 0,
PrivateKey: priv, PrivateKey: priv,
PublicKey: pub, PublicKey: pub,
Address: []string{"10.207.0.1/24"}, Address: []string{"10.207.0.1/24"},
@@ -525,7 +525,7 @@ func TestEnsureRejectsHeaderProtectionKeyWithLowS1S4(t *testing.T) {
inst := amneziawg.Instance{ inst := amneziawg.Instance{
Id: 8, Id: 8,
InterfaceName: "awgtest8", InterfaceName: "awgtest8",
ListenPort: 58718, ListenPort: 0,
PrivateKey: priv, PrivateKey: priv,
PublicKey: pub, PublicKey: pub,
Address: []string{"10.208.0.1/24"}, Address: []string{"10.208.0.1/24"},
+6 -8
View File
@@ -145,7 +145,7 @@ func TestPortForwardSetReconcileOpensAndClosesListeners(t *testing.T) {
gs := newTestStack(t, "10.211.0.1") gs := newTestStack(t, "10.211.0.1")
set := NewPortForwardSet(gs, 501) set := NewPortForwardSet(gs, 501)
const port = 58910 port := freeTCPUDPPort(t)
inst := amneziawg.Instance{Peers: []amneziawg.Peer{ inst := amneziawg.Instance{Peers: []amneziawg.Peer{
peerWithPortsAndIPs("a@x", fmt.Sprintf("%d", port), "10.211.0.2/32"), peerWithPortsAndIPs("a@x", fmt.Sprintf("%d", port), "10.211.0.2/32"),
}} }}
@@ -190,8 +190,7 @@ func TestPortForwardSetReconcileSurvivesPreBoundPort(t *testing.T) {
gs := newTestStack(t, "10.211.1.1") gs := newTestStack(t, "10.211.1.1")
set := NewPortForwardSet(gs, 502) set := NewPortForwardSet(gs, 502)
const collidingPort = 58911 collidingPort, okPort := freeTCPUDPPort(t), freeTCPUDPPort(t)
const okPort = 58912
blocker, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", collidingPort)) blocker, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", collidingPort))
if err != nil { if err != nil {
t.Fatalf("pre-bind test port: %v", err) t.Fatalf("pre-bind test port: %v", err)
@@ -280,9 +279,8 @@ func TestPortForwardRoundTripTCPAndUDP(t *testing.T) {
t.Fatalf("generate client keypair: %v", err) t.Fatalf("generate client keypair: %v", err)
} }
const listenPort = 58920 // fixed loopback test port, matches this package's existing test convention listenPort := freeUDPPort(t)
const tcpPort = 58921 tcpPort, udpPort := freeTCPUDPPort(t), freeTCPUDPPort(t)
const udpPort = 58922
const clientAddr = "10.202.0.2" const clientAddr = "10.202.0.2"
inst := amneziawg.Instance{ inst := amneziawg.Instance{
@@ -510,7 +508,7 @@ func waitPortForwardSlots(t *testing.T, slots chan struct{}, want int) {
} }
func TestPortForwardUDPSessionsAreCapped(t *testing.T) { func TestPortForwardUDPSessionsAreCapped(t *testing.T) {
const port = 58913 port := freeUDPPort(t)
const free, sources = 3, 10 const free, sources = 3, 10
held := leavePortForwardSlots(t, portForwardUDPSlots, free) held := leavePortForwardSlots(t, portForwardUDPSlots, free)
@@ -551,7 +549,7 @@ func TestPortForwardUDPSessionsAreCapped(t *testing.T) {
} }
func TestPortForwardTCPConnectionsAreCapped(t *testing.T) { func TestPortForwardTCPConnectionsAreCapped(t *testing.T) {
const port = 58914 port := freePort(t)
const free, clients = 3, 10 const free, clients = 3, 10
held := leavePortForwardSlots(t, portForwardTCPSlots, free) held := leavePortForwardSlots(t, portForwardTCPSlots, free)
+35 -2
View File
@@ -77,7 +77,7 @@ func TestSocksRelayAgainstRealXray(t *testing.T) {
t.Fatalf("generate client keypair: %v", err) t.Fatalf("generate client keypair: %v", err)
} }
const listenPort = 58715 listenPort := freeUDPPort(t)
inst := amneziawg.Instance{ inst := amneziawg.Instance{
Id: 4, Id: 4,
InterfaceName: "awgtest4", InterfaceName: "awgtest4",
@@ -305,7 +305,7 @@ func TestManagerEnsureAutomaticallyWiresRelay(t *testing.T) {
} }
const wantEmail = "manager-e2e-peer@example.com" const wantEmail = "manager-e2e-peer@example.com"
const listenPort = 58716 listenPort := freeUDPPort(t)
const inboundID = 5 const inboundID = 5
tcpEcho, tcpEchoAddr := startTCPEcho(t, localIP) tcpEcho, tcpEchoAddr := startTCPEcho(t, localIP)
@@ -595,6 +595,39 @@ func freePort(t *testing.T) int {
return l.Addr().(*net.TCPAddr).Port return l.Addr().(*net.TCPAddr).Port
} }
// freeUDPPort asks the OS for a free loopback UDP port: a fixed one sits in the
// Linux ephemeral range, where any concurrent :0 socket can already hold it.
func freeUDPPort(tb testing.TB) int {
tb.Helper()
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
tb.Fatal(err)
}
defer pc.Close()
return pc.LocalAddr().(*net.UDPAddr).Port
}
// freeTCPUDPPort finds a loopback port free for both protocols, since a port
// forward binds a TCP and a UDP listener on the same number.
func freeTCPUDPPort(t *testing.T) int {
t.Helper()
for range 20 {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
port := l.Addr().(*net.TCPAddr).Port
pc, err := net.ListenPacket("udp", fmt.Sprintf("127.0.0.1:%d", port))
l.Close()
if err == nil {
pc.Close()
return port
}
}
t.Fatal("no loopback port free for both TCP and UDP")
return 0
}
func waitForPort(t *testing.T, port int) { func waitForPort(t *testing.T, port int) {
t.Helper() t.Helper()
deadline := time.Now().Add(15 * time.Second) deadline := time.Now().Add(15 * time.Second)
+1 -1
View File
@@ -34,7 +34,7 @@ func TestNewDeviceUDPHandlerAndReply(t *testing.T) {
t.Fatalf("generate client keypair: %v", err) t.Fatalf("generate client keypair: %v", err)
} }
const listenPort = 58713 // distinct from the TCP test's port listenPort := freeUDPPort(t)
const wantEmail = "udp-test-peer@example.com" const wantEmail = "udp-test-peer@example.com"
const echoPayload = "hello-from-client" const echoPayload = "hello-from-client"
+1 -1
View File
@@ -305,7 +305,7 @@ func TestManagerRemoveLeavesHostOwnedAddressInPlace(t *testing.T) {
inst := amneziawg.Instance{ inst := amneziawg.Instance{
Id: 7, Id: 7,
InterfaceName: "awgtest7", InterfaceName: "awgtest7",
ListenPort: 58717, ListenPort: 0,
PrivateKey: priv, PrivateKey: priv,
PublicKey: pub, PublicKey: pub,
Address: []string{"10.207.0.1/24", "fd86:ea04:1115::1/64"}, Address: []string{"10.207.0.1/24", "fd86:ea04:1115::1/64"},