README
¶
End-to-end test docket
Run the Docker docket from the repository root:
./tests/e2e/run.sh
The runner skips if Docker or Compose is not available. It removes its
containers and network after the run. It clears per-run success, address, and
auth markers. It keeps the gitignored identity caches and Headscale database to
test cache reuse. Remove tests/e2e/.state to force a clean registration
cycle.
The Docker docket uses Headscale v0.29.3 with embedded DERP and STUN behind a
test-only self-signed TLS certificate. The registrar container approves wgo
nodes with headscale auth register and creates preauth keys for official
Tailscale containers.
TLS DERP tunnel
Test setup:
- Services:
derp-a,derp-b, Headscale, registrar, verifier. - Both nodes use deterministic wgo node keys and native
gonnect.Network. DISABLE_DISCOVERY=1prevents local, STUN, and DISCO endpoints.- DERP stays enabled and uses Headscale's HTTPS/TLS DERP endpoint.
Testing sequence:
- Each node starts
wgo-tailscaleand writes its auth URL to shared state. - The registrar approves both auth URLs in Headscale.
- Each node waits until the intended peer is installed in wgo.
- Each node injects one IPv4 UDP packet into its in-memory TUN.
- Each node waits for the peer packet to emerge after WireGuard processing.
- Each node checks that the peer path is
derp-tls.
Expected result:
- Both nodes receive the expected peer payload.
- Both nodes report
derp-tls, which proves encrypted WireGuard packets moved through the TLS DERP tunnel when UDP discovery was disabled.
STUN and NAT traversal
Test setup:
- Services:
stun-a,stun-b, Headscale, registrar, verifier. - Both nodes use deterministic wgo node keys and native
gonnect.Network. - Discovery is enabled.
DISABLE_DERP=1prevents DERP from carrying test traffic.- The assertion requires the Headscale DERP map to advertise a STUN endpoint.
Testing sequence:
- Each node starts and is approved by the registrar.
- Each node receives the Headscale DERP map with the embedded STUN endpoint.
- Each node checks that the DERP map includes a STUN port.
- Each node waits until the intended peer is installed in wgo.
- Each node exchanges one encrypted IPv4 UDP payload through the in-memory TUN.
- Each node checks that the peer path is
direct-udp.
Expected result:
- The control map contains a usable STUN endpoint for discovery and netcheck.
- Bidirectional encrypted traffic succeeds with
direct-udpwhile DERP is disabled. - This proves the Docker fixture receives STUN discovery metadata and can use a direct UDP path with DERP disabled. On Docker backends where the mapped address equals the bridge-local endpoint, this test does not require a distinct public endpoint.
Local peer discovery
Test setup:
- Services:
local-a,local-b, Headscale, registrar, verifier. - Both nodes use deterministic wgo node keys and native
gonnect.Network. - Discovery is enabled.
DISABLE_DERP=1prevents fallback through DERP.- Each node must publish at least one local endpoint with source
local.
Testing sequence:
- Each node starts and is approved by the registrar.
- Each node opens its UDP bind and discovers local interface endpoints.
- Each node publishes those local endpoints to Headscale.
- Each node waits until the intended peer is installed in wgo.
- Each node exchanges one encrypted IPv4 UDP payload through the in-memory TUN.
- Each node checks that the peer path is
direct-udp.
Expected result:
- Both nodes publish a
localendpoint. - Both nodes receive the expected peer payload.
- Both nodes report
direct-udp, which proves local peer discovery can create a usable direct path without DERP.
AmneziaWG obfuscation
Test setup:
- Services:
amnezia-a,amnezia-b, Headscale, registrar, verifier. - Both nodes use deterministic wgo node keys and native
gonnect.Network. - Both nodes enable the same non-default AmneziaWG obfuscation profile.
DISABLE_DERP=1prevents fallback through DERP.
Testing sequence:
- Each node starts and is approved by the registrar.
- Each node installs its peer with the e2e AmneziaWG profile.
- Each node exchanges one encrypted IPv4 UDP payload through the in-memory TUN.
- Each node checks that the peer path is
direct-udp.
Expected result:
- Both nodes receive the expected peer payload.
- Both nodes report
direct-udp, which proves Docker e2e traffic succeeds when wgo peers use matching AmneziaWG packet obfuscation.
Official client curl
Test setup:
- Services:
mixed-wgo,mixed-official, Headscale, registrar, verifier. mixed-wgouseswgo-tailscale, a userspace VTun, and an in-memory HTTP server on TCP80.mixed-officialuses the officialtailscaleandtailscaledbinaries in userspace networking mode with a SOCKS5 proxy forcurl.- The Headscale policy allows TCP
80from the official client to the wgo node.
Testing sequence:
mixed-wgostarts, writes its auth URL, and is approved by the registrar.- The registrar creates a Headscale preauth key for
mixed-official. mixed-officialtrusts the test Headscale certificate and logs in with the preauth key.mixed-wgowaits until the official peer is installed in wgo and the ACL view allows TCP80.mixed-officialrunscurlthrough the Tailscale SOCKS5 proxy to the HTTP server onmixed-wgo.
Expected result:
curlin the official Tailscale client container receiveshello from mixed-wgo.
Multi-Headscale shared device
Test setup:
- Services:
headscale-alpha,headscale-beta,multi-a,multi-b,multi-c, two registrars, and verifier. headscale-alphauses100.64.0.0/16;headscale-betauses100.65.0.0/16.multi-aconnects only toheadscale-alpha.multi-bconnects only toheadscale-beta.multi-cstarts twowgo-tailscaleclients against the same wgo device. Each client uses its own cache file andTransportID.- DERP is disabled so each edge must use a direct UDP path.
Testing sequence:
- Each
wgo-tailscaleclient writes one auth URL for its Headscale server. - The matching registrar approves each auth URL.
multi-cwaits until both control servers are running and both peers are installed in the same wgo device.multi-aexchanges one encrypted IPv4 UDP payload withmulti-cthroughheadscale-alpha.multi-bexchanges one encrypted IPv4 UDP payload withmulti-cthroughheadscale-beta.- Each node checks that its peer path is
direct-udp.
Expected result:
multi-creceives traffic from both independent Headscale tailnets through one wgo device.multi-aandmulti-beach receive the expectedmulti-cpayload.- The two controllers keep separate transports and do not remove or overwrite each other's peers.
Hosted service registration
This Go test compiles with the regular suite but skips unless
tests/e2e/real-service.json exists:
cp tests/e2e/real-service.json.example tests/e2e/real-service.json
# Fill control URL, hostname, node private key, and optionally an auth key.
go test -v ./tests/e2e -run TestRealTailscaleService
Test setup:
- One real control service account or Headscale service.
- One wgo node private key in
real-service.json. - One gitignored cache file for machine and DISCO identity reuse.
Testing sequence:
- The test creates a wgo device and
wgo-tailscaleclient. - The client starts with the configured control URL and TLS config.
- If the control service requires approval, the test prints the auth URL.
- The test waits until control returns a running self node.
Expected result:
- The node reaches
StateRunning. - Repeat runs reuse the cache instead of creating a new machine identity.
Hosted two-node real traffic
Run the interactive real traffic check with:
cp tests/e2e/real-service.json.example tests/e2e/real-service.json
just test-real
Test setup:
- Two real nodes from
real-service.json. - Userspace VTuns from
gonnect-netstack. - ACLs that allow TCP
80in both directions between the two node addresses.
Testing sequence:
- The command starts both nodes.
- If needed, it prints auth URLs and waits for approval.
- It waits until both nodes are running and each peer is installed in wgo.
- It waits until ACL checks allow TCP
80in both directions. - It attaches userspace VTuns and starts HTTP servers on both node addresses.
- It sends HTTP requests both ways with discovery enabled.
- It repeats the HTTP check with discovery disabled.
Expected result:
- HTTP succeeds in both directions in direct discovery mode.
- HTTP succeeds in both directions in forced TLS DERP mode.
- The observed peer paths match
direct-udpandderp-tlsrespectively.