The Cloudflare "reserved" bytes (1-3) were zeroed unconditionally on every received datagram across all StdNetBind/WinRingBind receive paths. AmneziaWG reads its magic header as LittleEndian.Uint32(packet[padding:]) where padding is s1/s2/s4; with small padding (0-3) the magic overlaps bytes 1-3, so clearing them collapses it out of the ranged h1-h4 window and every packet is dropped (handshake included) — the AWG endpoint never comes up. Plain WG (types 1-4, bytes 1-3 already zero) and large padding are unaffected, which is why it went unnoticed. Gate all five receive clears (bind_std receiveIP, msgx_darwin receiveSingle + makeReceiveMsgX, bind_windows receiveIPv4/v6) behind a new hasReserved() so bytes 1-3 are only touched when a WARP reserved value is actually configured. Send paths already gate on a per-endpoint loaded/non-zero check, so they are left unchanged. The reserved map is populated before the receive goroutines start and never mutated after, so the lock-free read is safe. Tests: awg_stdnetbind_reserved_lx_test.go brings up two Devices over StdNetBind with zero padding (magic in bytes 0-3) and asserts delivery (red before the fix, green after); reserved_gate_lx_test.go pins the hasReserved() gate.
56 lines
1.7 KiB
Go
56 lines
1.7 KiB
Go
/* SPDX-License-Identifier: MIT
|
|
*
|
|
* lx: unit coverage for the StdNetBind.hasReserved() gate that guards the
|
|
* receive-side reserved-clear. receiveIP zeroes bytes 1-3 (Cloudflare WARP
|
|
* "reserved") only when a non-zero reserved value is set for some endpoint;
|
|
* otherwise an AmneziaWG magic header landing in bytes 1-3 (small s1/s2/s4
|
|
* padding) would be corrupted and the packet dropped. This test pins the gate
|
|
* itself; the end-to-end handshake proof lives in the device package.
|
|
*/
|
|
|
|
package conn
|
|
|
|
import (
|
|
"net/netip"
|
|
"testing"
|
|
)
|
|
|
|
func stdNetBindForTest(t *testing.T) *StdNetBind {
|
|
t.Helper()
|
|
b, ok := NewStdNetBind(nil).(*StdNetBind)
|
|
if !ok {
|
|
t.Fatalf("NewStdNetBind did not return *StdNetBind")
|
|
}
|
|
return b
|
|
}
|
|
|
|
func TestStdNetBindHasReserved(t *testing.T) {
|
|
b := stdNetBindForTest(t)
|
|
if b.hasReserved() {
|
|
t.Fatal("fresh bind must report no reserved value")
|
|
}
|
|
|
|
ep := netip.MustParseAddrPort("127.0.0.1:51820")
|
|
|
|
// An all-zero reserved value is indistinguishable from "unset" and must
|
|
// not arm the clear.
|
|
b.SetReservedForEndpoint(ep, [3]byte{0, 0, 0})
|
|
if b.hasReserved() {
|
|
t.Fatal("all-zero reserved must not count as reserved")
|
|
}
|
|
|
|
// Any non-zero byte (WARP anycast tag) arms the clear.
|
|
b.SetReservedForEndpoint(ep, [3]byte{0, 0, 1})
|
|
if !b.hasReserved() {
|
|
t.Fatal("non-zero reserved (byte 3) must count as reserved")
|
|
}
|
|
|
|
// A second endpoint's non-zero value must also be seen.
|
|
b2 := stdNetBindForTest(t)
|
|
ep2 := netip.MustParseAddrPort("192.0.2.1:2408")
|
|
b2.SetReservedForEndpoint(ep, [3]byte{0, 0, 0})
|
|
b2.SetReservedForEndpoint(ep2, [3]byte{0xAB, 0, 0})
|
|
if !b2.hasReserved() {
|
|
t.Fatal("non-zero reserved on any endpoint must count as reserved")
|
|
}
|
|
}
|