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
parent
8eac9cc87f
commit
496c7982cb
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue