mirror of
https://github.com/logos-storage/logos-storage-nim.git
synced 2026-06-27 21:09:28 +00:00
Add custom address mapper to remap the port mapping by UPnP / PCP
This commit is contained in:
parent
1ec0651139
commit
fdf5396e60
@ -165,6 +165,34 @@ proc setupPeerInfoObserver*(
|
||||
switch.peerInfo.addObserver(observer)
|
||||
observer
|
||||
|
||||
proc setupMappedAddrMapper*(switch: Switch, natMapper: NatPortMapper) =
|
||||
## We define a custom mapper that adds the external port to peerInfo.addrs when
|
||||
## a port mapping is active, so AutoNAT tests that port.
|
||||
## PCP/NAT-PMP may grant an external port different from the listen port.
|
||||
let mapper: AddressMapper = proc(
|
||||
addrs: seq[MultiAddress]
|
||||
): Future[seq[MultiAddress]] {.gcsafe, async: (raises: [CancelledError]).} =
|
||||
result = addrs
|
||||
|
||||
if natMapper.activeTcpPort.isNone:
|
||||
return result
|
||||
|
||||
let mappedPort = natMapper.activeTcpPort.get
|
||||
for listenAddr in switch.peerInfo.listenAddrs:
|
||||
# Dialable IP (observed public, or the listen IP if already public)
|
||||
# used with the mapped port.
|
||||
let mappedAddr = switch.peerStore.guessDialableAddr(listenAddr).remapAddr(
|
||||
port = some(mappedPort)
|
||||
)
|
||||
if mappedAddr.isPublicMA():
|
||||
# Insert first so AutoNAT dials it before the listen-port candidate (the
|
||||
# server tests only the first dialable address).
|
||||
result.insert(mappedAddr, 0)
|
||||
|
||||
return result.deduplicate()
|
||||
|
||||
switch.peerInfo.addressMappers.add(mapper)
|
||||
|
||||
method handleNatStatus*(
|
||||
m: NatPortMapper,
|
||||
networkReachability: NetworkReachability,
|
||||
|
||||
@ -490,6 +490,8 @@ proc new*(
|
||||
peerInfoObserver =
|
||||
some(setupPeerInfoObserver(switch, autonatService.get, discovery, natMapper.get))
|
||||
|
||||
setupMappedAddrMapper(switch, natMapper.get)
|
||||
|
||||
autonatService.get.setStatusAndConfidenceHandler(
|
||||
proc(
|
||||
networkReachability: NetworkReachability,
|
||||
|
||||
@ -30,6 +30,8 @@ const
|
||||
discoveryPort = Port(8090)
|
||||
# ms — AutoNAT probe + confidence + reaction
|
||||
detectTimeout = 20000
|
||||
mockMappedTcpPort = Port(40000)
|
||||
mockMappedUdpPort = Port(40001)
|
||||
|
||||
type MockNatPortMapper = ref object of NatPortMapper
|
||||
|
||||
@ -40,6 +42,19 @@ method mapNatPorts*(
|
||||
.} =
|
||||
none((Port, Port, MappingProtocol))
|
||||
|
||||
# Simulates a successful PCP mapping
|
||||
type MockMappingNatPortMapper = ref object of NatPortMapper
|
||||
|
||||
method mapNatPorts*(
|
||||
m: MockMappingNatPortMapper
|
||||
): Future[Option[(Port, Port, MappingProtocol)]] {.
|
||||
async: (raises: [CancelledError]), gcsafe
|
||||
.} =
|
||||
m.activeTcpPort = some(mockMappedTcpPort)
|
||||
m.activeUdpPort = some(mockMappedUdpPort)
|
||||
m.activeMappingProtocol = some(MappingProtocol.PCP)
|
||||
some((mockMappedTcpPort, mockMappedUdpPort, MappingProtocol.PCP))
|
||||
|
||||
# Captures the candidate addresses the service sends and answers Reachable, so
|
||||
# the service flips to reachable and runs its address mapper — without dialing.
|
||||
type MockAutonatV2Client = ref object of AutonatV2Client
|
||||
@ -241,3 +256,59 @@ asyncchecksuite "NAT detection - dial request candidates":
|
||||
|
||||
await autonat.stop(sw)
|
||||
await sw2.stop()
|
||||
|
||||
test "after a port mapping, the mapped address is AutoNAT's first dial candidate":
|
||||
let mapper = MockMappingNatPortMapper()
|
||||
|
||||
setupMappedAddrMapper(sw, mapper)
|
||||
|
||||
# Reach the observation quorum so guessDialableAddr trusts 8.8.8.8
|
||||
let observed = MultiAddress.init("/ip4/8.8.8.8/tcp/4001").expect("valid")
|
||||
let quorum = 3
|
||||
for _ in 0 ..< quorum:
|
||||
discard sw.peerStore.identify.observedAddrManager.addObservation(observed)
|
||||
|
||||
# Setup AutoRelayService
|
||||
let relay = AutoRelayService.new(
|
||||
1, relayClientModule.RelayClient.new(), nil, Rng.instance().libp2pRng
|
||||
)
|
||||
autorelayservice.setup(relay, sw)
|
||||
|
||||
# Define our handleNatStatus callback
|
||||
let disc = Discovery.new(
|
||||
PrivateKey.random(Rng.instance().libp2pRng).get(), announceAddrs = @[]
|
||||
)
|
||||
let dialBack = MultiAddress.init("/ip4/8.8.8.8/tcp/8080").expect("valid")
|
||||
await mapper.handleNatStatus(
|
||||
NotReachable, Opt.some(dialBack), discoveryPort, disc, sw, relay
|
||||
)
|
||||
|
||||
# Define our AutonatV2Service
|
||||
let mockClient = MockAutonatV2Client()
|
||||
let autonat = AutonatV2Service.new(
|
||||
Rng.instance().libp2pRng,
|
||||
mockClient,
|
||||
AutonatV2ServiceConfig.new(
|
||||
enableDialableCandidates = true, maxQueueSize = 1, minConfidence = 0.5
|
||||
),
|
||||
)
|
||||
service.setup(autonat, sw)
|
||||
await autonat.start(sw)
|
||||
|
||||
# Connect to a second switch to test NAT detection
|
||||
let sw2 = newStandardSwitch()
|
||||
await sw2.start()
|
||||
await sw.connect(sw2.peerInfo.peerId, sw2.peerInfo.addrs)
|
||||
|
||||
# The expected mapped address should be the guessDialableAddr (8.8.8.8)
|
||||
# using the mapping mocked port (40000) because a mapping was created.
|
||||
let mapped =
|
||||
MultiAddress.init("/ip4/8.8.8.8/tcp/" & $mockMappedTcpPort).expect("valid")
|
||||
check eventually(mapped in mockClient.reqAddrs)
|
||||
# Ensute that it comes first (because AutonatV2 test only the first candidate)
|
||||
check mockClient.reqAddrs[0] == mapped
|
||||
|
||||
await autonat.stop(sw)
|
||||
await sw2.stop()
|
||||
if relay.isRunning:
|
||||
await relay.stop(sw)
|
||||
|
||||
@ -5,6 +5,7 @@ import pkg/libp2p/protocols/connectivity/autonat/types
|
||||
import pkg/libp2p/protocols/connectivity/autonatv2/service except setup
|
||||
import pkg/libp2p/protocols/connectivity/relay/client as relayClientModule
|
||||
import pkg/libp2p/services/autorelayservice except setup
|
||||
import pkg/libp2p/observedaddrmanager
|
||||
import pkg/results
|
||||
|
||||
import ./helpers
|
||||
@ -223,3 +224,36 @@ asyncchecksuite "NAT reaction - address announcing":
|
||||
await sw.peerInfo.update()
|
||||
|
||||
check disc.announceAddrs == newSeq[MultiAddress]()
|
||||
|
||||
test "mapped-addr mapper injects the mapped port as the first candidate":
|
||||
const mockMappedTcpPort = 40000
|
||||
|
||||
setupMappedAddrMapper(
|
||||
sw, NatPortMapper(activeTcpPort: some(Port(mockMappedTcpPort)))
|
||||
)
|
||||
|
||||
# Reach the observation quorum so guessDialableAddr trusts 8.8.8.8
|
||||
let observed = MultiAddress.init("/ip4/8.8.8.8/tcp/4001").expect("valid")
|
||||
let quorum = 3
|
||||
for _ in 0 ..< quorum:
|
||||
discard sw.peerStore.identify.observedAddrManager.addObservation(observed)
|
||||
|
||||
await sw.peerInfo.update()
|
||||
|
||||
# Ensure that the address mapper injects the mapped port as the first candidate
|
||||
# after peer info update
|
||||
check sw.peerInfo.addrs[0] ==
|
||||
MultiAddress.init("/ip4/8.8.8.8/tcp/" & $mockMappedTcpPort).expect("valid")
|
||||
|
||||
test "mapped-addr mapper is a no-op without an active mapping":
|
||||
setupMappedAddrMapper(sw, NatPortMapper())
|
||||
|
||||
let observed = MultiAddress.init("/ip4/8.8.8.8/tcp/4001").expect("valid")
|
||||
let quorum = 3
|
||||
for _ in 0 ..< quorum:
|
||||
discard sw.peerStore.identify.observedAddrManager.addObservation(observed)
|
||||
|
||||
await sw.peerInfo.update()
|
||||
|
||||
# Ensure that nothing is injected because there is no active mapping
|
||||
check sw.peerInfo.addrs == sw.peerInfo.listenAddrs
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user