mirror of
https://github.com/logos-storage/logos-storage-nim.git
synced 2026-08-01 22:43:11 +00:00
Add hole punching
This commit is contained in:
parent
0ffee3d14f
commit
03933c2b3a
@ -16,6 +16,10 @@ import pkg/chronicles
|
||||
import pkg/libp2p
|
||||
import pkg/libp2p/services/autorelayservice
|
||||
import pkg/libp2p/protocols/connectivity/autonatv2/service
|
||||
import pkg/libp2p/protocols/connectivity/relay/relay as relayProtocol
|
||||
import pkg/libp2p/protocols/connectivity/dcutr/client as dcutrClientModule
|
||||
import pkg/libp2p/protocols/connectivity/dcutr/server as dcutrServerModule
|
||||
import pkg/libp2p/wire
|
||||
|
||||
import ./utils
|
||||
import ./utils/natutils
|
||||
@ -60,8 +64,7 @@ method mapNatPorts*(
|
||||
if not m.plumInitialized:
|
||||
# 5s matches the old NatPortMappingTimeout used with miniupnpc/libnatpmp.
|
||||
let plumLogLevel =
|
||||
if getEnv("DEBUG") == "1": PLUM_LOG_LEVEL_VERBOSE
|
||||
else: PLUM_LOG_LEVEL_NONE
|
||||
if getEnv("DEBUG") == "1": PLUM_LOG_LEVEL_VERBOSE else: PLUM_LOG_LEVEL_NONE
|
||||
let res = init(
|
||||
logLevel = plumLogLevel,
|
||||
discoverTimeout = m.discoverTimeout,
|
||||
@ -229,3 +232,76 @@ proc findReachableNodes*(bootstrapNodes: seq[SignedPeerRecord]): seq[SignedPeerR
|
||||
## Currently returns bootstrap nodes. In the future, any network participant
|
||||
## confirmed reachable by AutoNAT could be included.
|
||||
bootstrapNodes
|
||||
|
||||
# Hole punching logic below is adapted from libp2p's HPService
|
||||
# (libp2p/services/hpservice.nim). HPService cannot be used directly because it
|
||||
# depends on AutoNAT v1 and starts the relay immediately on NotReachable,
|
||||
# bypassing the UPnP step.
|
||||
|
||||
proc tryStartingDirectConn(
|
||||
switch: Switch, peerId: PeerId
|
||||
): Future[bool] {.async: (raises: [CancelledError]).} =
|
||||
proc tryConnect(
|
||||
address: MultiAddress
|
||||
): Future[bool] {.async: (raises: [DialFailedError, CancelledError]).} =
|
||||
debug "Trying to create direct connection", peerId, address
|
||||
await switch.connect(peerId, @[address], true, false)
|
||||
debug "Direct connection created."
|
||||
return true
|
||||
|
||||
await sleepAsync(500.milliseconds) # wait for AddressBook to be populated
|
||||
for address in switch.peerStore[AddressBook][peerId]:
|
||||
try:
|
||||
let isRelayedAddr = address.contains(multiCodec("p2p-circuit"))
|
||||
if not isRelayedAddr.get(false) and address.isPublicMA():
|
||||
return await tryConnect(address)
|
||||
except CatchableError as err:
|
||||
debug "Failed to create direct connection.", description = err.msg
|
||||
continue
|
||||
return false
|
||||
|
||||
proc closeRelayConn(relayedConn: Connection) {.async: (raises: [CancelledError]).} =
|
||||
await sleepAsync(2000.milliseconds) # grace period before closing relayed connection
|
||||
await relayedConn.close()
|
||||
|
||||
proc holePunchIfRelayed*(
|
||||
switch: Switch, peerId: PeerId
|
||||
) {.async: (raises: [CancelledError]).} =
|
||||
## Attempts to establish a direct connection when a peer connected via relay.
|
||||
## First tries a direct TCP connect (if the peer's address is known and public),
|
||||
## then falls back to dcutr simultaneous-open hole punching.
|
||||
## Closes the relay connection once a direct path is established.
|
||||
let connections =
|
||||
switch.connManager.getConnections().getOrDefault(peerId).mapIt(it.connection)
|
||||
if connections.anyIt(not isRelayed(it)):
|
||||
return
|
||||
let incomingRelays = connections.filterIt(it.transportDir == Direction.In)
|
||||
if incomingRelays.len == 0:
|
||||
return
|
||||
|
||||
let relayedConn = incomingRelays[0]
|
||||
|
||||
if await tryStartingDirectConn(switch, peerId):
|
||||
await closeRelayConn(relayedConn)
|
||||
return
|
||||
|
||||
var natAddrs = switch.peerStore.getMostObservedProtosAndPorts()
|
||||
if natAddrs.len == 0:
|
||||
natAddrs = switch.peerInfo.listenAddrs.mapIt(switch.peerStore.guessDialableAddr(it))
|
||||
try:
|
||||
await DcutrClient.new().startSync(switch, peerId, natAddrs)
|
||||
await closeRelayConn(relayedConn)
|
||||
except DcutrError as err:
|
||||
debug "Hole punching failed during dcutr", description = err.msg
|
||||
|
||||
proc setupHolePunching*(switch: Switch) =
|
||||
try:
|
||||
switch.mount(Dcutr.new(switch))
|
||||
except LPError as err:
|
||||
error "Failed to mount Dcutr protocol", description = err.msg
|
||||
|
||||
let handler = proc(
|
||||
peerId: PeerId, event: PeerEvent
|
||||
) {.async: (raises: [CancelledError]).} =
|
||||
await holePunchIfRelayed(switch, peerId)
|
||||
switch.addPeerEventHandler(handler, PeerEventKind.Joined)
|
||||
|
||||
@ -451,6 +451,8 @@ proc new*(
|
||||
)
|
||||
)
|
||||
|
||||
setupHolePunching(switch)
|
||||
|
||||
# REST server
|
||||
var restServer: RestServerRef = nil
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@ import pkg/chronos
|
||||
import pkg/libp2p/[multiaddress, multihash, multicodec]
|
||||
import pkg/libp2p/protocols/connectivity/autonat/types
|
||||
import pkg/libp2p/protocols/connectivity/relay/client as relayClientModule
|
||||
import pkg/libp2p/protocols/connectivity/dcutr/core as dcutrCore
|
||||
import pkg/libp2p/multistream
|
||||
import pkg/libp2p/services/autorelayservice except setup
|
||||
import pkg/results
|
||||
|
||||
@ -48,8 +50,9 @@ asyncchecksuite "NAT - handleNatStatus":
|
||||
|
||||
test "handleNatStatus announces mapped address when NotReachable and UPnP succeeds":
|
||||
let dialBack = MultiAddress.init("/ip4/1.2.3.4/tcp/8080").expect("valid")
|
||||
let mapper =
|
||||
MockNatPortMapper(mappedPorts: some((Port(9000), Port(9001), MappingProtocol.UPnP)))
|
||||
let mapper = MockNatPortMapper(
|
||||
mappedPorts: some((Port(9000), Port(9001), MappingProtocol.UPnP))
|
||||
)
|
||||
|
||||
await mapper.handleNatStatus(
|
||||
NotReachable, Opt.some(dialBack), discoveryPort, disc, sw, autoRelay
|
||||
@ -105,3 +108,31 @@ asyncchecksuite "NAT - handleNatStatus":
|
||||
check not autoRelay.isRunning
|
||||
check disc.announceAddrs == @[dialBack]
|
||||
check not disc.protocol.clientMode
|
||||
|
||||
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())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user