Skip to content

feat(proto): Discover NAT candidates from off-path PATH_CHALLENGEs#647

Draft
Frando wants to merge 1 commit into
mainfrom
Frando/feat-discover-off-path-probes
Draft

feat(proto): Discover NAT candidates from off-path PATH_CHALLENGEs#647
Frando wants to merge 1 commit into
mainfrom
Frando/feat-discover-off-path-probes

Conversation

@Frando
Copy link
Copy Markdown
Member

@Frando Frando commented May 12, 2026

Description

This is claude's attempt at fixing holepunching when the server is behind a hard NAT - not yet reviewed.
See n0-computer/iroh#4254 for the iroh PR using this to unignore two so-far failing patchbay NAT tests.

When the server is behind a symmetric NAT, its NAT sets a different mapping per destination 4-tuple. Say the server discovers its reflexive address by pinging a relay, let's call it S_relay, and advertises via ADD_ADDRESS. This address is unreachable from the client. When the server sends a PATH_CHALLENGE to the client directly, its NAT sets a fresh binding S_client against the client's address. The client sees an off-path PATH_CHALLENGE arriving from
S_client, a source no ADD_ADDRESS announced.

Old behavior: the client sends a PATH_RESPONSE back to S_client and forgets the source. No path is ever opened, because the server never opens paths.

New behavior: the client treats S_client as a fresh probe target and sends its own PATH_CHALLENGE there. The server's NAT recognizes return traffic on the binding it just defined and forwards it through. The PATH_RESPONSE matches one of the client's outstanding challenges, and the existing client-only path-opening flow opens the path.

Sequence ids for discovered addresses walk down from VarInt::MAX so they cannot collide with peer-advertised ADD_ADDRESS ids (which start at 0).

Breaking Changes

Notes & open questions

Change checklist

  • Self-review.
  • Documentation updates following the style guide, if relevant.
  • Tests if relevant.
  • All breaking changes documented.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 12, 2026

Documentation for this PR has been generated and is available at: https://n0-computer.github.io/noq/pr/647/docs/noq/

Last updated: 2026-05-13T18:37:20Z

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 12, 2026

Performance Comparison Report

75496b54101f42bf1e4c0ccd5d87d50f7617482e - artifacts

Raw Benchmarks (localhost)

Scenario noq upstream Delta CPU (avg/max)
large-single 5631.1 Mbps 7985.4 Mbps -29.5% 96.0% / 101.0%
medium-concurrent 5504.5 Mbps 7970.0 Mbps -30.9% 94.5% / 101.0%
medium-single 3899.4 Mbps 4749.5 Mbps -17.9% 97.4% / 151.0%
small-concurrent 3804.0 Mbps 5344.1 Mbps -28.8% 99.4% / 152.0%
small-single 3511.1 Mbps 4754.8 Mbps -26.2% 91.0% / 101.0%

Netsim Benchmarks (network simulation)

Condition noq upstream Delta
ideal 3067.8 Mbps 4064.7 Mbps -24.5%
lan 782.4 Mbps 810.3 Mbps -3.4%
lossy 69.8 Mbps 69.9 Mbps ~0%
wan 83.8 Mbps 83.8 Mbps ~0%

Summary

noq is 26.5% slower on average

---
1749cb88ac6fd32463056d0d0b431d4e5872954a - artifacts

Raw Benchmarks (localhost)

Scenario noq upstream Delta CPU (avg/max)
large-single 5647.9 Mbps 7871.5 Mbps -28.2% 96.9% / 98.4%
medium-concurrent 5479.2 Mbps 7862.7 Mbps -30.3% 95.9% / 97.7%
medium-single 4263.7 Mbps 4749.9 Mbps -10.2% 96.6% / 98.6%
small-concurrent 3841.8 Mbps 5257.2 Mbps -26.9% 97.7% / 99.7%
small-single 3576.9 Mbps 4849.8 Mbps -26.2% 95.9% / 98.1%

Netsim Benchmarks (network simulation)

Condition noq upstream Delta
ideal 2985.6 Mbps 3939.8 Mbps -24.2%
lan 782.4 Mbps 810.3 Mbps -3.4%
lossy 69.8 Mbps 69.8 Mbps ~0%
wan 83.8 Mbps 83.8 Mbps ~0%

Summary

noq is 24.7% slower on average

@n0bot n0bot Bot added this to iroh May 12, 2026
@github-project-automation github-project-automation Bot moved this to 🚑 Needs Triage in iroh May 12, 2026
When the server is behind a symmetric NAT, its ADD_ADDRESS-advertised
reflexive address is mapped only against the relay and is unreachable
from the client. The server's own probes do reach the client when the
client's NAT is permissive, but they come from a different,
NAT-rebound source address. Until now we replied with PATH_RESPONSE
and forgot that source, so no path was ever opened.

On the client side, treat the source of an incoming off-path
PATH_CHALLENGE as a fresh probe target. The client probes back on the
4-tuple the server's NAT just opened, the response matches one of the
client's own sent challenges, and the existing client-only
path-opening flow opens the path.

Sequence ids for discovered addresses walk down from VarInt::MAX so
they cannot collide with peer-advertised ADD_ADDRESS ids (which start
at 0).
@Frando Frando force-pushed the Frando/feat-discover-off-path-probes branch from 75496b5 to 1749cb8 Compare May 13, 2026 18:34
self.paths_to_be_opened.push(network_path);
return true;
} else {
debug!("inconsistent remote addrs and seq");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
debug!("inconsistent remote addrs and seq");
self.paths_to_be_opened.push(network_path);
return true;

I think the entire PR should be replaced by this, probably.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

logically at least, exact code can be written a bit nicer I think

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

Labels

None yet

Projects

Status: 🚑 Needs Triage

Development

Successfully merging this pull request may close these issues.

2 participants