diff --git a/tests/integration/1_minute/testnat.nim b/tests/integration/1_minute/testnat.nim index 50c0467d..4a76e31c 100644 --- a/tests/integration/1_minute/testnat.nim +++ b/tests/integration/1_minute/testnat.nim @@ -1,5 +1,6 @@ import std/json import std/options +import std/sequtils import pkg/chronos import pkg/questionable/results @@ -7,7 +8,13 @@ import ../multinodes import ../storageclient import ../storageconfig -multinodesuite "AutoNAT integration": +const + DetectionTimeout = 15_000 + RelayTimeout = 30_000 + PollInterval = 1_000 + +# Reminder: multinodesuite setup the first node as bootstrap node +multinodesuite "AutoNAT detection": let natConfig = NodeConfigs( clients: StorageConfigs .init(nodes = 2) @@ -19,14 +26,145 @@ multinodesuite "AutoNAT integration": # .withLogLevel("DEBUG") .some ) - - # Reminder: multinodesuite setup the first node as bootstrap node test "node is reachable when using bootstrap node on same network", natConfig: - let node1 = clients()[0] let node2 = clients()[1] check eventuallySafe( (await node2.client.natReachability()).get() == "Reachable", - timeout = 30_000, - pollInterval = 500, + timeout = RelayTimeout, + pollInterval = PollInterval, + ) + + check eventuallySafe( + not (await node2.client.natRelayRunning()).get(), + timeout = RelayTimeout, + pollInterval = PollInterval, + ) + + let autonatConfig = NodeConfigs( + clients: StorageConfigs + .init(nodes = 2) + .withRelay(idx = 0) + .withNatSimulation(idx = 1) + .withNatNumPeersToAsk(1) + .withNatMinConfidence(0.5) + .withNatScheduleInterval(10.seconds) + .withNatMaxQueueSize(1) + # .withLogLevel("DEBUG") + # .debug() + # .withLogFile() + .some + ) + # node2 is behind simulated NAT: AutoNAT peers try to dial back but are blocked. + test "nat node is detected as not reachable and starts relay", autonatConfig: + let node2 = clients()[1] + + check eventuallySafe( + (await node2.client.natReachability()).get() == "NotReachable", + timeout = DetectionTimeout, + pollInterval = PollInterval, + ) + + check eventuallySafe( + (await node2.client.natRelayRunning()).get(), + timeout = RelayTimeout, + pollInterval = PollInterval, + ) + + check eventuallySafe( + block: + let addrs = (await node2.client.info()).get["addrs"].getElems.mapIt(it.getStr) + addrs.anyIt("p2p-circuit" in it), + timeout = RelayTimeout, + pollInterval = PollInterval, + ) + + let transitionConfig = NodeConfigs( + clients: StorageConfigs + .init(nodes = 2) + .withRelay(idx = 0) + .withNatSimulation(idx = 1) + .withNatNumPeersToAsk(1) + .withNatMinConfidence(0.5) + .withNatScheduleInterval(5.seconds) + .withNatMaxQueueSize(1).some + ) + # node2 starts behind simulated NAT (NotReachable + relay), then NAT is lifted + # and AutoNAT detects Reachable on the next scheduled check. + test "nat node recovers to reachable when nat is lifted", transitionConfig: + let node2 = clients()[1] + + check eventuallySafe( + (await node2.client.natReachability()).get() == "NotReachable", + timeout = DetectionTimeout, + pollInterval = PollInterval, + ) + + check eventuallySafe( + (await node2.client.natRelayRunning()).get(), + timeout = RelayTimeout, + pollInterval = PollInterval, + ) + + check eventuallySafe( + block: + let addrs = (await node2.client.info()).get["addrs"].getElems.mapIt(it.getStr) + addrs.anyIt("p2p-circuit" in it), + timeout = RelayTimeout, + pollInterval = PollInterval, + ) + + check (await node2.client.setNatFiltering("endpoint-independent")).isOk + + check eventuallySafe( + (await node2.client.natReachability()).get() == "Reachable", + timeout = RelayTimeout, + pollInterval = PollInterval, + ) + + check eventuallySafe( + not (await node2.client.natRelayRunning()).get(), + timeout = RelayTimeout, + pollInterval = PollInterval, + ) + + let natToSimConfig = NodeConfigs( + clients: StorageConfigs + .init(nodes = 2) + .withRelay(idx = 0) + .withNatSimulation(idx = 1, "endpoint-independent") + .withNatNumPeersToAsk(1) + .withNatMinConfidence(0.5) + .withNatScheduleInterval(5.seconds) + .withNatMaxQueueSize(1).some + ) + # node2 starts reachable (endpoint-independent NAT sim = pass-through), + # then NAT is tightened to block dial-backs and AutoNAT detects NotReachable. + test "reachable node becomes not reachable when nat is applied", natToSimConfig: + let node2 = clients()[1] + + check eventuallySafe( + (await node2.client.natReachability()).get() == "Reachable", + timeout = RelayTimeout, + pollInterval = PollInterval, + ) + + check eventuallySafe( + not (await node2.client.natRelayRunning()).get(), + timeout = RelayTimeout, + pollInterval = PollInterval, + ) + + check (await node2.client.setNatFiltering("address-and-port-dependent")).isOk + + check eventuallySafe( + (await node2.client.natReachability()).get() == "NotReachable", + timeout = RelayTimeout, + pollInterval = PollInterval, + ) + + check eventuallySafe( + (await node2.client.natRelayRunning()).get(), + timeout = RelayTimeout, + pollInterval = PollInterval, ) diff --git a/tests/integration/storageclient.nim b/tests/integration/storageclient.nim index 62d72917..2c0ec356 100644 --- a/tests/integration/storageclient.nim +++ b/tests/integration/storageclient.nim @@ -281,3 +281,23 @@ proc natReachability*( return info.get()["nat"]["reachability"].getStr().success except KeyError as e: return failure e.msg + +proc natRelayRunning*( + client: StorageClient +): Future[?!bool] {.async: (raises: [CancelledError, HttpError]).} = + let info = await client.info() + if info.isErr: + return failure "Failed to get node info" + try: + return info.get()["nat"]["relayRunning"].getBool().success + except KeyError as e: + return failure e.msg + +proc setNatFiltering*( + client: StorageClient, filtering: string +): Future[?!void] {.async: (raises: [CancelledError, HttpError]).} = + let response = + await client.post(client.baseurl & "/debug/nat/filtering?filtering=" & filtering) + if response.status != 200: + return failure "Failed to set NAT filtering: " & $response.status + return success() diff --git a/tests/integration/storageconfig.nim b/tests/integration/storageconfig.nim index 7b218bd3..ed839b1f 100644 --- a/tests/integration/storageconfig.nim +++ b/tests/integration/storageconfig.nim @@ -338,3 +338,35 @@ proc withExtIp*( for config in startConfig.configs.mitems: config.addCliOption("--nat", "extip:" & ip) return startConfig + +proc withRelay*( + self: StorageConfigs, idx: int +): StorageConfigs {.raises: [StorageConfigError].} = + self.checkBounds idx + + var startConfig = self + startConfig.configs[idx].addCliOption("--relay") + return startConfig + +proc withRelay*(self: StorageConfigs): StorageConfigs {.raises: [StorageConfigError].} = + var startConfig = self + for config in startConfig.configs.mitems: + config.addCliOption("--relay") + return startConfig + +proc withNatSimulation*( + self: StorageConfigs, idx: int, filtering = "address-and-port-dependent" +): StorageConfigs {.raises: [StorageConfigError].} = + self.checkBounds idx + + var startConfig = self + startConfig.configs[idx].addCliOption("--nat-simulation", filtering) + return startConfig + +proc withNatSimulation*( + self: StorageConfigs, filtering = "address-and-port-dependent" +): StorageConfigs {.raises: [StorageConfigError].} = + var startConfig = self + for config in startConfig.configs.mitems: + config.addCliOption("--nat-simulation", filtering) + return startConfig