Add DHT client mode

This commit is contained in:
Arnaud 2026-05-14 10:56:56 +04:00
parent 48abbe20fa
commit 641b9f0443
No known key found for this signature in database
GPG Key ID: A6C7C781817146FA
9 changed files with 52 additions and 52 deletions

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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()

View File

@ -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")

View File

@ -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 =

View File

@ -1,5 +1,4 @@
import std/json
import std/sequtils
import pkg/chronos
import pkg/questionable/results

View File

@ -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]).} =

View File

@ -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