Skip to content

Integrate Mozilla's mtu crate to constrain MTU discovery#2465

Open
ali-molajani wants to merge 3 commits into
quinn-rs:mainfrom
ali-molajani:main
Open

Integrate Mozilla's mtu crate to constrain MTU discovery#2465
ali-molajani wants to merge 3 commits into
quinn-rs:mainfrom
ali-molajani:main

Conversation

@ali-molajani

Copy link
Copy Markdown

This change layers interface MTU detection on top of user configuration rather than replacing it. When creating client connections, we query the interface MTU for the destination IP using Mozilla's mtu crate and apply it as an additional constraint on the MTU discovery upper bound.

Key changes:

  • Add mtu crate dependency to quinn
  • Query interface MTU when creating client connections in quinn layer
  • Pass interface MTU constraint through to quinn-proto layer
  • Apply constraint in MTU discovery SearchState, respecting user config and peer limits (effective_upper_bound = min(user_bound, interface_mtu, peer_limit))
  • Add tests for interface MTU constraint behavior

The constraint is applied as an additional limit, so user configuration takes precedence if it's more restrictive, maintaining backward compatibility.

This change layers interface MTU detection on top of user configuration
rather than replacing it. When creating client connections, we query the
interface MTU for the destination IP using Mozilla's mtu crate and apply
it as an additional constraint on the MTU discovery upper bound.

Key changes:
- Add mtu crate dependency to quinn
- Query interface MTU when creating client connections in quinn layer
- Pass interface MTU constraint through to quinn-proto layer
- Apply constraint in MTU discovery SearchState, respecting user config
  and peer limits (effective_upper_bound = min(user_bound, interface_mtu, peer_limit))
- Add tests for interface MTU constraint behavior

The constraint is applied as an additional limit, so user configuration
takes precedence if it's more restrictive, maintaining backward
compatibility.

@Ralith Ralith left a comment

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.

This is very clean, thanks! I bet we can do better for migrations/incoming connections with some effort, but no reason to block this straightforward improvement on those subtler problems.

@Ralith

Ralith commented Dec 6, 2025

Copy link
Copy Markdown
Collaborator

See some previous discussion at #1978 also. One other limitation in the current implementation is incorrect behavior when the client involuntarily migrates (e.g. a phone moving between wifi and cellular), though the worst case there is that the MTU remains clamped lower than it should be (if it's too large, that's just the status quo again), so probably not a blocker. We might want to explore monitoring the source address of outgoing packets for changes in follow-up work.

@mxinden

mxinden commented Dec 6, 2025

Copy link
Copy Markdown
Collaborator

//CC @larseggert as the author of the mtu crate.

Comment thread quinn/src/endpoint.rs
// IPv6: 40 bytes IP header + 8 bytes UDP header = 48 bytes
let header_overhead = if addr.is_ipv6() { 48 } else { 28 };
let udp_payload_size = interface_mtu.saturating_sub(header_overhead);
// Convert usize to u16, clamping to u16::MAX if too large

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.

Seems like you are returning None instead of clamping here, no?

if pref_addr_cid.is_some() { 2 } else { 1 },
),
path: PathData::new(remote, allow_mtud, None, 0, now, &config),
path: PathData::new(remote, allow_mtud, None, 0, now, &config, interface_mtu_constraint),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How about we drop the interface_ prefix in names? It seems pretty verbose and it's not like we have a bunch of other MTU constraints right now (can expand more on it in documentation).

Comment thread Cargo.lock
checksum = "7dfdb4953a096c551ce9ace855a604d702e6e62d77fac690575ae347571717f5"

[[package]]
name = "bindgen"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems ugly. Why does mtu need bindgen? Can it be updated to something newer?

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.

Oof, yeah, I missed that. I would rather not have a bindgen build dependency.

@larseggert larseggert Dec 9, 2025

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.

We need bindgen, because libc and similar don't have the necessary bindings for talking to routing sockets.
https://github.com/mozilla/neqo/blob/042e684f33df3127f485deeb3e122a8963168158/mtu/build.rs#L51-L65

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.

Could we commit the bindgen output, or vendor handwritten bindings for the necessary interfaces?

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 we originally tried that. It's a pain, because of course all the BSDs are slightly different.

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.

could you use one of the https://docs.rs/rtnetlink crates?

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.

Fyi we are currently using https://docs.rs/netdev/ for these things, which is quite nice and includes support for parsing MTUs from the network interfaces

@larseggert larseggert Dec 18, 2025

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.

Isn't for the first one Linux-only?

For the second, I think I looked at it in the past, and it didn't do MTU. If it does, maybe we can switch to it for neqo, too.

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.

Ah, I think the issue with netdev was that it doesn't do routing table lookups.

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.

Ah, I think the issue with netdev was that it doesn't do routing table lookups.

I think it would be great to add that to netdev, the developer has been super responsive to any requests and improvements and it is becoming a really great crate to collect these kinds of informations across OSes

Comment thread quinn/Cargo.toml
futures-io = { workspace = true, optional = true }
rustc-hash = { workspace = true }
pin-project-lite = { workspace = true }
mtu = "0.2"

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.

I just published mtu v0.3.0: https://crates.io/crates/mtu/0.3.0

Suggested change
mtu = "0.2"
mtu = "0.3"

larseggert added a commit to larseggert/neqo that referenced this pull request Jan 20, 2026
larseggert added a commit to larseggert/neqo that referenced this pull request Feb 12, 2026
larseggert added a commit to larseggert/neqo that referenced this pull request Mar 27, 2026
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.

6 participants