Skip to content

fix(uring): match tokio's MTU and honor quic.mtu_discovery - #6414

Merged
kixelated merged 7 commits into
mainfrom
quest/m1/uring-mtu
Oct 24, 2026
Merged

kixelated merged 7 commits into
mainfrom
quest/m1/uring-mtu

Conversation

@kixelated

@kixelated kixelated commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Completes quest/m1/uring-mtu.md.

Problem

The io_uring QUIC driver pinned its initial and minimum MTU at 1350 bytes, turned discovery off, and advertised a 1350-byte max_udp_payload_size. moq-quic pads the client Initial to the current MTU, so any path that cannot carry 1350 bytes (an IPv6 path at its 1280-byte link minimum, a tunnel) black-holed the handshake with no fallback. Tokio starts at the 1200-byte floor. The relay also refused quic.mtu_discovery under --runtime-io-uring.

Reproduced first: a userspace hop that drops anything above 1232 bytes (IPv6 minimum less headers) timed the handshake out, and with no cap the ring sent 1350-byte datagrams with discovery "off".

Changes

  • rs/moq-uring/src/quic/driver/mod.rs: drop the initial_mtu/min_mtu pins and the max_udp_payload_size override, so the ring uses moq-quic's defaults like tokio (1200 floor, 1472 advertised). Discovery is turned off unless Transport::mtu_discovery is set, mirroring moq_tokio::quic::backend::apply_transport. endpoint_config can no longer fail, so it returns the config directly.
  • rs/moq-uring/src/quic/mod.rs: delete SEGMENT; add Transport::mtu_discovery: bool (default false).
  • rs/moq-uring/src/quic/driver/endpoint.rs: pass allow_mtud = true. This is sound because every udp::Socket now sets IP_PMTUDISC_PROBE and IPV6_DONTFRAG (from moq-dev/moq#6397), so an oversized probe is lost rather than fragmented. Without it, discovery would stay off whatever the setting.
  • rs/moq-uring/src/quic/driver/connection.rs: size each GSO train as capacity / current_mtu, not capacity / 1350. At a discovered 1452 the old formula gives 48 segments, 69,696 bytes, which overflows the 64 KiB transmit buffer.
  • DATAGRAM_WINDOW was 64 * SEGMENT (86,400 bytes). It is now a plain 96 KiB, about 64 datagrams at the largest MTU discovery reaches.
  • rs/moq-relay/src/uring.rs: map quic.mtu_discovery onto the workers instead of refusing it, with a unit test that the default and the set value both arrive. doc/bin/relay/config.md no longer says io_uring refuses it.
  • Quests: deletes quest/m1/uring-mtu.md and its links. quest/m2/perf/uring-profile.md keeps its GSO-on-jumbo-paths item, with the dead link removed.

Tests

rs/moq-uring/tests/integration/mtu.rs puts two ring endpoints behind a userspace hop that silently drops datagrams above an MTU you can change at runtime. The tests run on the wall clock because they cross real sockets, and they take 0.1 to 0.6 s each:

  • a_path_at_the_ipv6_minimum_connects: a 1232-byte path connects and carries 1 MiB each way. Fails before this PR (handshake timeout).
  • discovery_stays_off_by_default: no datagram either way exceeds 1200. Fails before this PR (1350).
  • discovery_finds_a_larger_mtu: with discovery on over a 1472-byte path, most of a 1 MiB transfer rides above the floor, and GSO trains still batch (worker tx_datagrams above twice tx_sends; measured about 18x). It fails if trains are sized with the old 1350 or if allow_mtud stays false (both mutations checked).
  • a_shrinking_path_falls_back_to_the_floor: after discovery the path drops to 1232 mid-connection, and 1 MiB each way still completes once black-hole detection falls back.

Stress run: 30/30 iterations of the four tests passed. Like the other moq-uring integration tests, these skip below the kernel floor and run in CI on ubuntu-24.04-arm (release gate and nightly rs uring) under MOQ_STRICT=1.

Public API / wire

  • Public API: additive. New mtu_discovery: bool field on the #[non_exhaustive] moq_uring::quic::Transport, named after moq_tokio::quic::Resolved::mtu_discovery. The crate-private SEGMENT is removed. Behavior change for direct moq-uring users: connections start at 1200 bytes instead of 1350 and advertise a 1472-byte receive limit.
  • Wire: none.
  • Config: quic.mtu_discovery now works under --runtime-io-uring instead of being refused at startup.

Checks

  • just check: passed (748 + 223 tests, all modules).
  • cargo nextest run -p moq-relay --features io-uring --lib uring:: and cargo clippy -p moq-relay --features io-uring --all-targets -D warnings: passed. just check builds default features, which leave out the relay's io_uring module.
  • just test interop --all: not run (no wire or moq-ffi change).

🤖 Generated with Claude Code

https://claude.ai/code/session_01EeyiEy5FWz3z44DYZVo7vU

(Written by Claude Opus 5.5)

kixelated and others added 4 commits October 22, 2026 16:41
Quest-Claim: b9932b2c63486756
Every send error but ECONNREFUSED, an unreachable destination, and the
GSO fallback failed the io_uring socket for good, and with it every
connection its worker serves. EPERM is the realistic trigger: a
netfilter drop or a full conntrack table, which a spoofed-source flood
can fill.

Classify by errno: unreachable stays per destination; EBADF, ENOTSOCK
and EFAULT, which mean the socket itself is unusable, still fail it;
everything else, a short send included, is loss, logged at most once a
minute (ECONNREFUSED and EMSGSIZE at debug). Receives also tolerate
ECONNRESET and EMSGSIZE, as tokio does. The ring's sockets now set
IP(V6)_PMTUDISC_PROBE and IPV6_DONTFRAG like moq-sock's, so a forged
ICMP fragmentation-needed cannot shrink a path below a GSO train.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeyiEy5FWz3z44DYZVo7vU
TxBuf::send still promised every failed send surfaced on the next pool
acquire. Only an unusable socket, or an unrouted unreachable send, does now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ring pinned its initial and minimum MTU at 1350 bytes and advertised a
1350-byte max_udp_payload_size, so a path carrying fewer bytes (an IPv6
path at its 1280-byte minimum, a tunnel) black-holed the handshake. Drop
the overrides so the ring starts at moq-quic's 1200-byte floor like tokio,
add `quic::Transport::mtu_discovery` (off by default), allow discovery on
the endpoint now that every socket refuses to fragment, and size each GSO
train from the connection's current MTU. The relay maps
`quic.mtu_discovery` instead of refusing it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeyiEy5FWz3z44DYZVo7vU
@kixelated kixelated changed the title io_uring MTU matches tokio fix(uring): match tokio's MTU and honor quic.mtu_discovery Oct 23, 2026
@kixelated
kixelated marked this pull request as ready for review October 23, 2026 00:03
@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 49 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e52b9e14-6deb-42e0-8bc9-9afb1a24a79c
📥 Commits

Reviewing files that changed from the base of the PR and between fe9bf01 and 31f4937.

📒 Files selected for processing: 12
  • doc/bin/relay/config.md
  • quest/m1/README.md
  • quest/m1/uring-mtu.md
  • quest/m2/perf/uring-profile.md
  • quest/m2/relay-io-uring-package.md
  • rs/moq-relay/src/uring.rs
  • rs/moq-uring/src/quic/driver/connection.rs
  • rs/moq-uring/src/quic/driver/endpoint.rs
  • rs/moq-uring/src/quic/driver/mod.rs
  • rs/moq-uring/src/quic/mod.rs
  • rs/moq-uring/tests/integration/main.rs
  • rs/moq-uring/tests/integration/mtu.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-23T23:14:11.175507Z 31f4937 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@kixelated

Copy link
Copy Markdown
Collaborator Author

Outcome: the quest is complete and the PR is marked ready. No decisions are open. Two judgment calls, both open to challenge:

  • The tests use a small in-test hop that drops by size, not a new moq-shaper MTU impairment. The shaper has no size filter and no runtime trigger for a path change, and adding them would grow this PR into a second crate. Suggested follow-up: give the shaper a path MTU knob once a second consumer appears, such as a tokio mtu_discovery test.
  • DATAGRAM_WINDOW went from 64 * 1350 to 96 KiB, since the 1350 constant no longer exists.

Sequencing: merge #6397 first, then rebase this branch to drop its cherry-picked commits.

(Written by Claude Opus 5.5)

@kixelated

Copy link
Copy Markdown
Collaborator Author

@codex review

(Written by Claude Opus 5.5)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: d63be9ba06

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kixelated

Copy link
Copy Markdown
Collaborator Author

Merging. Matches tokio's MTU defaults on io_uring and honors quic.mtu_discovery, as the maintainer chose. CI is green, the current head has a non-Grok review, and quest check passes against main.

(Written by Claude Opus 5.5)

@kixelated
kixelated enabled auto-merge October 23, 2026 18:09
kixelated and others added 2 commits October 23, 2026 12:19
# Conflicts:
#	quest/m1/README.md
#	quest/m1/uring-local-addr.md
#	quest/m1/uring-mtu.md
#	quest/m2/relay-io-uring-package.md
#	rs/moq-uring/src/udp.rs
#	rs/moq-uring/tests/integration/main.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated added this pull request to the merge queue Oct 23, 2026
@kixelated

Copy link
Copy Markdown
Collaborator Author

Merged origin/main (no rebase) after moq-dev/moq#6400 and moq-dev/moq#6397 landed:

  • rs/moq-uring/src/udp.rs: this PR never changed it (its fix(uring): a failed send costs its packet, not the socket #6397 cherry-picks match the squashed commit exactly), so it resolves to main's version, with fix(uring): reply from the local address a packet arrived on #6400's resent flag and GSO-rejection path intact. No send-path logic changed.
  • rs/moq-uring/tests/integration/main.rs: keeps both mod local_addr; and mod mtu;.
  • Quests: both quest/m1/uring-mtu.md and quest/m1/uring-local-addr.md are complete, so both files and their index lines in quest/m1/README.md and quest/m2/relay-io-uring-package.md are gone.
  • Dropped a reasonless #[allow(clippy::field_reassign_with_default)] in the relay's mtu_discovery_is_applied test, which main's lint rule (moq-dev/moq#6159) rejects and the test does not need.
  • Removed the stale "Stacking" section from the body.

Checks: just check passed; all 134 moq-uring tests passed under MOQ_STRICT=1, including the four mtu:: and three local_addr:: integration tests (some needed a rerun only because other local processes exhausted the shared RLIMIT_MEMLOCK); relay uring:: tests and clippy -D warnings with io-uring passed; quest check --base origin/main passed.

(Written by Claude Opus 5.5)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Merged main into the branch (31f4937) to clear a conflict in quest/m1/README.md: kept main's new uring-listen-extensions entry and dropped the uring-mtu entry this PR finishes. No source changes. just check and quest check --base origin/main pass locally; Codex approved the last authored commit (0be76ee). Re-queuing.

(Written by Claude Opus 5.5)

@kixelated
kixelated added this pull request to the merge queue Oct 23, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31f4937569

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +281 to +284
// moq-quic probes by default; the 1200-byte floor reaches every path.
if !config.mtu_discovery {
transport.mtu_discovery_config(None);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bound MTU discovery by the socket's transmit-buffer ceiling

When a direct moq-uring consumer enables discovery with a valid smaller udp::Config::tx_buffer_len, such as 1350 bytes, the handshake succeeds but discovery eventually terminates the connection. The default discovery upper bound is 1452 bytes, and moq_quic::Connection::poll_transmit emits individual probes larger than current_mtu() regardless of the calculated segment count. udp::TxBuf::send rejects these probes because their length exceeds the configured buffer ceiling, and flush_one treats that rejection as a terminal I/O error. Bound discovery's upper limit by the socket's transmit-buffer size so enabling discovery does not break connections using smaller buffers.

Useful? React with 👍 / 👎.

Merged via the queue into main with commit 8a13d58 Oct 24, 2026
10 checks passed
@kixelated
kixelated deleted the quest/m1/uring-mtu branch October 24, 2026 00:47
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.

1 participant