Files
fips/testing/nat/docker-compose.yml
T
Johnathan Corgan 2fdc831ce3 Claim the NAT lab's two bridges per run and derive every address from them
The NAT lab was the last suite pinning fixed IPv4 subnets, so two overlapping
runs collided on the wan and shared-lan bridges.

Each run now claims a free /24 for each bridge, scanning candidates and
advancing on an overlap while still failing fast on any other network-create
error. The claim exports NAT_WAN_PREFIX and NAT_LAN_PREFIX, and every routable
address in the compose file and the suite scripts derives from them, with
defaults that render exactly what the lab used before. The router-side LANs are
deliberately left pinned: they live inside per-container network namespaces,
never become docker networks, and cannot collide.

An external-network overlay lets the suites attach to the networks the run
already claimed instead of creating their own. Two of the three suite scripts
had no overlay hook, so they would have requested the claimed range a second
time; both now have one.

Host veth names are scoped by a short token rather than the full run id, which
overruns the fifteen-character interface-name limit at the default run-id
length, and the cleanup reaper's pattern is widened to match the new shape.

Networks are released inline on every exit path rather than in a trap, since a
trap written inside a shell function replaces the script-level handler and
would disable the whole run's teardown.
2026-07-27 14:34:46 +00:00

342 lines
11 KiB
YAML

