- docker-compose.yml: run peer-a and peer-b with TS_USERSPACE=false +
/dev/net/tun device + cap_add. Pin peer-a's WG listen port to 41641
via TS_TAILSCALED_EXTRA_ARGS and publish it to the host so direct
UDP from outside docker has somewhere to land.
- run.sh: use an ephemeral pre-auth key for the test client so
Headscale auto-deletes the test node when its map stream drops
(instead of accumulating hundreds of stale entries that eventually
slow netmap propagation to a crawl). Disable shields-up on both
peers so the kernel firewall doesn't drop inbound tailnet TCP. Tweak
the JSON key extraction to handle pretty-printed output.
- integration.rs: add `test_e2e_tcp_through_tunnel` that brings up
the daemon, dials peer-a's echo server through the proxy, and
asserts the echo body comes back. Currently `#[ignore]`d — the
docker stack runs Headscale over plain HTTP, but Tailscale's client
unconditionally tries TLS to DERP relays ("tls: first record does
not look like a TLS handshake"), so peer-a can never receive
packets we forward via the relay. Unblocking needs either TLS
termination on the docker DERP or running the test inside the same
docker network as peer-a. Test stays in the tree because everything
it tests up to the read timeout is real verified behavior.
110 lines
3.8 KiB
YAML
110 lines
3.8 KiB
YAML
# Integration test stack for sunbeam-net VPN client.
|
|
# Spins up Headscale (coordination + embedded DERP) and two Tailscale
|
|
# peers so we can test the full TS2021 → WireGuard → DERP pipeline.
|
|
#
|
|
# Usage:
|
|
# docker compose -f sunbeam-net/tests/docker-compose.yml up -d
|
|
# docker compose -f sunbeam-net/tests/docker-compose.yml exec headscale \
|
|
# headscale preauthkeys create --user test --reusable --expiration 1h
|
|
# # Copy the key, then:
|
|
# SUNBEAM_NET_TEST_AUTH_KEY=<key> \
|
|
# SUNBEAM_NET_TEST_COORD_URL=http://localhost:8080 \
|
|
# cargo test -p sunbeam-net --features integration --test integration
|
|
# docker compose -f sunbeam-net/tests/docker-compose.yml down
|
|
|
|
services:
|
|
# ── Headscale (coordination server + embedded DERP relay) ───────────
|
|
headscale:
|
|
image: headscale/headscale:0.23
|
|
command: serve
|
|
ports:
|
|
- "8080:8080" # control plane (TS2021 Noise + HTTP API)
|
|
- "3478:3478/udp" # STUN
|
|
- "9090:9090" # metrics
|
|
volumes:
|
|
- ./config/headscale.yaml:/etc/headscale/config.yaml:ro
|
|
- headscale-data:/var/lib/headscale
|
|
healthcheck:
|
|
test: ["CMD", "headscale", "nodes", "list"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 15
|
|
|
|
# ── Tailscale peer A (validates that Headscale is working) ──────────
|
|
# This peer registers with Headscale and stays online so our Rust
|
|
# client can discover it in the netmap and attempt WireGuard tunnels.
|
|
# Runs in TUN mode (TS_USERSPACE=false) so the host kernel actually
|
|
# routes packets to peer-a's tailnet IP — this is what makes inbound
|
|
# TCP from other tailnet members work end-to-end.
|
|
peer-a:
|
|
image: tailscale/tailscale:stable
|
|
hostname: peer-a
|
|
depends_on:
|
|
headscale:
|
|
condition: service_healthy
|
|
environment:
|
|
TS_AUTHKEY: "${PEER_A_AUTH_KEY}"
|
|
TS_STATE_DIR: /var/lib/tailscale
|
|
TS_USERSPACE: "false"
|
|
TS_EXTRA_ARGS: --login-server=http://headscale:8080
|
|
# Pin the WireGuard listen port (passed to tailscaled itself) so we
|
|
# can publish it to the host — without this our test daemon (running
|
|
# outside docker) can't reach peer-a's UDP endpoint.
|
|
TS_TAILSCALED_EXTRA_ARGS: --port=41641
|
|
cap_add:
|
|
- NET_ADMIN
|
|
- NET_RAW
|
|
devices:
|
|
- /dev/net/tun:/dev/net/tun
|
|
ports:
|
|
- "41641:41641/udp"
|
|
volumes:
|
|
- peer-a-state:/var/lib/tailscale
|
|
# Tailscale doesn't have a great healthcheck, but it registers fast
|
|
healthcheck:
|
|
test: ["CMD", "tailscale", "status", "--json"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 20
|
|
|
|
# ── Tailscale peer B (second peer for relay/direct tests) ───────────
|
|
peer-b:
|
|
image: tailscale/tailscale:stable
|
|
hostname: peer-b
|
|
depends_on:
|
|
headscale:
|
|
condition: service_healthy
|
|
environment:
|
|
TS_AUTHKEY: "${PEER_B_AUTH_KEY}"
|
|
TS_STATE_DIR: /var/lib/tailscale
|
|
TS_USERSPACE: "false"
|
|
TS_EXTRA_ARGS: --login-server=http://headscale:8080
|
|
cap_add:
|
|
- NET_ADMIN
|
|
- NET_RAW
|
|
devices:
|
|
- /dev/net/tun:/dev/net/tun
|
|
volumes:
|
|
- peer-b-state:/var/lib/tailscale
|
|
healthcheck:
|
|
test: ["CMD", "tailscale", "status", "--json"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 20
|
|
|
|
# ── Simple HTTP echo server on peer-a's tailnet IP ──────────────────
|
|
# Used to verify end-to-end TCP connectivity through the WireGuard tunnel.
|
|
# Listens on peer-a's container network; reachable via peer-a's tailnet IP.
|
|
echo:
|
|
image: hashicorp/http-echo:latest
|
|
command: -listen=:5678 -text="sunbeam-net integration test"
|
|
network_mode: "service:peer-a"
|
|
depends_on:
|
|
peer-a:
|
|
condition: service_healthy
|
|
|
|
volumes:
|
|
headscale-data:
|
|
peer-a-state:
|
|
peer-b-state:
|