feat: end-to-end relay test scripts

Three Python scripts send real packets from the kind node through the
full relay path (biscayne → tunnel → mia-sw01 → was-sw01 → internet)
and verify responses come back via the inbound path. No indirect
counter-checking — a response proves both directions work.

- relay-test-udp.py: DNS query with sport 8001
- relay-test-tcp-sport.py: HTTP request with sport 8001
- relay-test-tcp-dport.py: TCP connect to entrypoint dport 8001 (ip_echo)
- test-ashburn-relay.sh: orchestrates from ansible controller via nsenter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix/kind-mount-propagation
A. F. Dudley 2026-03-08 00:43:06 +00:00
parent 8eac9cc87f
commit 496c7982cb
4 changed files with 213 additions and 0 deletions

View File

@ -0,0 +1,46 @@
#!/usr/bin/env python3
"""TCP dport 8001 round trip — connect to a Solana entrypoint (ip_echo path).
The mangle rule matches -p tcp --dport 8001, so connecting TO port 8001
on any host triggers SNAT to the relay IP. The entrypoint responds with
ip_echo (4 bytes: our IP in network order).
"""
import socket
import sys
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8001
HOST = sys.argv[2] if len(sys.argv) > 2 else "34.83.231.102" # entrypoint.mainnet-beta.solana.com
# Resolve hostname
try:
addr = socket.getaddrinfo(HOST, PORT, socket.AF_INET)[0][4][0]
except socket.gaierror:
addr = HOST
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
try:
s.connect((addr, PORT))
print(f"OK TCP handshake to {addr}:{PORT}")
# ip_echo: peer sends our IP back as 4 bytes
s.settimeout(2)
try:
data = s.recv(64)
if len(data) >= 4:
ip = socket.inet_ntoa(data[:4])
print(f"OK ip_echo says we are {ip}")
else:
print(f"OK got {len(data)} bytes: {data.hex()}")
except socket.timeout:
print("NOTE: no ip_echo response (handshake succeeded)")
except socket.timeout:
print("TIMEOUT")
sys.exit(1)
except ConnectionRefusedError:
print(f"OK connection refused by {addr}:{PORT} (host reachable)")
except Exception as e:
print(f"ERROR {e}")
sys.exit(1)
finally:
s.close()

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
"""TCP sport 8001 round trip via HTTP HEAD to 1.1.1.1."""
import socket
import sys
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8001
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("0.0.0.0", PORT))
try:
s.connect(("1.1.1.1", 80))
s.sendall(b"HEAD / HTTP/1.0\r\nHost: 1.1.1.1\r\n\r\n")
resp = s.recv(256)
if b"HTTP" in resp:
print("OK HTTP response received")
else:
print(f"OK {len(resp)} bytes (non-HTTP)")
except socket.timeout:
print("TIMEOUT")
sys.exit(1)
except Exception as e:
print(f"ERROR {e}")
sys.exit(1)
finally:
s.close()

View File

@ -0,0 +1,30 @@
#!/usr/bin/env python3
"""UDP sport 8001 round trip via DNS query to 8.8.8.8."""
import socket
import sys
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8001
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("0.0.0.0", PORT))
# DNS query: txn ID 0x1234, standard query for example.com A
query = (
b"\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00"
b"\x07example\x03com\x00\x00\x01\x00\x01"
)
s.sendto(query, ("8.8.8.8", 53))
s.settimeout(5)
try:
resp, addr = s.recvfrom(512)
print(f"OK {len(resp)} bytes from {addr[0]}:{addr[1]}")
except socket.timeout:
print("TIMEOUT")
sys.exit(1)
except Exception as e:
print(f"ERROR {e}")
sys.exit(1)
finally:
s.close()

View File

