2026-05-19 14:59:23 +04:00
|
|
|
import std/[net]
|
2025-01-09 23:41:22 +05:30
|
|
|
import pkg/chronos
|
|
|
|
|
import pkg/libp2p/[multiaddress, multihash, multicodec]
|
2026-06-04 14:36:12 +04:00
|
|
|
import pkg/libp2p/protocols/connectivity/autonat/types
|
2026-04-22 12:21:43 +04:00
|
|
|
import pkg/libp2p/protocols/connectivity/relay/client as relayClientModule
|
2026-05-22 22:54:07 +04:00
|
|
|
import pkg/libp2p/protocols/connectivity/dcutr/core as dcutrCore
|
|
|
|
|
import pkg/libp2p/multistream
|
2026-04-22 12:21:43 +04:00
|
|
|
import pkg/libp2p/services/autorelayservice except setup
|
2025-02-18 11:17:05 +01:00
|
|
|
import pkg/results
|
2025-01-09 23:41:22 +05:30
|
|
|
|
2026-04-22 12:21:43 +04:00
|
|
|
import ./helpers
|
|
|
|
|
import ../asynctest
|
2026-05-12 10:03:54 +04:00
|
|
|
import ../../storage/utils/natutils
|
2026-02-19 15:59:15 +11:00
|
|
|
import ../../storage/nat
|
2026-04-21 17:17:25 +04:00
|
|
|
import ../../storage/discovery
|
|
|
|
|
import ../../storage/rng
|
2026-02-19 15:59:15 +11:00
|
|
|
import ../../storage/utils
|
2025-01-09 23:41:22 +05:30
|
|
|
|
2026-05-22 21:16:57 +04:00
|
|
|
type MockNatPortMapper = ref object of NatPortMapper
|
2026-05-19 14:59:23 +04:00
|
|
|
mappedPorts: Option[(Port, Port, MappingProtocol)]
|
2026-05-05 14:58:26 +04:00
|
|
|
|
2026-05-08 14:56:42 +04:00
|
|
|
method mapNatPorts*(
|
2026-05-22 21:16:57 +04:00
|
|
|
m: MockNatPortMapper
|
2026-05-19 14:59:23 +04:00
|
|
|
): Future[Option[(Port, Port, MappingProtocol)]] {.
|
|
|
|
|
async: (raises: [CancelledError]), gcsafe
|
|
|
|
|
.} =
|
2026-05-05 14:58:26 +04:00
|
|
|
m.mappedPorts
|
|
|
|
|
|
2026-06-04 14:36:12 +04:00
|
|
|
asyncchecksuite "NAT - handleNatStatus":
|
2026-04-22 12:21:43 +04:00
|
|
|
var sw: Switch
|
|
|
|
|
var key: PrivateKey
|
|
|
|
|
var disc: Discovery
|
2026-06-04 14:36:12 +04:00
|
|
|
var autoRelay: AutoRelayService
|
2026-04-22 12:21:43 +04:00
|
|
|
|
|
|
|
|
setup:
|
2026-06-04 14:36:12 +04:00
|
|
|
autoRelay =
|
|
|
|
|
AutoRelayService.new(1, relayClientModule.RelayClient.new(), nil, Rng.instance())
|
2026-04-22 12:21:43 +04:00
|
|
|
key = PrivateKey.random(Rng.instance[]).get()
|
|
|
|
|
disc = Discovery.new(key, announceAddrs = @[])
|
|
|
|
|
sw = newStandardSwitch()
|
|
|
|
|
await sw.start()
|
|
|
|
|
|
|
|
|
|
teardown:
|
|
|
|
|
await sw.stop()
|
|
|
|
|
|
|
|
|
|
if autoRelay.isRunning:
|
|
|
|
|
discard await autoRelay.stop(sw)
|
|
|
|
|
|
2026-05-05 14:58:26 +04:00
|
|
|
let discoveryPort = Port(8090)
|
2026-04-22 12:21:43 +04:00
|
|
|
|
2026-05-05 14:58:26 +04:00
|
|
|
test "handleNatStatus announces mapped address when NotReachable and UPnP succeeds":
|
|
|
|
|
let dialBack = MultiAddress.init("/ip4/1.2.3.4/tcp/8080").expect("valid")
|
2026-05-22 22:54:07 +04:00
|
|
|
let mapper = MockNatPortMapper(
|
|
|
|
|
mappedPorts: some((Port(9000), Port(9001), MappingProtocol.UPnP))
|
|
|
|
|
)
|
2026-04-22 12:21:43 +04:00
|
|
|
|
2026-06-04 14:35:46 +04:00
|
|
|
await mapper.handleNatStatus(
|
|
|
|
|
NotReachable, Opt.some(dialBack), discoveryPort, disc, sw, autoRelay
|
2026-05-05 14:58:26 +04:00
|
|
|
)
|
2026-04-22 12:21:43 +04:00
|
|
|
|
2026-05-05 14:58:26 +04:00
|
|
|
check disc.announceAddrs ==
|
|
|
|
|
@[MultiAddress.init("/ip4/1.2.3.4/tcp/9000").expect("valid")]
|
|
|
|
|
check not autoRelay.isRunning
|
2026-05-14 12:15:14 +04:00
|
|
|
check disc.protocol.clientMode
|
2026-04-22 12:21:43 +04:00
|
|
|
|
2026-05-05 14:58:26 +04:00
|
|
|
test "handleNatStatus starts autoRelay when NotReachable and UPnP failed":
|
2026-05-22 21:16:57 +04:00
|
|
|
let mapper = MockNatPortMapper(mappedPorts: none((Port, Port, MappingProtocol)))
|
2026-04-22 12:21:43 +04:00
|
|
|
|
2026-06-04 14:35:46 +04:00
|
|
|
await mapper.handleNatStatus(
|
|
|
|
|
NotReachable, Opt.none(MultiAddress), discoveryPort, disc, sw, autoRelay
|
2026-05-05 14:58:26 +04:00
|
|
|
)
|
2026-04-22 12:21:43 +04:00
|
|
|
|
|
|
|
|
check autoRelay.isRunning
|
2026-05-14 10:56:56 +04:00
|
|
|
check disc.protocol.clientMode
|
2026-04-22 12:21:43 +04:00
|
|
|
|
2026-06-04 14:35:46 +04:00
|
|
|
test "handleNatStatus starts autoRelay when NotReachable and mapping fails":
|
|
|
|
|
let dialBack = MultiAddress.init("/ip4/1.2.3.4/tcp/8080").expect("valid")
|
2026-05-22 21:16:57 +04:00
|
|
|
let mapper = MockNatPortMapper(mappedPorts: none((Port, Port, MappingProtocol)))
|
2026-06-04 14:35:46 +04:00
|
|
|
|
|
|
|
|
await mapper.handleNatStatus(
|
|
|
|
|
NotReachable, Opt.some(dialBack), discoveryPort, disc, sw, autoRelay
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
check autoRelay.isRunning
|
|
|
|
|
check disc.announceAddrs == newSeq[MultiAddress]()
|
2026-05-14 10:56:56 +04:00
|
|
|
check disc.protocol.clientMode
|
2026-06-04 14:35:46 +04:00
|
|
|
|
2026-05-05 14:58:26 +04:00
|
|
|
test "handleNatStatus does not announce address when Reachable and no dialBackAddr":
|
2026-05-22 21:16:57 +04:00
|
|
|
let mapper = MockNatPortMapper(mappedPorts: none((Port, Port, MappingProtocol)))
|
2026-04-22 12:21:43 +04:00
|
|
|
|
2026-06-04 14:35:46 +04:00
|
|
|
await mapper.handleNatStatus(
|
|
|
|
|
Reachable, Opt.none(MultiAddress), discoveryPort, disc, sw, autoRelay
|
2026-05-05 14:58:26 +04:00
|
|
|
)
|
2026-04-22 12:21:43 +04:00
|
|
|
|
|
|
|
|
check disc.announceAddrs == newSeq[MultiAddress]()
|
|
|
|
|
check not autoRelay.isRunning
|
2026-05-14 10:56:56 +04:00
|
|
|
check not disc.protocol.clientMode
|
2026-04-21 17:17:25 +04:00
|
|
|
|
2026-05-05 14:58:26 +04:00
|
|
|
test "handleNatStatus stops relay and announces dialBackAddr when Reachable":
|
|
|
|
|
let dialBack = MultiAddress.init("/ip4/1.2.3.4/tcp/8080").expect("valid")
|
2026-05-22 21:16:57 +04:00
|
|
|
let mapper = MockNatPortMapper(mappedPorts: none((Port, Port, MappingProtocol)))
|
2026-04-21 17:17:25 +04:00
|
|
|
|
2026-04-22 12:21:43 +04:00
|
|
|
discard await autorelayservice.setup(autoRelay, sw)
|
2026-06-04 14:35:46 +04:00
|
|
|
await mapper.handleNatStatus(
|
|
|
|
|
Reachable, Opt.some(dialBack), discoveryPort, disc, sw, autoRelay
|
2026-05-05 14:58:26 +04:00
|
|
|
)
|
2026-04-21 17:17:25 +04:00
|
|
|
|
2026-04-22 12:21:43 +04:00
|
|
|
check not autoRelay.isRunning
|
2026-05-05 14:58:26 +04:00
|
|
|
check disc.announceAddrs == @[dialBack]
|
2026-05-14 10:56:56 +04:00
|
|
|
check not disc.protocol.clientMode
|
2026-05-22 22:54:07 +04:00
|
|
|
|
|
|
|
|
asyncchecksuite "NAT - Hole punching":
|
|
|
|
|
test "setupHolePunching mounts the dcutr protocol on the switch":
|
|
|
|
|
let sw = newStandardSwitch()
|
|
|
|
|
setupHolePunching(sw)
|
|
|
|
|
check sw.ms.handlers.anyIt(dcutrCore.DcutrCodec in it.protos)
|
|
|
|
|
|
|
|
|
|
test "holePunchIfRelayed returns early when the peer has no connections":
|
|
|
|
|
let sw1 = newStandardSwitch()
|
|
|
|
|
let sw2 = newStandardSwitch()
|
|
|
|
|
await allFutures(sw1.start(), sw2.start())
|
|
|
|
|
|
|
|
|
|
await holePunchIfRelayed(sw1, sw2.peerInfo.peerId)
|
|
|
|
|
|
|
|
|
|
await allFutures(sw1.stop(), sw2.stop())
|
|
|
|
|
|
|
|
|
|
test "holePunchIfRelayed returns early when a direct connection already exists":
|
|
|
|
|
let sw1 = newStandardSwitch()
|
|
|
|
|
let sw2 = newStandardSwitch()
|
|
|
|
|
await allFutures(sw1.start(), sw2.start())
|
|
|
|
|
|
|
|
|
|
await sw1.connect(sw2.peerInfo.peerId, sw2.peerInfo.addrs)
|
|
|
|
|
check sw1.isConnected(sw2.peerInfo.peerId)
|
|
|
|
|
|
|
|
|
|
await holePunchIfRelayed(sw1, sw2.peerInfo.peerId)
|
|
|
|
|
|
|
|
|
|
check sw1.isConnected(sw2.peerInfo.peerId)
|
|
|
|
|
await allFutures(sw1.stop(), sw2.stop())
|