From 4d9eb9c34a73bf9b94d8aba9d179358fedbbf60a Mon Sep 17 00:00:00 2001 From: Arnaud Date: Fri, 10 Apr 2026 18:28:21 +0400 Subject: [PATCH] Autonat integration and reachability info in debug api --- storage/rest/api.nim | 12 ++++++++-- storage/storage.nim | 55 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/storage/rest/api.nim b/storage/rest/api.nim index 865591fc..af4b009e 100644 --- a/storage/rest/api.nim +++ b/storage/rest/api.nim @@ -23,6 +23,7 @@ import pkg/confutils import pkg/libp2p import pkg/libp2p/routing_record +import pkg/libp2p/protocols/connectivity/autonat/service import pkg/codexdht/discv5/spr as spr import ../logutils @@ -557,7 +558,12 @@ proc initNodeApi(node: StorageNodeRef, conf: StorageConf, router: var RestRouter return RestApiResponse.error(Http500, "Unknown error dialling peer", headers = headers) -proc initDebugApi(node: StorageNodeRef, conf: StorageConf, router: var RestRouter) = +proc initDebugApi( + node: StorageNodeRef, + conf: StorageConf, + autonat: AutonatService, + router: var RestRouter, +) = let allowedOrigin = router.allowedOrigin router.api(MethodGet, "/api/storage/v1/debug/info") do() -> RestApiResponse: @@ -577,6 +583,7 @@ proc initDebugApi(node: StorageNodeRef, conf: StorageConf, router: var RestRoute "announceAddresses": node.discovery.announceAddrs, "table": table, "storage": {"version": $storageVersion, "revision": $storageRevision}, + "nat": {"reachability": $autonat.networkReachability}, } # return pretty json for human readability @@ -637,12 +644,13 @@ proc initRestApi*( node: StorageNodeRef, conf: StorageConf, repoStore: RepoStore, + autonat: AutonatService, corsAllowedOrigin: ?string, ): RestRouter = var router = RestRouter.init(validate, corsAllowedOrigin) initDataApi(node, repoStore, router) initNodeApi(node, conf, router) - initDebugApi(node, conf, router) + initDebugApi(node, conf, autonat, router) return router diff --git a/storage/storage.nim b/storage/storage.nim index f332f530..0b3f08e7 100644 --- a/storage/storage.nim +++ b/storage/storage.nim @@ -17,6 +17,7 @@ import pkg/chronos import pkg/taskpools import pkg/presto import pkg/libp2p +import pkg/libp2p/protocols/connectivity/autonat/[service, client] import pkg/confutils import pkg/confutils/defs import pkg/stew/io2 @@ -51,6 +52,7 @@ type repoStore: RepoStore maintenance: BlockMaintainer taskpool: Taskpool + autonatService*: AutonatService isStarted: bool StoragePrivateKey* = libp2p.PrivateKey # alias @@ -76,9 +78,25 @@ proc start*(s: StorageServer) {.async.} = await s.storageNode.switch.start() - let (announceAddrs, discoveryAddrs) = nattedAddress( - s.config.nat, s.storageNode.switch.peerInfo.addrs, s.config.discoveryPort - ) + let announceIp = + if s.config.nat.hasExtIp: + some(s.config.nat.extIp) + else: + getBestLocalAddress(s.config.listenIp) + + if announceIp.isNone: + # We should have an IP, even at private IP + raise newException(StorageError, "Unable to determine an IP address to announce") + + # Remap switch addresses to the resolved IP (replaces 0.0.0.0 or :: with the actual address), + # keeping unique entries only. + let announceAddrs = s.storageNode.switch.peerInfo.addrs + .mapIt(it.remapAddr(ip = announceIp, port = none(Port))) + .deduplicate() + let discoveryAddrs = + @[getMultiAddrWithIPAndUDPPort(announceIp.get, s.config.discoveryPort)] + s.storageNode.discovery.updateDhtRecord(discoveryAddrs) + s.storageNode.discovery.updateAnnounceRecord(announceAddrs) var hasPublicAddr = false for announceAddr in announceAddrs: @@ -90,9 +108,6 @@ proc start*(s: StorageServer) {.async.} = if not hasPublicAddr: warn "Unable to determine a public IP address. This node will only be reachable on a private network." - s.storageNode.discovery.updateAnnounceRecord(announceAddrs) - s.storageNode.discovery.updateDhtRecord(discoveryAddrs) - await s.storageNode.start() if s.restServer != nil: @@ -171,6 +186,16 @@ proc new*( ## create StorageServer including setting up datastore, repostore, etc let listenMultiAddr = getMultiAddrWithIpAndTcpPort(config.listenIp, config.listenPort) + let autonatService = AutonatService.new( + autonatClient = AutonatClient.new(), + rng = random.Rng.instance(), + scheduleInterval = Opt.some(config.natScheduleInterval), + askNewConnectedPeers = true, + numPeersToAsk = config.natNumPeersToAsk, + maxQueueSize = config.natMaxQueueSize, + minConfidence = config.natMinConfidence, + ) + let switch = SwitchBuilder .new() .withPrivateKey(privateKey) @@ -182,6 +207,8 @@ proc new*( .withAgentVersion(config.agentString) .withSignedPeerRecord(true) .withTcpTransport({ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay}) + .withAutonat() + .withServices(@[Service(autonatService)]) .build() var @@ -290,7 +317,9 @@ proc new*( if config.apiBindAddress.isSome: restServer = RestServerRef .new( - storageNode.initRestApi(config, repoStore, config.apiCorsAllowedOrigin), + storageNode.initRestApi( + config, repoStore, autonatService, config.apiCorsAllowedOrigin + ), initTAddress(config.apiBindAddress.get(), config.apiPort), bufferSize = (1024 * 64), maxRequestBodySize = int.high, @@ -300,6 +329,17 @@ proc new*( switch.mount(network) switch.mount(manifestProto) + autonatService.statusAndConfidenceHandler( + proc( + networkReachability: NetworkReachability, confidence: Opt[float] + ) {.async: (raises: [CancelledError]).} = + if networkReachability == NotReachable: + let (announceAddrs, discoveryAddrs) = + nattedAddress(config.nat, switch.peerInfo.addrs, config.discoveryPort) + discovery.updateAnnounceRecord(announceAddrs) + discovery.updateDhtRecord(discoveryAddrs) + ) + StorageServer( config: config, storageNode: storageNode, @@ -308,4 +348,5 @@ proc new*( maintenance: maintenance, taskPool: taskPool, logFile: logFile, + autonatService: autonatService, )