diff --git a/openapi.yaml b/openapi.yaml index f35c54b2..eed45eba 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -135,15 +135,24 @@ components: type: object required: - reachability + - clientMode - relayRunning + - portMapping properties: reachability: type: string enum: [Unknown, Reachable, NotReachable] description: AutoNAT reachability status + clientMode: + type: boolean + description: Whether the DHT is running in client mode (not added to remote routing tables) relayRunning: type: boolean description: Whether the AutoRelay service is currently running + portMapping: + type: string + enum: [none, upnp, pmp] + description: Active NAT port mapping type DataList: type: object diff --git a/storage/nat.nim b/storage/nat.nim index 53b474a7..fa9d51b4 100644 --- a/storage/nat.nim +++ b/storage/nat.nim @@ -149,10 +149,12 @@ method handleNatStatus*( debug "AutoRelayService stopped" discovery.updateRecords(@[dialBackAddr.get], discoveryPort) - # TODO: switch DHT to server mode + discovery.protocol.clientMode = false of NotReachable: var hasPortMapping = false + discovery.protocol.clientMode = true + if dialBackAddr.isNone: warn "Got empty dialback address in AutoNat when node is NotReachable" else: @@ -176,6 +178,7 @@ method handleNatStatus*( debug "AutoRelayService stopped" discovery.updateRecords(@[announceAddress], udpPort) + discovery.protocol.clientMode = false hasPortMapping = true if not hasPortMapping and not autoRelayService.isRunning: diff --git a/storage/rest/api.nim b/storage/rest/api.nim index 03e593c7..6acdfd61 100644 --- a/storage/rest/api.nim +++ b/storage/rest/api.nim @@ -596,6 +596,7 @@ proc initDebugApi( $autonat.get.networkReachability else: "unknown", + "clientMode": node.discovery.protocol.clientMode, "relayRunning": autoRelay.isSome and autoRelay.get.isRunning, "portMapping": if natMapper.isNone or natMapper.get.portMappingType == NoMapping: diff --git a/storage/storage.nim b/storage/storage.nim index 053e7bc2..d89c76a9 100644 --- a/storage/storage.nim +++ b/storage/storage.nim @@ -99,9 +99,13 @@ proc start*(s: StorageServer) {.async.} = ] else: # Don't announce address and wait for AutoNat - # TODO: DHT client mode @[] + if not s.config.nat.hasExtIp: + # Nodes with autonat start with client mode. + # It will be updated if reachable. + s.storageNode.discovery.protocol.clientMode = true + s.storageNode.discovery.updateRecords(announceAddrs, s.config.discoveryPort) await s.storageNode.start() diff --git a/tests/integration/1_minute/testnat.nim b/tests/integration/1_minute/testnat.nim index 7402eb74..795ed6b6 100644 --- a/tests/integration/1_minute/testnat.nim +++ b/tests/integration/1_minute/testnat.nim @@ -13,28 +13,25 @@ const RelayTimeout = 30_000 PollInterval = 1_000 -proc checkNatReachability*(client: StorageClient, reachability: string) {.async.} = - check eventuallySafe( - (await client.natReachability()).get() == reachability, - timeout = RelayTimeout, - pollInterval = PollInterval, - ) - -proc checkRelayIsRunning*(client: StorageClient, isRunning: bool) {.async.} = - check eventuallySafe( - (await client.natRelayRunning()).get() == isRunning, - timeout = RelayTimeout, - pollInterval = PollInterval, - ) - +proc checkNatStatus*( + client: StorageClient, reachability: string, relayRunning: bool +) {.async.} = check eventuallySafe( block: - let addrs = (await client.info()).get["addrs"].getElems.mapIt(it.getStr) - addrs.anyIt("p2p-circuit" in it) == isRunning, + let info = (await client.info()).get + let nat = info["nat"] + let addrs = info["addrs"].getElems.mapIt(it.getStr) + nat["reachability"].getStr() == reachability and + nat["clientMode"].getBool() == relayRunning and + nat["relayRunning"].getBool() == relayRunning and + addrs.anyIt("p2p-circuit" in it) == relayRunning, timeout = RelayTimeout, pollInterval = PollInterval, ) +proc checkNatStatus*(client: StorageClient, reachability: string) {.async.} = + await client.checkNatStatus(reachability, reachability == "NotReachable") + # Reminder: multinodesuite setup the first node as bootstrap node multinodesuite "AutoNAT detection": let natConfig = NodeConfigs( @@ -48,8 +45,7 @@ multinodesuite "AutoNAT detection": ) test "node is reachable when using bootstrap node on same network", natConfig: let node2 = clients()[1] - await node2.client.checkNatReachability("Reachable") - await node2.client.checkRelayIsRunning(false) + await node2.client.checkNatStatus("Reachable") let endpointIndependentConfig = NodeConfigs( clients: StorageConfigs @@ -64,8 +60,7 @@ multinodesuite "AutoNAT detection": # EIF = Endpoint Independent Filtering test "node with simulated EIF nat is detected as reachable", endpointIndependentConfig: let node2 = clients()[1] - await node2.client.checkNatReachability("Reachable") - await node2.client.checkRelayIsRunning(false) + await node2.client.checkNatStatus("Reachable") let autonatConfig = NodeConfigs( clients: StorageConfigs @@ -81,8 +76,7 @@ multinodesuite "AutoNAT detection": test "node with simulated APDF nat is detected as not reachable and starts relay", autonatConfig: let node2 = clients()[1] - await node2.client.checkNatReachability("NotReachable") - await node2.client.checkRelayIsRunning(true) + await node2.client.checkNatStatus("NotReachable") let transitionConfig = NodeConfigs( clients: StorageConfigs @@ -100,13 +94,11 @@ multinodesuite "AutoNAT detection": transitionConfig: let node2 = clients()[1] - await node2.client.checkNatReachability("NotReachable") - await node2.client.checkRelayIsRunning(true) + await node2.client.checkNatStatus("NotReachable") check (await node2.client.setNatFiltering("endpoint-independent")).isOk - await node2.client.checkNatReachability("Reachable") - await node2.client.checkRelayIsRunning(false) + await node2.client.checkNatStatus("Reachable") let natToSimConfig = NodeConfigs( clients: StorageConfigs @@ -123,13 +115,11 @@ multinodesuite "AutoNAT detection": natToSimConfig: let node2 = clients()[1] - await node2.client.checkNatReachability("Reachable") - await node2.client.checkRelayIsRunning(false) + await node2.client.checkNatStatus("Reachable") check (await node2.client.setNatFiltering("address-and-port-dependent")).isOk - await node2.client.checkNatReachability("NotReachable") - await node2.client.checkRelayIsRunning(true) + await node2.client.checkNatStatus("NotReachable") let multiNatConfig = NodeConfigs( clients: StorageConfigs @@ -148,7 +138,5 @@ multinodesuite "AutoNAT detection": let node2 = clients()[1] let node3 = clients()[2] - await node2.client.checkNatReachability("NotReachable") - await node3.client.checkNatReachability("NotReachable") - await node2.client.checkRelayIsRunning(true) - await node3.client.checkRelayIsRunning(true) + await node2.client.checkNatStatus("NotReachable") + await node3.client.checkNatStatus("NotReachable") diff --git a/tests/integration/1_minute/testnatupnp.nim b/tests/integration/1_minute/testnatupnp.nim index 4839797e..3c09c9c5 100644 --- a/tests/integration/1_minute/testnatupnp.nim +++ b/tests/integration/1_minute/testnatupnp.nim @@ -8,7 +8,7 @@ import ../storageclient import ../storageconfig import ../../../storage/utils/natutils -from ./testnat.nim import checkNatReachability, checkRelayIsRunning +from ./testnat.nim import checkNatStatus const DetectionTimeout = 15_000 @@ -39,7 +39,8 @@ multinodesuite "AutoNAT UPnP port mapping": let node2 = clients()[1] - await node2.client.checkNatReachability("NotReachable") + let isRelayRunning = false + await node2.client.checkNatStatus("NotReachable", isRelayRunning) check eventuallySafe( block: @@ -51,7 +52,8 @@ multinodesuite "AutoNAT UPnP port mapping": # Ideally we should find a way to test that the node is Reachable now - await node2.client.checkRelayIsRunning(false) + let isRelayRunning = false + await node2.client.checkNatStatus("NotReachable", isRelayRunning) # Extract mapped TCP port from announce addresses and verify it exists on the IGD let announceAddrs = diff --git a/tests/integration/5_minutes/testnatdownload.nim b/tests/integration/5_minutes/testnatdownload.nim index 8407ebb5..0216c000 100644 --- a/tests/integration/5_minutes/testnatdownload.nim +++ b/tests/integration/5_minutes/testnatdownload.nim @@ -1,5 +1,4 @@ import std/json -import std/sequtils import pkg/chronos import pkg/questionable/results diff --git a/tests/integration/storageclient.nim b/tests/integration/storageclient.nim index b12f388a..e1bf04a8 100644 --- a/tests/integration/storageclient.nim +++ b/tests/integration/storageclient.nim @@ -271,17 +271,6 @@ proc connectPeer*( let response = await client.get(url) assert response.status == 200 -proc natReachability*( - client: StorageClient -): Future[?!string] {.async: (raises: [CancelledError, HttpError]).} = - let info = await client.info() - if info.isErr: - return failure "Failed to get node info" - try: - 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]).} = diff --git a/tests/storage/testnat.nim b/tests/storage/testnat.nim index 5aaca402..888279ee 100644 --- a/tests/storage/testnat.nim +++ b/tests/storage/testnat.nim @@ -1,4 +1,4 @@ -import std/[net, importutils, envvars] +import std/[net, importutils] import pkg/chronos import pkg/libp2p/[multiaddress, multihash, multicodec] import pkg/libp2p/protocols/connectivity/autonat/types @@ -95,6 +95,7 @@ asyncchecksuite "NAT - handleNatStatus": check disc.announceAddrs == @[MultiAddress.init("/ip4/1.2.3.4/tcp/9000").expect("valid")] check not autoRelay.isRunning + check not disc.protocol.clientMode test "handleNatStatus starts autoRelay when NotReachable and UPnP failed": let mapper = MockNatMapper(mappedPorts: none((Port, Port))) @@ -104,6 +105,7 @@ asyncchecksuite "NAT - handleNatStatus": ) check autoRelay.isRunning + check disc.protocol.clientMode test "handleNatStatus starts autoRelay when NotReachable and mapping fails": let dialBack = MultiAddress.init("/ip4/1.2.3.4/tcp/8080").expect("valid") @@ -115,6 +117,7 @@ asyncchecksuite "NAT - handleNatStatus": check autoRelay.isRunning check disc.announceAddrs == newSeq[MultiAddress]() + check disc.protocol.clientMode test "handleNatStatus does not announce address when Reachable and no dialBackAddr": let mapper = MockNatMapper(mappedPorts: none((Port, Port))) @@ -125,6 +128,7 @@ asyncchecksuite "NAT - handleNatStatus": check disc.announceAddrs == newSeq[MultiAddress]() check not autoRelay.isRunning + check not disc.protocol.clientMode test "handleNatStatus stops relay and announces dialBackAddr when Reachable": let dialBack = MultiAddress.init("/ip4/1.2.3.4/tcp/8080").expect("valid") @@ -137,3 +141,4 @@ asyncchecksuite "NAT - handleNatStatus": check not autoRelay.isRunning check disc.announceAddrs == @[dialBack] + check not disc.protocol.clientMode