Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion tests/mesh/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,10 @@ start_vms() {
sleep 1
done
done
# Each node's Tailscale client advertises the endpoints it finds for itself: its interface addresses, and the address a STUN server on a public DERP region sees it come from, which on a hosted runner is the runner's public address. That address hairpins back through the runner's NAT, and magicsock picks between it and the test network address by measured latency, with a bonus for a private address that sub-millisecond jitter can outweigh (betterAddr in wgengine/magicsock/endpoint.go of the pinned Tailscale), so which one a node uses varied between runs. STUN is UDP to port 3478, so it is dropped in every VM, as on a LAN whose NAT does not hairpin: a node then advertises only addresses of its own VM's interfaces (see assert_direct_paths), and DERP, over TCP, still carries the disco messages that set a direct path up.
for index in $(seq 1 "$node_count"); do
docker exec "$(vm "$index")" iptables -I OUTPUT 1 -p udp --dport 3478 -j DROP
done
}

# A Headscale server on this machine's Docker, at a fixed address on the test network, with a policy that lets the nodes register, reach each other and route pods; prints the API key and a join key on two lines.
Expand Down Expand Up @@ -562,7 +566,7 @@ write_host_network_inventory() {

# With host networking, every node's Tailscale client must reach every other node directly at that node's daemon's own address (its stand-in LAN address), not through a relay, and the node container must publish no port.
assert_direct_paths() {
local index other status deadline
local index other status deadline own outside
for index in $(seq 1 "$node_count"); do
[ "$(docker_on "$index" inspect --format '{{.HostConfig.NetworkMode}}' "$(container "$index")")" = host ] || fail "$(container "$index") is not on the host network"
[ -z "$(docker_on "$index" port "$(container "$index")")" ] || fail "$(container "$index") publishes ports: $(docker_on "$index" port "$(container "$index")")"
Expand All @@ -577,6 +581,15 @@ assert_direct_paths() {
echo " $(node_name "$index") -> $(node_name "$other"): $(echo "$status" | tail -n 1)"
done
done
# Every endpoint each node advertises for itself is an address of its own VM's interfaces (the test network address, and the bridges its Docker and k3s create): none is an address learnt over STUN, which belongs to the runner's NAT and would let its peers pick a path through it instead (see start_vms). tailscale status lists a node's own endpoints under Self.Addrs, and none for its peers (UpdateStatus in wgengine/magicsock/magicsock.go of the pinned Tailscale fills Self only). The node container is on its VM's host network, so the VM's interfaces are the node's.
for index in $(seq 1 "$node_count"); do
own=$(docker exec "$(vm "$index")" ip -o addr show | awk '{ split($4, parts, "/"); print parts[1] }')
status=$(docker_on "$index" exec "$(container "$index")" tailscale status --json </dev/null)
outside=$(echo "$status" | python3 -c 'import json, sys
own = set(sys.argv[1].split())
print(" ".join(addr for addr in json.load(sys.stdin)["Self"]["Addrs"] if addr.rsplit(":", 1)[0].strip("[]") not in own))' "$own")
[ -z "$outside" ] || fail "$(node_name "$index") advertises endpoints that are not its own VM's addresses: ${outside}"
done
log "Every node reaches every other directly at its daemon's address"
}

Expand Down
Loading