mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Cover UDP forward, multi-forward, and multi-client paths in gateway-test
Extend the gateway integration suite with three previously unexercised runtime paths. All three share testing/static/scripts/gateway-test.sh and testing/static/docker-compose.yml so they land as one commit. 6A — UDP port forwarding runtime path. Add udp 18081 -> [fd02::20]:8081 to inject_gateway_config() and a phase-7 case where gw-client runs an inline Python UDP echo server bound [::]:8081 and gw-server sends a UDP probe to [GW_MESH_IP]:18081 via inline python3, asserting the echoed payload prefix. The config layer already accepted proto: udp (test_port_forwards_same_port_different_proto_ok) but the UDP NAT rule shape and conntrack handling differ from TCP and were unverified. Uses Python rather than socat because fips-test:latest does not ship socat; nc -u IPv6 round-trip semantics are messier than a Python one-liner. 6B — Second simultaneous TCP forward. Add tcp 18082 -> [fd02::20]:8081 alongside the existing 18080 forward. Phase 7 now greps the daemon's nft DNAT table for all three rules (18080, 18082, 18081) and runs HTTP fetches through both TCP forwards with distinct backend payloads (inbound-forward-ok vs inbound-forward-ok-2) so a misrouted response fails the assertion. 11A — Concurrent multi-client flows. Add gw-client-2 service to docker-compose mirroring gw-client (IPv6 fd02::21, IPv4 172.20.1.21). Phase 3 sets the fd01::/112 route on both. Phase 4 issues DNS lookups from both, asserts they receive distinct virtual IPs, and queries the gateway control socket (show_mappings) to confirm exactly 2 active mappings (5-attempt retry loop tolerates snapshot-publish lag). Phase 5 launches both curl requests concurrently as background processes, asserts each response. Validates concurrent NAT mappings, pool contention, proxy NDP under simultaneous LAN-client traffic — all real-world deployment shape that was not pinned. Phase 8 reclamation timing unchanged (TTL=5s, grace=5s, 25s wait covers both mapping ticks generously even with a slight stagger).
This commit is contained in:
@@ -1,6 +1,14 @@
|
|||||||
# Gateway Integration Test Topology
|
# Gateway Integration Test Topology
|
||||||
#
|
#
|
||||||
# Two FIPS nodes: gateway (a) and server (b), directly peered.
|
# Three FIPS nodes:
|
||||||
|
# a (gw-gateway) — gateway with LAN interface
|
||||||
|
# b (gw-server) — first mesh destination (LAN client #1 target)
|
||||||
|
# c (gw-server-2) — second mesh destination (LAN client #2 target)
|
||||||
|
#
|
||||||
|
# Node `a` is directly peered with both `b` and `c`. Two distinct mesh
|
||||||
|
# destinations are required so the gateway-test multi-client phase can
|
||||||
|
# allocate distinct virtual-IP mappings (one per LAN client).
|
||||||
|
#
|
||||||
# A non-FIPS client container connects via the gateway's LAN interface.
|
# A non-FIPS client container connects via the gateway's LAN interface.
|
||||||
#
|
#
|
||||||
# Uses deterministic key derivation (mesh-name: gateway-test).
|
# Uses deterministic key derivation (mesh-name: gateway-test).
|
||||||
@@ -8,8 +16,12 @@
|
|||||||
nodes:
|
nodes:
|
||||||
a:
|
a:
|
||||||
docker_ip: "172.20.0.10"
|
docker_ip: "172.20.0.10"
|
||||||
peers: [b]
|
peers: [b, c]
|
||||||
|
|
||||||
b:
|
b:
|
||||||
docker_ip: "172.20.0.11"
|
docker_ip: "172.20.0.11"
|
||||||
peers: [a]
|
peers: [a]
|
||||||
|
|
||||||
|
c:
|
||||||
|
docker_ip: "172.20.0.12"
|
||||||
|
peers: [a]
|
||||||
|
|||||||
@@ -512,6 +512,21 @@ services:
|
|||||||
fips-net:
|
fips-net:
|
||||||
ipv4_address: 172.20.0.11
|
ipv4_address: 172.20.0.11
|
||||||
|
|
||||||
|
# Second mesh destination — gives gw-client-2 a distinct npub to target
|
||||||
|
# so the gateway allocates a separate virtual-IP mapping per LAN client.
|
||||||
|
# Mirrors gw-server; not on gateway-lan.
|
||||||
|
gw-server-2:
|
||||||
|
<<: *fips-common
|
||||||
|
profiles: ["gateway"]
|
||||||
|
container_name: fips-gw-server-2
|
||||||
|
hostname: gw-server-2
|
||||||
|
volumes:
|
||||||
|
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||||
|
- ./generated-configs/gateway/node-c.yaml:/etc/fips/fips.yaml:ro
|
||||||
|
networks:
|
||||||
|
fips-net:
|
||||||
|
ipv4_address: 172.20.0.12
|
||||||
|
|
||||||
gw-client:
|
gw-client:
|
||||||
image: fips-test-app:latest
|
image: fips-test-app:latest
|
||||||
profiles: ["gateway"]
|
profiles: ["gateway"]
|
||||||
@@ -530,3 +545,25 @@ services:
|
|||||||
restart: "no"
|
restart: "no"
|
||||||
env_file:
|
env_file:
|
||||||
- ./generated-configs/npubs.env
|
- ./generated-configs/npubs.env
|
||||||
|
|
||||||
|
# Second LAN client — exercises concurrent multi-client mappings.
|
||||||
|
# Same image and gateway-lan attachment as
|
||||||
|
# gw-client; the gateway must allocate a distinct virtual IP for it.
|
||||||
|
gw-client-2:
|
||||||
|
image: fips-test-app:latest
|
||||||
|
profiles: ["gateway"]
|
||||||
|
container_name: fips-gw-client-2
|
||||||
|
hostname: gw-client-2
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
sysctls:
|
||||||
|
- net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
volumes:
|
||||||
|
- ./configs/gateway-resolv.conf:/etc/resolv.conf:ro
|
||||||
|
networks:
|
||||||
|
gateway-lan:
|
||||||
|
ipv4_address: 172.20.1.21
|
||||||
|
ipv6_address: fd02::21
|
||||||
|
restart: "no"
|
||||||
|
env_file:
|
||||||
|
- ./generated-configs/npubs.env
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ ENV_FILE="$GENERATED_DIR/npubs.env"
|
|||||||
|
|
||||||
GATEWAY="fips-gw-gateway"
|
GATEWAY="fips-gw-gateway"
|
||||||
SERVER="fips-gw-server"
|
SERVER="fips-gw-server"
|
||||||
|
SERVER2="fips-gw-server-2"
|
||||||
CLIENT="fips-gw-client"
|
CLIENT="fips-gw-client"
|
||||||
|
CLIENT2="fips-gw-client-2"
|
||||||
|
|
||||||
# ── inject-config subcommand ─────────────────────────────────────────────
|
# ── inject-config subcommand ─────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -58,6 +60,20 @@ cfg['gateway'] = {
|
|||||||
'proto': 'tcp',
|
'proto': 'tcp',
|
||||||
'target': '[fd02::20]:8080',
|
'target': '[fd02::20]:8080',
|
||||||
},
|
},
|
||||||
|
# 6B: second TCP forward — exercises multiple simultaneous TCP
|
||||||
|
# rules sharing the same LAN backend on a different listen port.
|
||||||
|
{
|
||||||
|
'listen_port': 18082,
|
||||||
|
'proto': 'tcp',
|
||||||
|
'target': '[fd02::20]:8081',
|
||||||
|
},
|
||||||
|
# 6A: UDP forward — exercises the runtime UDP DNAT path (rule
|
||||||
|
# shape + conntrack handling) end-to-end.
|
||||||
|
{
|
||||||
|
'listen_port': 18081,
|
||||||
|
'proto': 'udp',
|
||||||
|
'target': '[fd02::20]:8081',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,10 +116,11 @@ check() {
|
|||||||
echo "=== FIPS Gateway Integration Test ==="
|
echo "=== FIPS Gateway Integration Test ==="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Phase 1: Wait for mesh convergence (gateway ↔ server)
|
# Phase 1: Wait for mesh convergence (gateway ↔ server, gateway ↔ server-2)
|
||||||
echo "Phase 1: Mesh convergence"
|
echo "Phase 1: Mesh convergence"
|
||||||
wait_for_peers "$GATEWAY" 1 30 || true
|
wait_for_peers "$GATEWAY" 2 30 || true
|
||||||
wait_for_peers "$SERVER" 1 30 || true
|
wait_for_peers "$SERVER" 1 30 || true
|
||||||
|
wait_for_peers "$SERVER2" 1 30 || true
|
||||||
|
|
||||||
# Phase 2: Wait for gateway DNS to respond
|
# Phase 2: Wait for gateway DNS to respond
|
||||||
echo ""
|
echo ""
|
||||||
@@ -130,35 +147,114 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
echo "Phase 3: Client network setup"
|
echo "Phase 3: Client network setup"
|
||||||
docker exec "$CLIENT" ip -6 route add fd01::/112 via fd02::10 2>/dev/null || true
|
docker exec "$CLIENT" ip -6 route add fd01::/112 via fd02::10 2>/dev/null || true
|
||||||
echo " Added route fd01::/112 via fd02::10"
|
echo " Added route fd01::/112 via fd02::10 on $CLIENT"
|
||||||
|
docker exec "$CLIENT2" ip -6 route add fd01::/112 via fd02::10 2>/dev/null || true
|
||||||
|
echo " Added route fd01::/112 via fd02::10 on $CLIENT2"
|
||||||
|
|
||||||
# Phase 4: DNS resolution test — resolve server npub from client
|
# Phase 4: DNS resolution test — resolve server npub from both clients,
|
||||||
|
# exercising concurrent multi-client mappings.
|
||||||
echo ""
|
echo ""
|
||||||
echo "Phase 4: DNS resolution"
|
echo "Phase 4: DNS resolution"
|
||||||
VIRTUAL_IP=$(docker exec "$CLIENT" dig +short AAAA "${NPUB_B}.fips" @fd02::10 2>/dev/null | head -1)
|
VIRTUAL_IP=$(docker exec "$CLIENT" dig +short AAAA "${NPUB_B}.fips" @fd02::10 2>/dev/null | head -1)
|
||||||
if [ -n "$VIRTUAL_IP" ] && echo "$VIRTUAL_IP" | grep -q "fd01"; then
|
if [ -n "$VIRTUAL_IP" ] && echo "$VIRTUAL_IP" | grep -q "fd01"; then
|
||||||
check "Resolve ${NPUB_B:0:20}...fips → $VIRTUAL_IP" 0
|
check "Resolve ${NPUB_B:0:20}...fips on $CLIENT → $VIRTUAL_IP" 0
|
||||||
else
|
else
|
||||||
check "Resolve ${NPUB_B:0:20}...fips (got: '$VIRTUAL_IP')" 1
|
check "Resolve ${NPUB_B:0:20}...fips on $CLIENT (got: '$VIRTUAL_IP')" 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Phase 5: End-to-end HTTP test
|
VIRTUAL_IP_2=$(docker exec "$CLIENT2" dig +short AAAA "${NPUB_C}.fips" @fd02::10 2>/dev/null | head -1)
|
||||||
|
if [ -n "$VIRTUAL_IP_2" ] && echo "$VIRTUAL_IP_2" | grep -q "fd01"; then
|
||||||
|
check "Resolve ${NPUB_C:0:20}...fips on $CLIENT2 → $VIRTUAL_IP_2" 0
|
||||||
|
else
|
||||||
|
check "Resolve ${NPUB_C:0:20}...fips on $CLIENT2 (got: '$VIRTUAL_IP_2')" 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Both clients must receive distinct virtual-IP mappings — this is the
|
||||||
|
# core multi-client invariant: each LAN client gets its own pool entry.
|
||||||
|
if [ -n "$VIRTUAL_IP" ] && [ -n "$VIRTUAL_IP_2" ] && [ "$VIRTUAL_IP" != "$VIRTUAL_IP_2" ]; then
|
||||||
|
check "Distinct virtual IPs per client ($VIRTUAL_IP vs $VIRTUAL_IP_2)" 0
|
||||||
|
else
|
||||||
|
check "Distinct virtual IPs per client (got: '$VIRTUAL_IP' vs '$VIRTUAL_IP_2')" 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify gateway show_mappings reports both client mappings. Mapping
|
||||||
|
# allocation happens in the DNS response path, but the gateway control
|
||||||
|
# socket serves a snapshot that is refreshed on a 10s tick (see
|
||||||
|
# src/bin/fips-gateway.rs tick interval). Poll up to 15s so at least
|
||||||
|
# one post-allocation snapshot tick is guaranteed to land.
|
||||||
|
ACTIVE_COUNT="error"
|
||||||
|
# Control socket protocol is line-delimited JSON ({"command": "..."});
|
||||||
|
# bare "show_mappings" returns an "invalid request" error response with
|
||||||
|
# no data field and the parse below counts that as 0 mappings.
|
||||||
|
for _ in $(seq 1 15); do
|
||||||
|
GW_MAPPINGS=$(docker exec "$GATEWAY" bash -c \
|
||||||
|
'echo "{\"command\":\"show_mappings\"}" | nc -U -w1 /run/fips/gateway.sock 2>/dev/null' || echo "")
|
||||||
|
ACTIVE_COUNT=$(echo "$GW_MAPPINGS" \
|
||||||
|
| python3 -c "import sys,json; r=json.load(sys.stdin); print(len(r.get('data',{}).get('mappings',[])))" 2>/dev/null || echo "error")
|
||||||
|
if [ "$ACTIVE_COUNT" = "2" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if [ "$ACTIVE_COUNT" = "2" ]; then
|
||||||
|
check "Gateway reports 2 active mappings (multi-client)" 0
|
||||||
|
else
|
||||||
|
check "Gateway active mapping count (got: $ACTIVE_COUNT)" 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Phase 5: End-to-end HTTP test from both clients in parallel
|
||||||
echo ""
|
echo ""
|
||||||
echo "Phase 5: HTTP through gateway"
|
echo "Phase 5: HTTP through gateway"
|
||||||
|
|
||||||
# Use --resolve to bind the .fips hostname to the virtual IP for curl
|
# Use --resolve to bind the .fips hostname to the virtual IP for curl.
|
||||||
if [ -n "$VIRTUAL_IP" ]; then
|
# Run both client requests concurrently to exercise simultaneous flows
|
||||||
RESPONSE=$(docker exec "$CLIENT" curl -6 -s --max-time 10 \
|
# through distinct NAT mappings.
|
||||||
--resolve "${NPUB_B}.fips:8000:[$VIRTUAL_IP]" \
|
RESP_FILE=$(mktemp)
|
||||||
"http://${NPUB_B}.fips:8000/" 2>&1) || true
|
RESP_FILE_2=$(mktemp)
|
||||||
|
trap 'rm -f "$RESP_FILE" "$RESP_FILE_2"' EXIT
|
||||||
|
|
||||||
|
if [ -n "$VIRTUAL_IP" ]; then
|
||||||
|
docker exec "$CLIENT" curl -6 -s --max-time 10 \
|
||||||
|
--resolve "${NPUB_B}.fips:8000:[$VIRTUAL_IP]" \
|
||||||
|
"http://${NPUB_B}.fips:8000/" >"$RESP_FILE" 2>&1 &
|
||||||
|
PID1=$!
|
||||||
|
else
|
||||||
|
PID1=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$VIRTUAL_IP_2" ]; then
|
||||||
|
docker exec "$CLIENT2" curl -6 -s --max-time 10 \
|
||||||
|
--resolve "${NPUB_C}.fips:8000:[$VIRTUAL_IP_2]" \
|
||||||
|
"http://${NPUB_C}.fips:8000/" >"$RESP_FILE_2" 2>&1 &
|
||||||
|
PID2=$!
|
||||||
|
else
|
||||||
|
PID2=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -n "$PID1" ] && wait "$PID1" || true
|
||||||
|
[ -n "$PID2" ] && wait "$PID2" || true
|
||||||
|
|
||||||
|
RESPONSE=$(cat "$RESP_FILE")
|
||||||
|
RESPONSE_2=$(cat "$RESP_FILE_2")
|
||||||
|
|
||||||
|
if [ -n "$VIRTUAL_IP" ]; then
|
||||||
if echo "$RESPONSE" | grep -q "Fuck IPs"; then
|
if echo "$RESPONSE" | grep -q "Fuck IPs"; then
|
||||||
check "HTTP GET ${NPUB_B:0:20}...fips:8000" 0
|
check "HTTP GET from $CLIENT" 0
|
||||||
else
|
else
|
||||||
check "HTTP GET (response: '${RESPONSE:0:80}')" 1
|
check "HTTP GET from $CLIENT (response: '${RESPONSE:0:80}')" 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
check "HTTP GET (skipped — no virtual IP)" 1
|
check "HTTP GET from $CLIENT (skipped — no virtual IP)" 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$VIRTUAL_IP_2" ]; then
|
||||||
|
if echo "$RESPONSE_2" | grep -q "Fuck IPs"; then
|
||||||
|
check "HTTP GET from $CLIENT2" 0
|
||||||
|
else
|
||||||
|
check "HTTP GET from $CLIENT2 (response: '${RESPONSE_2:0:80}')" 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
check "HTTP GET from $CLIENT2 (skipped — no virtual IP)" 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Phase 6: Verify NAT state on gateway
|
# Phase 6: Verify NAT state on gateway
|
||||||
@@ -172,34 +268,74 @@ else
|
|||||||
check "nftables DNAT rules" 1
|
check "nftables DNAT rules" 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Phase 7: Inbound port forwarding (TASK-2026-0061)
|
# Phase 7: Inbound port forwarding — UDP and a second simultaneous TCP forward.
|
||||||
#
|
#
|
||||||
# Mesh peer (gw-server) → gw-gateway fips0:18080 → DNAT → [fd02::20]:8080
|
# Three forwards exercised:
|
||||||
# (gw-client LAN HTTP server). Exercises the DNAT rule + LAN-side
|
# tcp 18080 → [fd02::20]:8080 (original — single TCP rule)
|
||||||
|
# tcp 18082 → [fd02::20]:8081 (6B — second TCP rule, multiple forwards)
|
||||||
|
# udp 18081 → [fd02::20]:8081 (6A — UDP DNAT runtime path)
|
||||||
|
#
|
||||||
|
# Mesh peer (gw-server) hits each gw-gateway fips0:<port> rule, which
|
||||||
|
# DNATs into the LAN-side gw-client. Exercises the DNAT rules + LAN-side
|
||||||
# masquerade installed by set_port_forwards().
|
# masquerade installed by set_port_forwards().
|
||||||
echo ""
|
echo ""
|
||||||
echo "Phase 7: Inbound port forward"
|
echo "Phase 7: Inbound port forwards"
|
||||||
|
|
||||||
# Confirm the port-forward DNAT rule is present on the gateway. The
|
# Confirm all three port-forward DNAT rules are present on the gateway.
|
||||||
# distinctive listen port (18080) identifies our rule regardless of how
|
# The distinctive listen ports identify our rules regardless of how nft
|
||||||
# nft renders the l4proto/dport predicates.
|
# renders the l4proto/dport predicates.
|
||||||
if echo "$NFT_RULES" | grep -q "18080"; then
|
if echo "$NFT_RULES" | grep -q "18080"; then
|
||||||
check "nftables port-forward DNAT rule (tcp 18080)" 0
|
check "nftables port-forward DNAT rule (tcp 18080)" 0
|
||||||
else
|
else
|
||||||
check "nftables port-forward DNAT rule (tcp 18080)" 1
|
check "nftables port-forward DNAT rule (tcp 18080)" 1
|
||||||
fi
|
fi
|
||||||
|
if echo "$NFT_RULES" | grep -q "18082"; then
|
||||||
|
check "nftables port-forward DNAT rule (tcp 18082)" 0
|
||||||
|
else
|
||||||
|
check "nftables port-forward DNAT rule (tcp 18082)" 1
|
||||||
|
fi
|
||||||
|
if echo "$NFT_RULES" | grep -q "18081"; then
|
||||||
|
check "nftables port-forward DNAT rule (udp 18081)" 0
|
||||||
|
else
|
||||||
|
check "nftables port-forward DNAT rule (udp 18081)" 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Start a marker HTTP server on the LAN-side client (fd02::20:8080).
|
# Start marker HTTP servers on the LAN-side client.
|
||||||
|
# :8080 → "inbound-forward-ok" (target of tcp 18080)
|
||||||
|
# :8081 → "inbound-forward-ok-2" (target of tcp 18082)
|
||||||
# `docker exec -d` is required; `docker exec bash -c 'cmd &'` doesn't
|
# `docker exec -d` is required; `docker exec bash -c 'cmd &'` doesn't
|
||||||
# keep the child alive past the exec session, even with nohup.
|
# keep the child alive past the exec session, even with nohup.
|
||||||
docker exec "$CLIENT" sh -c \
|
docker exec "$CLIENT" sh -c '
|
||||||
'mkdir -p /tmp/inbound && echo "inbound-forward-ok" > /tmp/inbound/index.html && pkill -f "http.server 8080" 2>/dev/null || true' \
|
mkdir -p /tmp/inbound /tmp/inbound2
|
||||||
>/dev/null 2>&1 || true
|
echo "inbound-forward-ok" > /tmp/inbound/index.html
|
||||||
|
echo "inbound-forward-ok-2" > /tmp/inbound2/index.html
|
||||||
|
pkill -f "http.server 8080" 2>/dev/null || true
|
||||||
|
pkill -f "http.server 8081" 2>/dev/null || true
|
||||||
|
pkill -f "udp_echo.py" 2>/dev/null || true
|
||||||
|
' >/dev/null 2>&1 || true
|
||||||
docker exec -d "$CLIENT" python3 -m http.server 8080 --bind :: --directory /tmp/inbound \
|
docker exec -d "$CLIENT" python3 -m http.server 8080 --bind :: --directory /tmp/inbound \
|
||||||
>/dev/null 2>&1 || true
|
>/dev/null 2>&1 || true
|
||||||
# Give the server a moment to bind.
|
docker exec -d "$CLIENT" python3 -m http.server 8081 --bind :: --directory /tmp/inbound2 \
|
||||||
|
>/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Start a UDP echo server on the LAN-side client at [::]:8081/udp.
|
||||||
|
# This is the target of the udp 18081 forward. Stash the script as a
|
||||||
|
# named file (`udp_echo.py`) so the cleanup pkill above can find it.
|
||||||
|
docker exec "$CLIENT" sh -c 'cat > /tmp/udp_echo.py <<'\''PYEOF'\''
|
||||||
|
import socket, sys
|
||||||
|
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||||
|
s.bind(("::", 8081))
|
||||||
|
while True:
|
||||||
|
data, addr = s.recvfrom(2048)
|
||||||
|
s.sendto(b"udp-forward-ok:" + data, addr)
|
||||||
|
PYEOF' >/dev/null 2>&1 || true
|
||||||
|
docker exec -d "$CLIENT" python3 /tmp/udp_echo.py >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Give the servers a moment to bind.
|
||||||
for _ in 1 2 3 4 5; do
|
for _ in 1 2 3 4 5; do
|
||||||
if docker exec "$CLIENT" ss -6lnt 2>/dev/null | grep -q ':8080'; then
|
TCP_READY=$(docker exec "$CLIENT" ss -6lnt 2>/dev/null | grep -cE ':8080|:8081' || true)
|
||||||
|
UDP_READY=$(docker exec "$CLIENT" ss -6lnu 2>/dev/null | grep -c ':8081' || true)
|
||||||
|
if [ "$TCP_READY" -ge 2 ] && [ "$UDP_READY" -ge 1 ]; then
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
sleep 1
|
sleep 1
|
||||||
@@ -215,16 +351,53 @@ if [ -z "$GW_MESH_IP" ]; then
|
|||||||
else
|
else
|
||||||
echo " Gateway mesh IPv6: $GW_MESH_IP"
|
echo " Gateway mesh IPv6: $GW_MESH_IP"
|
||||||
|
|
||||||
# From the mesh side (gw-server), fetch through the forward rule.
|
# From the mesh side (gw-server), fetch through each TCP forward.
|
||||||
FWD_RESPONSE=$(docker exec "$SERVER" curl -6 -s --max-time 10 \
|
FWD_RESPONSE=$(docker exec "$SERVER" curl -6 -s --max-time 10 \
|
||||||
"http://[${GW_MESH_IP}]:18080/" 2>&1) || true
|
"http://[${GW_MESH_IP}]:18080/" 2>&1) || true
|
||||||
if echo "$FWD_RESPONSE" | grep -q "inbound-forward-ok"; then
|
# 8080 backend serves "inbound-forward-ok" (no -2 suffix) — distinct
|
||||||
check "Inbound HTTP via port forward 18080 → [fd02::20]:8080" 0
|
# from the 8081 backend so a misrouted response would be detectable.
|
||||||
|
if echo "$FWD_RESPONSE" | grep -qE '^inbound-forward-ok$'; then
|
||||||
|
check "Inbound HTTP via TCP forward 18080 → [fd02::20]:8080" 0
|
||||||
else
|
else
|
||||||
check "Inbound HTTP via port forward (response: '${FWD_RESPONSE:0:80}')" 1
|
check "Inbound HTTP via TCP forward 18080 (response: '${FWD_RESPONSE:0:80}')" 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
FWD_RESPONSE_2=$(docker exec "$SERVER" curl -6 -s --max-time 10 \
|
||||||
|
"http://[${GW_MESH_IP}]:18082/" 2>&1) || true
|
||||||
|
if echo "$FWD_RESPONSE_2" | grep -q "inbound-forward-ok-2"; then
|
||||||
|
check "Inbound HTTP via TCP forward 18082 → [fd02::20]:8081 (6B)" 0
|
||||||
|
else
|
||||||
|
check "Inbound HTTP via TCP forward 18082 (response: '${FWD_RESPONSE_2:0:80}')" 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6A: UDP forward. Send a probe via a one-shot Python client on
|
||||||
|
# gw-server; the LAN-side echo server prepends "udp-forward-ok:".
|
||||||
|
UDP_RESPONSE=$(docker exec "$SERVER" python3 -c "
|
||||||
|
import socket, sys
|
||||||
|
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||||
|
s.settimeout(5)
|
||||||
|
s.sendto(b'ping-via-udp-fwd', ('${GW_MESH_IP}', 18081))
|
||||||
|
try:
|
||||||
|
data, _ = s.recvfrom(2048)
|
||||||
|
sys.stdout.write(data.decode('utf-8', 'replace'))
|
||||||
|
except Exception as e:
|
||||||
|
sys.stdout.write('ERR: ' + str(e))
|
||||||
|
" 2>&1) || true
|
||||||
|
if echo "$UDP_RESPONSE" | grep -q "udp-forward-ok:ping-via-udp-fwd"; then
|
||||||
|
check "Inbound UDP via forward 18081 → [fd02::20]:8081 (6A)" 0
|
||||||
|
else
|
||||||
|
check "Inbound UDP via forward 18081 (response: '${UDP_RESPONSE:0:80}')" 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Cleanup: stop the LAN-side responders so Phase 8's pool-reclamation
|
||||||
|
# wait isn't interfered with by lingering sessions.
|
||||||
|
docker exec "$CLIENT" sh -c '
|
||||||
|
pkill -f "http.server 8080" 2>/dev/null || true
|
||||||
|
pkill -f "http.server 8081" 2>/dev/null || true
|
||||||
|
pkill -f "udp_echo.py" 2>/dev/null || true
|
||||||
|
' >/dev/null 2>&1 || true
|
||||||
|
|
||||||
# Phase 8: TTL expiration and pool reclamation
|
# Phase 8: TTL expiration and pool reclamation
|
||||||
echo ""
|
echo ""
|
||||||
echo "Phase 8: TTL expiration and pool reclamation"
|
echo "Phase 8: TTL expiration and pool reclamation"
|
||||||
@@ -239,7 +412,7 @@ sleep 25
|
|||||||
|
|
||||||
# Query gateway control socket for mapping count
|
# Query gateway control socket for mapping count
|
||||||
MAPPING_COUNT=$(docker exec "$GATEWAY" bash -c \
|
MAPPING_COUNT=$(docker exec "$GATEWAY" bash -c \
|
||||||
'echo "show_mappings" | nc -U -w1 /run/fips/gateway.sock 2>/dev/null' \
|
'echo "{\"command\":\"show_mappings\"}" | nc -U -w1 /run/fips/gateway.sock 2>/dev/null' \
|
||||||
| python3 -c "import sys,json; r=json.load(sys.stdin); print(len(r.get('data',{}).get('mappings',[])))" 2>/dev/null || echo "error")
|
| python3 -c "import sys,json; r=json.load(sys.stdin); print(len(r.get('data',{}).get('mappings',[])))" 2>/dev/null || echo "error")
|
||||||
if [ "$MAPPING_COUNT" = "0" ]; then
|
if [ "$MAPPING_COUNT" = "0" ]; then
|
||||||
check "Mapping reclaimed after TTL+grace" 0
|
check "Mapping reclaimed after TTL+grace" 0
|
||||||
|
|||||||
Reference in New Issue
Block a user