@ -0,0 +1,109 @@
#!/usr/bin/env bash
# End-to-end test for Ashburn validator relay
#
# Sends real packets from the kind node through the full relay path
# and waits for responses. A response proves both directions work.
#
# Outbound: kind node (172.20.0.2:8001) → biscayne mangle (fwmark 0x64)
# → policy route table ashburn → gre-ashburn → mia-sw01 Tunnel100
# (VRF relay) → egress-vrf default → backbone Et4/1 → was-sw01 Et1/1
# → internet (src 137.239.194.65)
#
# Inbound: internet → was-sw01 Et1/1 (dst 137.239.194.65) → static route
# → backbone → mia-sw01 → egress-vrf relay → Tunnel100 → biscayne
# gre-ashburn → conntrack reverse-SNAT → kind node (172.20.0.2:8001)
#
# Runs from the ansible controller host.
#
# Usage:
# ./scripts/test-ashburn-relay.sh
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR/.."
KIND_NODE=laconic-70ce4c4b47e23b85-control-plane
BISCAYNE_INV=inventory/biscayne.yml
GOSSIP_PORT=8001
PASS=0
FAIL=0
pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
# Copy test scripts to biscayne (once)
setup() {
for f in "$SCRIPT_DIR"/relay-test-*.py; do
ansible biscayne -i "$BISCAYNE_INV" -m ansible.builtin.copy \
-a "src=$f dest=/tmp/$(basename "$f") mode=0755" \
--become >/dev/null 2>&1
done
# Get kind node PID for nsenter (run in its network namespace,
# use biscayne's python3 since the kind node only has perl)
KIND_PID=$(ansible biscayne -i "$BISCAYNE_INV" -m ansible.builtin.shell \
-a "docker inspect --format '{{ '{{' }}.State.Pid{{ '}}' }}' $KIND_NODE" \
--become 2>&1 | grep -oP '^\d+$' || true)
if [[ -z "$KIND_PID" ]]; then
echo "FATAL: could not get kind node PID"
exit 1
fi
echo "Kind node PID: $KIND_PID"
}
# Run a test script in the kind node's network namespace
run_test() {
local name=$1
shift
ansible biscayne -i "$BISCAYNE_INV" -m ansible.builtin.shell \
-a "nsenter --net --target $KIND_PID python3 /tmp/$name $*" \
--become 2>&1 | grep -E '^OK|^TIMEOUT|^ERROR|^REFUSED|^NOTE' || echo "NO OUTPUT"
}
echo "=== Ashburn Relay End-to-End Test ==="
echo ""
setup
echo ""
# Test 1: UDP sport 8001 → DNS query to 8.8.8.8
# Triggers: mangle -p udp --sport 8001 → mark → SNAT → tunnel
echo "--- Test 1: UDP sport $GOSSIP_PORT (DNS query) ---"
result=$(run_test relay-test-udp.py "$GOSSIP_PORT")
if echo "$result" | grep -q "^OK"; then
pass "UDP sport $GOSSIP_PORT: $result"
else
fail "UDP sport $GOSSIP_PORT: $result"
fi
echo ""
# Test 2: TCP sport 8001 → HTTP HEAD to 1.1.1.1
# Triggers: mangle -p tcp --sport 8001 → mark → SNAT → tunnel
echo "--- Test 2: TCP sport $GOSSIP_PORT (HTTP request) ---"
result=$(run_test relay-test-tcp-sport.py "$GOSSIP_PORT")
if echo "$result" | grep -q "^OK"; then
pass "TCP sport $GOSSIP_PORT: $result"
else
fail "TCP sport $GOSSIP_PORT: $result"
fi
echo ""
# Test 3: TCP dport 8001 → connect to Solana entrypoint (ip_echo)
# Triggers: mangle -p tcp --dport 8001 → mark → SNAT → tunnel
# REFUSED counts as pass — proves the round trip completed.
echo "--- Test 3: TCP dport $GOSSIP_PORT (ip_echo path) ---"
result=$(run_test relay-test-tcp-dport.py "$GOSSIP_PORT")
if echo "$result" | grep -q "^OK"; then
pass "TCP dport $GOSSIP_PORT: $result"
else
fail "TCP dport $GOSSIP_PORT: $result"
fi
echo ""
# Summary
echo "=== Results: $PASS passed, $FAIL failed ==="
if [[ $FAIL -gt 0 ]]; then
exit 1
fi