Skip to content

Conversation

@dignifiedquire
Copy link
Contributor

@dignifiedquire dignifiedquire commented Nov 22, 2025

Description

This allows for multiple interfaces to be bound, and be actually used.

You can now use this by passing

let endpoint = Endpoint::builder()
  .bind_addr_v4_default("127.0.0.1", 1234)
  .bind_addr_v4("192.168.1.2/24".parse()?, 1234) // bind to prefix_len of 24
  .bind()
  .await?

The selection of the interface is done internally by first looking at all specific bindings, and then fallbing back to the default version for this family.

Breaking Changes

  • iroh
    • changed
      • Endpoint::bind_addr_v4 to Endpoint::bind_addr_v4_default
      • Endpoint::bind_addr_v6 to Endpoint::bind_addr_v6_default
    • added
      • Endpoint::bind_addr_v4
      • Endpoint::bind_addr_v6
    • endpoint::Ipv4Net
    • endpoint::Ipv6Net

@github-actions
Copy link

github-actions bot commented Nov 22, 2025

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

Last updated: 2026-01-07T20:54:33Z

@github-actions
Copy link

github-actions bot commented Nov 22, 2025

Netsim report & logs for this PR have been generated and is available at: LOGS
This report will remain available for 3 days.

Last updated for commit: 94a6e24

@n0bot n0bot bot added this to iroh Nov 22, 2025
@github-project-automation github-project-automation bot moved this to 🏗 In progress in iroh Nov 22, 2025
@dignifiedquire dignifiedquire marked this pull request as ready for review November 22, 2025 13:46
@dignifiedquire dignifiedquire changed the title feat(iroh): allow IP transports to bind to multiple interfaces feat(iroh)!: allow IP transports to bind to multiple interfaces Nov 22, 2025
@dignifiedquire dignifiedquire changed the title feat(iroh)!: allow IP transports to bind to multiple interfaces feat(iroh)!: allow multiple IP transports, including filtering by interface Nov 22, 2025
@dignifiedquire dignifiedquire force-pushed the feat-bind-interfaces branch 2 times, most recently from 6726f3c to 479652f Compare November 25, 2025 10:13
@dignifiedquire dignifiedquire changed the base branch from feat-multipath to main December 19, 2025 10:39
@Frando
Copy link
Member

Frando commented Dec 19, 2025

We could remove the _default API I think, if you set a netmask of 0 it will apply to everything. And instead return an error if you bind conflicting networks.

@dignifiedquire
Copy link
Contributor Author

We could remove the _default API I think, if you set a netmask of 0 it will apply to everything. And instead return an error if you bind conflicting networks.

well currently they are treated differently internally as an explicit fallback, thats why the difference in configuration exists

Copy link
Member

@matheus23 matheus23 left a comment

Choose a reason for hiding this comment

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

I'm not super convinced this is the right API 🤔

Looking at your example:

let endpoint = Endpoint::builder()
  .bind_addr_v4_default("127.0.0.1", 1234)
  .bind_addr_v4("192.168.1.2/24".parse()?, 1234) // bind to prefix_len of 24
  .bind()
  .await?

It looks like the configuration for an endpoint that will only send traffic in the local network and localhost.
So let's say quinn produces a datagram that's meant to be sent to 192.168.1.101:1234.
Iroh will then first check this against your 192.168.1.2/24 and find a match. However, it turns out that interface is actually busy, so it continues.
Then it'll see the "default" 127.0.0.1 interface and try to send on that. But obviously it can't send outside localhost, so ideally that should just fail sending, but I think it won't? I'm not actually sure what will happen.

Also another thing about the default socket: What if someone wants to bind a dual-stack socket with [::]? I think that should match against an IPv4 address, but IIUC currently it doesn't?

Minor note: I'm not sure what should happen if you bind multiple sockets to the same port, but different IP addrs. What happens in these cases? I'm not sure.

@flub flub added this to the iroh: v1.0.0-rc.0 milestone Dec 22, 2025
@dignifiedquire
Copy link
Contributor Author

What if someone wants to bind a dual-stack socket

We don't support dual socket, not currently, and not in this setup

@Frando
Copy link
Member

Frando commented Jan 7, 2026

So, if we removed the default, we could have this:

let endpoint = Endpoint::builder()
  .bind_addr_v4("192.168.1.2/0".parse()?, 0) // bind to prefix_len of 0
  .bind_addr_v4("10.0.0.3/24".parse()?, 0) // bind to prefix_len of 24
  .bind_addr_v6("[::]/0".parse()?, 0)
  .bind()
  .await?

Which would mean:

  • Send packets with destination 10.0.0.* via interface 10.0.0.3
  • Send packets with destination *.*.*.* via interface 192.168.1.2
  • Send all IPv6 packets via the wildcard bind

Which is, I think, the common thing people would want from this API. I.e. have a default interface (netmask prefix len 0) and a more specific interface that only handles destinations in the interface's subnet.

But what would happen if a user sets up binds, but not one with prefix len 0? Then we would have to error for all sends that are not matched by any of the netmasks of the bound addrs.

Not sure if this is preferable to the the _default API, but it's one public API less.

@flub flub moved this from 🏗 In progress to 👀 In review in iroh Jan 7, 2026
return false;
}
Self::V6 {
ip_addr, scope_id, ..
Copy link
Member

Choose a reason for hiding this comment

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

I think this is just an unused variable warning that makes CI barf (scope_id is unused)

Comment on lines +82 to +83
if let Some(src) = src {
match self {
Copy link
Member

Choose a reason for hiding this comment

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

Thoughts on code style: Perhaps matching on (src, self) is potentially slightly nicer?

@matheus23
Copy link
Member

Then it'll see the "default" 127.0.0.1 interface and try to send on that. But obviously it can't send outside localhost, so ideally that should just fail sending, but I think it won't? I'm not actually sure what will happen.

I think the example you give in the PR description is still weird. You wouldn't want to use a 127.0.0.1 address as the default address to bind to.
I'm not sure if you'd want to be able to have no default address? But I guess in that example, I'd say what you actually want is no default address and this configuration instead:

let endpoint = Endpoint::builder()
  .bind_addr_v4("127.0.0.1/8", 1234)
  .bind_addr_v4("192.168.1.2/24".parse()?, 1234) // bind to prefix_len of 24
  .bind()
  .await?

I.e. that'd be a configuration that only allows sending on localhost or LAN.

That said, one should be aware that this still in theory allows the endpoint to receive & process traffic from outside those netmasks.

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

Labels

None yet

Projects

Status: 👀 In review

Development

Successfully merging this pull request may close these issues.

6 participants