Skip to content

feat(endpoint): add DirectAddrFilter to drop direct address candidates - #4397

Open
ifdario wants to merge 10 commits into
n0-computer:mainfrom
rayfish:direct-addr-filter
Open

ifdario wants to merge 10 commits into
n0-computer:mainfrom
rayfish:direct-addr-filter

Conversation

@ifdario

@ifdario ifdario commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds Builder::direct_addr_filter to drop addresses from the endpoint's direct
address candidates. A dropped address is not stored, published, or used for
holepunching / NAT-traversal.

This lets an app exclude a virtual interface's address (e.g. a VPN overlay on a
TUN device) that would otherwise be advertised and dialed by peers, looping the
underlay back into the tunnel. addr_filter only covers published data; this runs
on the candidate set before store_direct_addresses, so it also covers in-band
NAT-traversal.

Tests cover local address collection with no filter and with a reject-all filter.

API Changes

  • Adds endpoint::DirectAddrFilter with
    use_nat_candidate(&self, ip: IpAddr) -> bool. Returning false drops the
    address from the endpoint's NAT traversal candidates.
  • Adds endpoint::Builder::direct_addr_filter to set the filter.

No breaking changes. Filtering is optional and disabled by default.

Notes & open questions

Applied at the final gate so it covers all sources (local, QAD, portmap, config).
Could be scoped to local-interface addresses instead if preferred.

Change checklist

  • Self-review.
  • Documentation updates following the style guide, if relevant.
  • Tests if relevant.
  • All API changes documented.
  • This PR was created by a human that thought critically about the
    proposed change and wrote an as clear and concise description as
    they could.
  • This PR isn't slop, and is carefully crafted to do have the
    intented effect.

Add `Builder::direct_addr_filter`, taking a `DirectAddrFilter` trait object
(`Arc<dyn>`, mirroring `PathSelector`), consulted in the magicsock's
`update_direct_addresses` before the direct addresses are stored. A rejected
address is never stored, published via Address Lookup, or offered as a
holepunch / NAT-traversal candidate.

This lets an application exclude the address of a virtual interface (e.g. a
VPN overlay bound on a TUN device) from the candidate set while still binding
to the unspecified address for normal multi-homing. `addr_filter` only filters
published data; this filters the underlying candidate set, so it also covers
in-band NAT-traversal.
@n0bot n0bot Bot added this to iroh Jul 7, 2026
@github-project-automation github-project-automation Bot moved this to 🚑 Needs Triage in iroh Jul 7, 2026
Add a blanket `impl DirectAddrFilter for Vec<IpNet>` so whole CIDR ranges can
be excluded without implementing the trait, plus `Builder::exclude_direct_addrs`
as a convenience wrapper over it for the common case (e.g. dropping a VPN
overlay bound on a TUN device).
@flub

flub commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

For the random observer: this is linked to #4399 and we need to decide which approach is best.

@flub flub added NAT Traversal c-iroh Functionality of the core iroh crate. labels Jul 8, 2026
@ifdario

ifdario commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

I think they fill different needs

`exclude_direct_addrs` now wraps a private `ExcludeNets` newtype instead of
a public `impl DirectAddrFilter for Vec<IpNet>`, which kept a trait impl on
a foreign type out of the public API. `IpNet` is re-exported from
`iroh::endpoint` so callers do not need a matching `ipnet` of their own, and
`ipnet::*` joins the accepted non-1.0 crates in `allowed_external_types`
(the check fails on the `exclude_direct_addrs` signature otherwise).

Also documents the filter as covering the whole candidate set rather than
just local interface addresses, borrows it instead of cloning the `Arc` on
every update, and imports `DirectAddrFilter` instead of naming it inline.
GrizzlT pushed a commit to GrizzlT/rayfish that referenced this pull request Aug 31, 2026
Use iroh's new `Builder::direct_addr_filter` to keep rayfish overlay addresses
(100.64.0.0/10, 200::/7) out of the gathered direct-address set, so a mesh IP
bound on the TUN is never advertised, published, or offered as a holepunch /
NAT-traversal candidate. Previously the overlay IP leaked into the candidate set
and peers dialed it, looping the underlay back through the tunnel (a flapping
path that fell back to relay latency).

Pins iroh (and iroh-base/-dns/-relay) to the fork branch carrying the API,
upstream PR n0-computer/iroh#4397.
Comment thread iroh/src/endpoint.rs Outdated
Comment thread iroh/src/endpoint.rs Outdated
Comment thread iroh/src/endpoint.rs Outdated
Comment thread iroh/src/endpoint.rs Outdated
Comment thread iroh/src/endpoint.rs Outdated
Comment thread iroh/src/endpoint.rs Outdated
Comment thread iroh/src/endpoint.rs Outdated
Comment thread iroh/src/socket.rs Outdated
Comment thread iroh/src/socket.rs Outdated
Comment thread iroh/src/socket.rs Outdated
@github-project-automation github-project-automation Bot moved this from 🚑 Needs Triage to 🏗 In progress in iroh Sep 21, 2026
Rename `keeps` to `use_nat_candidate` and update docs to describe
the filter in terms of NAT traversal candidates rather than direct
addresses.
@ifdario

ifdario commented Sep 23, 2026

Copy link
Copy Markdown
Contributor Author

ok, now the changes are very trivial

samuelmarinsoto pushed a commit to samuelmarinsoto/rayfish that referenced this pull request Sep 27, 2026
Use iroh's new `Builder::direct_addr_filter` to keep rayfish overlay addresses
(100.64.0.0/10, 200::/7) out of the gathered direct-address set, so a mesh IP
bound on the TUN is never advertised, published, or offered as a holepunch /
NAT-traversal candidate. Previously the overlay IP leaked into the candidate set
and peers dialed it, looping the underlay back through the tunnel (a flapping
path that fell back to relay latency).

Pins iroh (and iroh-base/-dns/-relay) to the fork branch carrying the API,
upstream PR n0-computer/iroh#4397.
samuelmarinsoto pushed a commit to samuelmarinsoto/rayfish that referenced this pull request Sep 27, 2026
Use iroh's new `Builder::direct_addr_filter` to keep rayfish overlay addresses
(100.64.0.0/10, 200::/7) out of the gathered direct-address set, so a mesh IP
bound on the TUN is never advertised, published, or offered as a holepunch /
NAT-traversal candidate. Previously the overlay IP leaked into the candidate set
and peers dialed it, looping the underlay back through the tunnel (a flapping
path that fell back to relay latency).

Pins iroh (and iroh-base/-dns/-relay) to the fork branch carrying the API,
upstream PR n0-computer/iroh#4397.
@ifdario
ifdario requested a review from flub September 28, 2026 10:36
@ramfox

ramfox commented Oct 5, 2026

Copy link
Copy Markdown
Member

hello, can you please rebase your branch and then I will run the CI workflow

@matheus23

Copy link
Copy Markdown
Member

Sorry, since the point at which you opened the PR, the PR template has changed a bit.
It would be helpful for us if you listed API changes in an "API Changes" section, instead of the "Breaking Changes" section ✌️

This branch has not been deployed

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

Labels

c-iroh Functionality of the core iroh crate. NAT Traversal

Projects

Status: 🏗 In progress

Development

Successfully merging this pull request may close these issues.

4 participants