networks:
wan:
driver: bridge
labels:
- "com.corganlabs.fips-ci=1"
ipam:
config:
- subnet: ${NAT_WAN_PREFIX:-172.31.254}.0/24
shared-lan:
driver: bridge
labels:
- "com.corganlabs.fips-ci=1"
ipam:
config:
- subnet: ${NAT_LAN_PREFIX:-172.31.10}.0/24
volumes:
relay-data:
x-fips-common: &fips-common
image: ${FIPS_TEST_IMAGE:-fips-test:latest}
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
restart: "no"
environment:
- RUST_LOG=info,fips::discovery::nostr=debug,fips::node::lifecycle=debug
services:
relay:
build:
context: ../..
dockerfile: examples/sidecar-nostr-relay/Dockerfile.app
container_name: fips-nat-relay${FIPS_CI_NAME_SUFFIX:-}
restart: "no"
volumes:
- relay-data:/usr/src/app/strfry-db
- ./relay/strfry.conf:/usr/src/app/strfry.conf:ro
- ../docker/resolv.conf:/etc/resolv.conf:ro
networks:
wan:
ipv4_address: ${NAT_WAN_PREFIX:-172.31.254}.30
shared-lan:
ipv4_address: ${NAT_LAN_PREFIX:-172.31.10}.30
stun:
build:
context: ./stun
container_name: fips-nat-stun${FIPS_CI_NAME_SUFFIX:-}
restart: "no"
networks:
wan:
ipv4_address: ${NAT_WAN_PREFIX:-172.31.254}.40
shared-lan:
ipv4_address: ${NAT_LAN_PREFIX:-172.31.10}.40
nat-a:
build:
context: ./router
profiles: ["cone", "symmetric"]
container_name: fips-nat-router-a${FIPS_CI_NAME_SUFFIX:-}
cap_add:
- NET_ADMIN
sysctls:
- net.ipv4.ip_forward=1
restart: "no"
environment:
- NAT_MODE=${NAT_MODE_A:-cone}
- TCP_FORWARD_PORTS=8443
- LAN_IF=eth1
- WAN_IF=eth0
- LAN_HOST=172.31.1.10
- LAN_SUBNET=172.31.1.0/24
- WAN_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
- WAN_GATEWAY=${NAT_WAN_PREFIX:-172.31.254}.1
networks:
wan:
ipv4_address: ${NAT_WAN_PREFIX:-172.31.254}.10
nat-b:
build:
context: ./router
profiles: ["cone", "symmetric"]
container_name: fips-nat-router-b${FIPS_CI_NAME_SUFFIX:-}
cap_add:
- NET_ADMIN
sysctls:
- net.ipv4.ip_forward=1
restart: "no"
environment:
- NAT_MODE=${NAT_MODE_B:-cone}
- TCP_FORWARD_PORTS=8443
- LAN_IF=eth1
- WAN_IF=eth0
- LAN_HOST=172.31.2.10
- LAN_SUBNET=172.31.2.0/24
- WAN_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
- WAN_GATEWAY=${NAT_WAN_PREFIX:-172.31.254}.1
networks:
wan:
ipv4_address: ${NAT_WAN_PREFIX:-172.31.254}.11
cone-a:
<<: *fips-common
profiles: ["cone"]
container_name: fips-nat-cone-a${FIPS_CI_NAME_SUFFIX:-}
hostname: fips-nat-cone-a
depends_on:
- nat-a
- relay
- stun
entrypoint:
- /usr/local/bin/nat-node-entrypoint.sh
environment:
- RUST_LOG=info,fips::discovery::nostr=debug,fips::node::lifecycle=debug
- DATA_IF=eth0
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
- ROUTE_VIA=172.31.1.254
- RELAY_HOST=${NAT_WAN_PREFIX:-172.31.254}.30
- RELAY_PORT=7777
- STUN_HOST=${NAT_WAN_PREFIX:-172.31.254}.40
- STUN_PORT=3478
volumes:
- ../docker/resolv.conf:/etc/resolv.conf:ro
- ./node/entrypoint.sh:/usr/local/bin/nat-node-entrypoint.sh:ro
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/cone/node-a.yaml:/etc/fips/fips.yaml:ro
network_mode: none
cone-b:
<<: *fips-common
profiles: ["cone"]
container_name: fips-nat-cone-b${FIPS_CI_NAME_SUFFIX:-}
hostname: fips-nat-cone-b
depends_on:
- nat-b
- relay
- stun
entrypoint:
- /usr/local/bin/nat-node-entrypoint.sh
environment:
- RUST_LOG=info,fips::discovery::nostr=debug,fips::node::lifecycle=debug
- DATA_IF=eth0
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
- ROUTE_VIA=172.31.2.254
- RELAY_HOST=${NAT_WAN_PREFIX:-172.31.254}.30
- RELAY_PORT=7777
- STUN_HOST=${NAT_WAN_PREFIX:-172.31.254}.40
- STUN_PORT=3478
volumes:
- ../docker/resolv.conf:/etc/resolv.conf:ro
- ./node/entrypoint.sh:/usr/local/bin/nat-node-entrypoint.sh:ro
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/cone/node-b.yaml:/etc/fips/fips.yaml:ro
network_mode: none
symmetric-a:
<<: *fips-common
profiles: ["symmetric"]
container_name: fips-nat-symmetric-a${FIPS_CI_NAME_SUFFIX:-}
hostname: fips-nat-symmetric-a
depends_on:
- nat-a
- relay
- stun
entrypoint:
- /usr/local/bin/nat-node-entrypoint.sh
environment:
- RUST_LOG=info,fips::discovery::nostr=debug,fips::node::lifecycle=debug
- DATA_IF=eth0
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
- ROUTE_VIA=172.31.1.254
- RELAY_HOST=${NAT_WAN_PREFIX:-172.31.254}.30
- RELAY_PORT=7777
- STUN_HOST=${NAT_WAN_PREFIX:-172.31.254}.40
- STUN_PORT=3478
volumes:
- ../docker/resolv.conf:/etc/resolv.conf:ro
- ./node/entrypoint.sh:/usr/local/bin/nat-node-entrypoint.sh:ro
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/symmetric/node-a.yaml:/etc/fips/fips.yaml:ro
network_mode: none
symmetric-b:
<<: *fips-common
profiles: ["symmetric"]
container_name: fips-nat-symmetric-b${FIPS_CI_NAME_SUFFIX:-}
hostname: fips-nat-symmetric-b
depends_on:
- nat-b
- relay
- stun
entrypoint:
- /usr/local/bin/nat-node-entrypoint.sh
environment:
- RUST_LOG=info,fips::discovery::nostr=debug,fips::node::lifecycle=debug
- DATA_IF=eth0
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
- ROUTE_VIA=172.31.2.254
- RELAY_HOST=${NAT_WAN_PREFIX:-172.31.254}.30
- RELAY_PORT=7777
- STUN_HOST=${NAT_WAN_PREFIX:-172.31.254}.40
- STUN_PORT=3478
volumes:
- ../docker/resolv.conf:/etc/resolv.conf:ro
- ./node/entrypoint.sh:/usr/local/bin/nat-node-entrypoint.sh:ro
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/symmetric/node-b.yaml:/etc/fips/fips.yaml:ro
network_mode: none
lan-a:
<<: *fips-common
profiles: ["lan"]
container_name: fips-nat-lan-a${FIPS_CI_NAME_SUFFIX:-}
hostname: fips-nat-lan-a
depends_on:
- relay
- stun
volumes:
- ../docker/resolv.conf:/etc/resolv.conf:ro
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/lan/node-a.yaml:/etc/fips/fips.yaml:ro
networks:
shared-lan:
ipv4_address: ${NAT_LAN_PREFIX:-172.31.10}.10
lan-b:
<<: *fips-common
profiles: ["lan"]
container_name: fips-nat-lan-b${FIPS_CI_NAME_SUFFIX:-}
hostname: fips-nat-lan-b
depends_on:
- relay
- stun
volumes:
- ../docker/resolv.conf:/etc/resolv.conf:ro
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/lan/node-b.yaml:/etc/fips/fips.yaml:ro
networks:
shared-lan:
ipv4_address: ${NAT_LAN_PREFIX:-172.31.10}.11
# ── Nostr publish/consume profile ──────────────────────────────────────
# Two FIPS daemons + the existing strfry relay, exercising the overlay
# advert publish → relay → consumer round-trip end-to-end. Both nodes
# share the same LAN bridge as the relay (no NAT in the way) so the
# focus of the test is the Nostr discovery layer rather than NAT
# traversal mechanics. Phase 3 (malformed advert) is driven by a
# one-shot publish from the test runner via the relay's WebSocket.
nostr-pub-a:
<<: *fips-common
profiles: ["nostr-publish-consume"]
container_name: fips-nat-nostr-pub-a${FIPS_CI_NAME_SUFFIX:-}
hostname: fips-nat-nostr-pub-a
depends_on:
- relay
- stun
volumes:
- ../docker/resolv.conf:/etc/resolv.conf:ro
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/nostr-publish-consume/node-a.yaml:/etc/fips/fips.yaml:ro
networks:
shared-lan:
ipv4_address: ${NAT_LAN_PREFIX:-172.31.10}.20
nostr-pub-b:
<<: *fips-common
profiles: ["nostr-publish-consume"]
container_name: fips-nat-nostr-pub-b${FIPS_CI_NAME_SUFFIX:-}
hostname: fips-nat-nostr-pub-b
depends_on:
- relay
- stun
volumes:
- ../docker/resolv.conf:/etc/resolv.conf:ro
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/nostr-publish-consume/node-b.yaml:/etc/fips/fips.yaml:ro
networks:
shared-lan:
ipv4_address: ${NAT_LAN_PREFIX:-172.31.10}.21
# ── STUN fault-injection profile ───────────────────────────────────────
# One FIPS daemon + a netns-sharing shim that injects tc/iptables faults
# against UDP egress to the STUN service. The runner script drives the
# shim via `docker exec` (Approach A) — no scripted timing inside the
# shim itself. Three phases:
# 1. drop — 100% UDP egress drop to STUN; assert daemon notices the
# observation timeout and retries.
# 2. delay — ~5s netem delay; assert daemon recovers and STUN succeeds
# again once the rule is removed.
# 3. kill — `docker stop fips-nat-stun`; assert daemon stays up and
# continues to handle "STUN unreachable" gracefully.
# The shim shares the daemon's network namespace so `tc qdisc add dev
# eth0 ...` operates on the daemon's egress path. The shim therefore
# has its own NET_ADMIN cap; the daemon already has one for TUN.
stun-fault-node:
<<: *fips-common
profiles: ["stun-faults"]
container_name: fips-nat-stun-fault-node${FIPS_CI_NAME_SUFFIX:-}
hostname: fips-nat-stun-fault-node
depends_on:
- relay
- stun
volumes:
- ../docker/resolv.conf:/etc/resolv.conf:ro
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/stun-faults/stun-fault-node.yaml:/etc/fips/fips.yaml:ro
networks:
shared-lan:
ipv4_address: ${NAT_LAN_PREFIX:-172.31.10}.50
# Fault-free peer that publishes a valid overlay advert, so the
# fault-node's NAT-traversal attempt actually reaches
# observe_traversal_addresses() (the STUN client). Without this peer the
# daemon would abort with "no overlay advert" and never generate the
# STUN egress that the shim's tc/iptables rules are meant to drop.
# Intentionally has NO fault shim sharing its netns; runs cleanly.
stun-fault-peer:
<<: *fips-common
profiles: ["stun-faults"]
container_name: fips-nat-stun-fault-peer${FIPS_CI_NAME_SUFFIX:-}
hostname: fips-nat-stun-fault-peer
depends_on:
- relay
- stun
volumes:
- ../docker/resolv.conf:/etc/resolv.conf:ro
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/stun-faults/stun-fault-peer.yaml:/etc/fips/fips.yaml:ro
networks:
shared-lan:
ipv4_address: ${NAT_LAN_PREFIX:-172.31.10}.51
stun-fault-shim:
image: ${FIPS_TEST_IMAGE:-fips-test:latest}
profiles: ["stun-faults"]
container_name: fips-nat-stun-fault-shim${FIPS_CI_NAME_SUFFIX:-}
depends_on:
- stun-fault-node
cap_add:
- NET_ADMIN
- NET_RAW
network_mode: "service:stun-fault-node"
restart: "no"
entrypoint:
- /bin/sh
- -c
- "exec sleep infinity"