Skip to content

refactor(nat): reimplement NAT Traversal#110

Open
markspanbroek wants to merge 14 commits intomainfrom
refactor-nat
Open

refactor(nat): reimplement NAT Traversal#110
markspanbroek wants to merge 14 commits intomainfrom
refactor-nat

Conversation

@markspanbroek
Copy link
Copy Markdown
Contributor

  • no longer sends internal addresses (0.0.0.0, 127.0.0.1, etc...) to the DHT
  • updates the DHT when port mapping changes
  • uses the node's task pool to renew port mapping
  • explicit start and stop of port mapping renewals
  • maps to any external port if specified port doesn't work

Depends on #102

@markspanbroek markspanbroek marked this pull request as ready for review April 2, 2026 10:20
Base automatically changed from pmp-gateway to main April 7, 2026 09:34
- explicit start and stop of port mapping renewals
- updates DHT when external port numbers change
- map to any external port if specified port doesn't work
- use taskpool instead of threadvars
1 thread does not work apparently
Announce port is configured as port 0, so we should take
the actual port from libp2p, not the port from the config
reason: the node now updates the DHT with external
addresses only, so for integration testing we set
an external ip address of 127.0.0.1 manually
Copy link
Copy Markdown
Contributor

@dryajov dryajov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this is a great rewrite, just a few nitpicks, the biggest issue is the thread cancelation handling path.

var mapped: ?!MultiAddress
trace "spawning port mapping background task", address
traversal.taskpool.spawn mapTask(portmapping, address, signal, addr mapped)
if error =? catchAsync(await signal.wait()).errorOption:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't handle cross-thread cancellations correctly on it's own. Cancelling without waiting for the thread to finish, will lead to all sorts of issue with writing to potentially released memory, etc... Look at how we're handling this in erasure and the kvstore, use a .join to wrap the signal future and avoid cancelling it, wait for the tread to finish, and the propagate the cancellation.

maintenance =
BlockMaintainer.new(repoStore, interval = config.overlayMaintenanceInterval)

natTraversal = !NatTraversal.new(config.nat, config.natRenewal, tp)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nat should be best effort, we should not crash the node on nat failure to start.

s.archivistNode.discovery.updateDhtRecord(discoveryAddrs)
let announceAddresses = s.archivistNode.switch.peerInfo.addrs
let discoveryPort = s.config.discoveryPort
let discoveryAddress = MultiAddress.init(IPv4_any(), udpProtocol, discoveryPort)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we hardcoding the address family here? This should be derived from the mapped address, not hardcoded. Also, wouldn't 0.0.0.0, mess with the nat:none path, as it is right now?

of NatStrategy.ExternalIp:
mapping.mapExternalIp(address)
of NatStrategy.None:
mapping.mapRoutingTable(address)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this unconditionally maps private addresses to public routes, even with nat:none? I would put this behind a explicit flag, and reject otherwise. I would not be very happy to find out that I just got exposed to the public internet, when I'm explicitely binding to a lan address and setting nat to none.

traversal.renewing = traversal.renew()
debug "started nat traversal"

proc stop*(traversal: NatTraversal) {.async: (raises: []).} =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we dropping the mappings on stop/shutdown? otherwise they'll just leave stale mapping entries.

import pkg/questionable/results

proc discoverGateways*(upnp: Miniupnp, timeout: Duration): ?!void =
upnp.discoverDelay = timeout.seconds.cint
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this takes ms, you're passing seconds?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants