mirror of
https://github.com/logos-storage/logos-storage-nim.git
synced 2026-07-20 08:10:24 +00:00
Add hole punch test
This commit is contained in:
parent
6395da590a
commit
182cd8df25
43
tests/integration/nat/hole-punch/README.md
Normal file
43
tests/integration/nat/hole-punch/README.md
Normal file
@ -0,0 +1,43 @@
|
||||
# NAT hole-punch scenario
|
||||
|
||||
## Scenario
|
||||
|
||||
Two nodes are each behind their **own** NAT, so neither can reach the other on a
|
||||
shared lan. Both are `NotReachable` and take a relay reservation on A. When D
|
||||
downloads from B through the relay, B drives a **coordinated** DCUtR
|
||||
simultaneous-open and starts hole-punching.
|
||||
|
||||
## Topology
|
||||
|
||||
```
|
||||
node B ── lan1 ── router1 (NAT) ──┐
|
||||
├── wan ── bootstrap A (relay + autonat)
|
||||
node D ── lan2 ── router2 (NAT) ──┘
|
||||
```
|
||||
|
||||
- **bootstrap A** — public node on the wan, autonat + relay server.
|
||||
- **router1 / router2** — `lan -> wan` masquerade, *no* inbound forward.
|
||||
- **node B** — behind router1, NotReachable, uploaded to and downloaded from.
|
||||
Holds the inbound relayed connection, so it is the DCUtR initiator.
|
||||
- **node D** — behind router2, NotReachable, downloads from B through the relay.
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
make testNatIntegration \
|
||||
STORAGE_INTEGRATION_TEST_INCLUDES=tests/integration/nat/hole-punch/testholepunch.nim
|
||||
```
|
||||
|
||||
Rootless, but needs the host netfilter modules — if a router fails on iptables:
|
||||
`sudo modprobe iptable_nat nf_conntrack`.
|
||||
|
||||
## Expected result
|
||||
|
||||
Both nodes are `NotReachable`. D downloads from B through the relay, opening a
|
||||
relayed connection; B then runs DCUtR and the connection is upgraded to a direct
|
||||
one. The test asserts B's log line
|
||||
`Dcutr initiator has directly connected to the remote peer.` — the line is
|
||||
unique to the simultaneous-open path, so it cannot be produced by a reversal.
|
||||
|
||||
Per-run container logs are written before teardown to
|
||||
`tests/integration/logs/<timestamp>__NAT_hole_punching/<test>/<service>.log`.
|
||||
126
tests/integration/nat/hole-punch/compose.yml
Normal file
126
tests/integration/nat/hole-punch/compose.yml
Normal file
@ -0,0 +1,126 @@
|
||||
# NAT hole-punch scenario — see README.md. Run via testholepunch.nim.
|
||||
name: nat-hole-punch
|
||||
|
||||
# Two NATed nodes, one behind each router (separate lans), so neither can reach
|
||||
# the other on a shared lan: the only direct path is a coordinated hole punch.
|
||||
x-addresses:
|
||||
wan_subnet: &wan_subnet 7.7.7.0/24
|
||||
lan1_subnet: &lan1_subnet 10.99.1.0/24
|
||||
lan2_subnet: &lan2_subnet 10.99.2.0/24
|
||||
# A: public bootstrap, autonat + relay server
|
||||
bootstrap_ip: &bootstrap_ip 7.7.7.10
|
||||
# router1 (B's NAT)
|
||||
router1_wan_ip: &router1_wan_ip 7.7.7.3
|
||||
router1_lan_ip: &router1_lan_ip 10.99.1.2
|
||||
# router2 (D's NAT)
|
||||
router2_wan_ip: &router2_wan_ip 7.7.7.4
|
||||
router2_lan_ip: &router2_lan_ip 10.99.2.2
|
||||
# B behind router1, D behind router2
|
||||
node_ip: &node_ip 10.99.1.10
|
||||
peer_ip: &peer_ip 10.99.2.10
|
||||
|
||||
networks:
|
||||
wan:
|
||||
internal: true
|
||||
ipam:
|
||||
config:
|
||||
- subnet: *wan_subnet
|
||||
lan1:
|
||||
ipam:
|
||||
config:
|
||||
- subnet: *lan1_subnet
|
||||
lan2:
|
||||
ipam:
|
||||
config:
|
||||
- subnet: *lan2_subnet
|
||||
|
||||
services:
|
||||
router1:
|
||||
image: localhost/storage-nat
|
||||
cap_add: [NET_ADMIN]
|
||||
sysctls:
|
||||
net.ipv4.ip_forward: 1
|
||||
networks:
|
||||
wan:
|
||||
ipv4_address: *router1_wan_ip
|
||||
lan1:
|
||||
ipv4_address: *router1_lan_ip
|
||||
environment:
|
||||
ROUTER_WAN_IP: *router1_wan_ip
|
||||
LAN_SUBNET: *lan1_subnet
|
||||
volumes:
|
||||
- ../router-common.sh:/scripts/router-common.sh:ro,z
|
||||
- ./router-entrypoint.sh:/scripts/router-entrypoint.sh:ro,z
|
||||
entrypoint: ["bash", "/scripts/router-entrypoint.sh"]
|
||||
|
||||
router2:
|
||||
image: localhost/storage-nat
|
||||
cap_add: [NET_ADMIN]
|
||||
sysctls:
|
||||
net.ipv4.ip_forward: 1
|
||||
networks:
|
||||
wan:
|
||||
ipv4_address: *router2_wan_ip
|
||||
lan2:
|
||||
ipv4_address: *router2_lan_ip
|
||||
environment:
|
||||
ROUTER_WAN_IP: *router2_wan_ip
|
||||
LAN_SUBNET: *lan2_subnet
|
||||
volumes:
|
||||
- ../router-common.sh:/scripts/router-common.sh:ro,z
|
||||
- ./router-entrypoint.sh:/scripts/router-entrypoint.sh:ro,z
|
||||
entrypoint: ["bash", "/scripts/router-entrypoint.sh"]
|
||||
|
||||
bootstrap:
|
||||
image: localhost/storage-nat
|
||||
networks:
|
||||
wan:
|
||||
ipv4_address: *bootstrap_ip
|
||||
entrypoint: ["/app/build/storage"]
|
||||
command:
|
||||
- --listen-ip=0.0.0.0
|
||||
- --api-bindaddr=0.0.0.0
|
||||
- --listen-port=8070
|
||||
- --disc-port=8090
|
||||
- --api-port=8080
|
||||
# bootstrap_ip (anchors can't go inside a string)
|
||||
- --nat=extip:7.7.7.10
|
||||
- --relay-server
|
||||
- --autonat-server
|
||||
- --no-bootstrap-node
|
||||
- --data-dir=/data
|
||||
- --log-level=DEBUG
|
||||
|
||||
# B: behind router1, uploaded to and downloaded from
|
||||
node:
|
||||
image: localhost/storage-nat
|
||||
cap_add: [NET_ADMIN]
|
||||
depends_on: [router1, bootstrap]
|
||||
networks:
|
||||
lan1:
|
||||
ipv4_address: *node_ip
|
||||
ports:
|
||||
- "127.0.0.1:18090:8080"
|
||||
environment:
|
||||
ROUTER_LAN_IP: *router1_lan_ip
|
||||
BOOTSTRAP_API: http://7.7.7.10:8080
|
||||
volumes:
|
||||
- ../node-entrypoint.sh:/scripts/node-entrypoint.sh:ro,z
|
||||
entrypoint: ["bash", "/scripts/node-entrypoint.sh"]
|
||||
|
||||
# D: behind router2, the one that downloads from B through the relay
|
||||
peer:
|
||||
image: localhost/storage-nat
|
||||
cap_add: [NET_ADMIN]
|
||||
depends_on: [router2, bootstrap]
|
||||
networks:
|
||||
lan2:
|
||||
ipv4_address: *peer_ip
|
||||
ports:
|
||||
- "127.0.0.1:18091:8080"
|
||||
environment:
|
||||
ROUTER_LAN_IP: *router2_lan_ip
|
||||
BOOTSTRAP_API: http://7.7.7.10:8080
|
||||
volumes:
|
||||
- ../node-entrypoint.sh:/scripts/node-entrypoint.sh:ro,z
|
||||
entrypoint: ["bash", "/scripts/node-entrypoint.sh"]
|
||||
11
tests/integration/nat/hole-punch/router-entrypoint.sh
Normal file
11
tests/integration/nat/hole-punch/router-entrypoint.sh
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source "$(dirname "$0")/router-common.sh"
|
||||
|
||||
# Drop early punch SYN so TCP retransmits until the
|
||||
# pinhole is open and the SYN gets forwarded.
|
||||
iptables -A INPUT -i "$wanif" -p tcp --dport 8070 -j DROP
|
||||
|
||||
echo "router ready (wan iface $wanif)"
|
||||
|
||||
hold_until_stopped
|
||||
64
tests/integration/nat/hole-punch/testholepunch.nim
Normal file
64
tests/integration/nat/hole-punch/testholepunch.nim
Normal file
@ -0,0 +1,64 @@
|
||||
## Coordinated DCUtR hole-punching scenario (both peers NATed). See README.md.
|
||||
|
||||
import std/[json, os, sequtils, strutils, times]
|
||||
import pkg/chronos
|
||||
import pkg/questionable/results
|
||||
|
||||
import ../../../asynctest
|
||||
import ../../../checktest
|
||||
import ../../storageclient
|
||||
import ../composehelper
|
||||
|
||||
const dcutrConnectedLog = "Dcutr initiator has directly connected to the remote peer."
|
||||
|
||||
proc announcesCircuitAddr(info: JsonNode): bool =
|
||||
info{"announceAddresses"}.getElems.anyIt("p2p-circuit" in it.getStr)
|
||||
|
||||
asyncchecksuite "NAT hole punching":
|
||||
let
|
||||
composeFile = currentSourcePath.parentDir / "compose.yml"
|
||||
nodeApiUrl = "http://127.0.0.1:18090/api/storage/v1"
|
||||
peerApiUrl = "http://127.0.0.1:18091/api/storage/v1"
|
||||
suiteName = "NAT hole punching"
|
||||
testName = "two NATed nodes upgrade a relayed connection to a direct one"
|
||||
services = ["router1", "router2", "bootstrap", "node", "peer"]
|
||||
startTime = now().format("yyyy-MM-dd'_'HH:mm:ss")
|
||||
var
|
||||
nodeClient: StorageClient
|
||||
peerClient: StorageClient
|
||||
|
||||
setup:
|
||||
compose(composeFile, "up -d")
|
||||
nodeClient = StorageClient.new(nodeApiUrl)
|
||||
peerClient = StorageClient.new(peerApiUrl)
|
||||
|
||||
teardown:
|
||||
await nodeClient.close()
|
||||
await peerClient.close()
|
||||
saveContainerLogs(composeFile, suiteName, testName, startTime, services)
|
||||
compose(composeFile, "down -v")
|
||||
|
||||
test testName:
|
||||
# Both nodes are NotReachable behind their own NAT and take a relay reservation.
|
||||
check eventuallyInfo(
|
||||
nodeClient,
|
||||
info{"nat"}{"reachability"}.getStr == "NotReachable" and
|
||||
info.announcesCircuitAddr(),
|
||||
)
|
||||
check eventuallyInfo(
|
||||
peerClient,
|
||||
info{"nat"}{"reachability"}.getStr == "NotReachable" and
|
||||
info.announcesCircuitAddr(),
|
||||
)
|
||||
|
||||
# D downloads from B through the relay; that opens the relayed connection.
|
||||
let cid = (await nodeClient.upload("punch me for real")).get
|
||||
check (await peerClient.download(cid)).isOk
|
||||
|
||||
# B sees the relayed peer D join and, since D has no public address, drives
|
||||
# the coordinated DCUtR simultaneous-open instead of a unilateral reversal.
|
||||
check eventuallySafe(
|
||||
dcutrConnectedLog in serviceLogs(composeFile, "node"),
|
||||
timeout = 60_000,
|
||||
pollInterval = 2_000,
|
||||
)
|
||||
Loading…
x
Reference in New Issue
Block a user