Skip to content

fix(transport): honor peer UDP payload limits for ordinary datagrams - #4081

Open
zerox80 wants to merge 3 commits into
mozilla:mainfrom
zerox80:codex/peer-udp-payload-limit
Open

zerox80 wants to merge 3 commits into
mozilla:mainfrom
zerox80:codex/peer-udp-payload-limit

Conversation

@zerox80

@zerox80 zerox80 commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

When a peer advertises max_udp_payload_size = 1200, Neqo still uses the default 1252-byte IPv4 or 1232-byte IPv6 payload size for ordinary datagrams. The parameter currently limits upward PMTU probes only, so a receiver enforcing its advertised limit can discard stream-data datagrams even with PMTUD disabled.

Cap Pmtud::plpmtu() by the known peer limit, so normal packet construction, batching and congestion control use the effective payload size. Apply peer-limit updates through the path and sender so the pacer uses the effective packet size immediately, including when a new path inherits the limit. Keep the discovered path MTU and probe search table intact.

The connection tests cover IPv4/IPv6, both senders and PMTUD enabled/disabled. Symmetric limits cover eight sending cases; one-sided limits cover another sixteen. Only traffic towards the peer advertising 1200 bytes is capped. The asymmetric test checks actual GSO segment lengths, prevents oversized probes towards the limited peer, and exercises a larger probe followed by a smaller final datagram in the opposite direction. Both directions deliver stream data.

The PMTUD unit test covers limits below, equal to and above the default, including restarts and the raise timer. A sender test checks the congestion-window floor, bytes in flight across send/ACK, and pacer MTU updates before the first ACK for IPv4/IPv6 and NewReno/Cubic with each of Classic/HyStart/SEARCH slow start.

Reference: RFC 9000, Sections 14 and 18.2.

Local validation (performed by the PR author):

  • The original two regressions fail before the fix with 1252 bytes instead of 1200. All six matching tests, including the asymmetric and sender tests, now pass (cargo test --locked -p neqo-transport peer_max_udp_payload).
  • cargo +nightly fmt --all -- --check: passed.
  • cargo hack clippy with the repository's feature-powerset options and -D warnings: all 45 combinations passed.
  • Workspace documentation with --deny rustdoc::broken_intra_doc_links --deny warnings: passed.
  • cargo hack test with the repository's feature-powerset options: all 38 combinations passed.
  • Cargo deny (advisories, bans, licenses, sources) and both cargo-machete checks: passed.

Copilot AI left a comment

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.

🟡 Changes recommended

Applying the peer limit leaves the pacer using its previous MTU until later ACK or loss processing.

1 open finding
What changed in this PR

Caps ordinary QUIC datagrams by the peer-advertised UDP payload limit while preserving PMTU discovery state.

Changes:

  • Applies the peer limit to effective PLPMTU.
  • Adds IPv4/IPv6, directional, PMTUD, congestion-control, and pacing tests.
File Description
neqo-transport/​src/​pmtud.rs Caps effective PLPMTU and adds unit coverage.
neqo-transport/​src/​sender.rs Tests congestion-control and pacer behavior.
neqo-transport/​src/​connection/​tests/​pmtud.rs Adds end-to-end payload-limit tests.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 118 to 119
pub const fn set_peer_max_udp_payload(&mut self, peer_max_udp_payload: usize) {
self.peer_max_udp_payload = Some(peer_max_udp_payload);
@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.97%. Comparing base (1ec4c2a) to head (9a6a1d7).
⚠️ Report is 18 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4081      +/-   ##
==========================================
- Coverage   97.15%   96.97%   -0.18%     
==========================================
  Files         113      118       +5     
  Lines       41200    41803     +603     
  Branches    41200    41803     +603     
==========================================
+ Hits        40028    40540     +512     
- Misses       1158     1239      +81     
- Partials       14       24      +10     
Flag Coverage Δ
freebsd 94.56% <100.00%> (-0.10%) ⬇️
linux 97.19% <100.00%> (-0.03%) ⬇️
macos 95.35% <100.00%> (-0.02%) ⬇️
windows 95.43% <100.00%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
neqo-common 99.31% <ø> (+<0.01%) ⬆️
neqo-http3 95.38% <ø> (+0.01%) ⬆️
neqo-qpack 97.10% <ø> (+0.01%) ⬆️
neqo-transport 97.96% <100.00%> (-0.06%) ⬇️
neqo-udp 95.37% <ø> (ø)
mtu 89.13% <ø> (ø)

@codspeed

codspeed Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will regress 2 benchmarks

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 4 improved benchmarks
❌ 2 regressed benchmarks
✅ 95 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ Simulation decode 10 STREAM frames 3.8 µs 3.9 µs -2.74%
❌ Simulation simulated/pacing-false/varying-seeds 73.2 ms 75 ms -2.41%
⚡ WallTime walltime/pacing-true/varying-seeds 2.1 ms 2 ms +6.83%
⚡ WallTime walltime/pacing-true/same-seed 2.1 ms 2 ms +4.46%
⚡ WallTime walltime/pacing-false/same-seed 2 ms 2 ms +3.63%
⚡ WallTime walltime/pacing-false/varying-seeds 2 ms 2 ms +2.79%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing zerox80:codex/peer-udp-payload-limit (9a6a1d7) with main (ad89cf0)

Open in CodSpeed

@zerox80 zerox80 closed this Oct 16, 2026
@zerox80
zerox80 deleted the codex/peer-udp-payload-limit branch October 16, 2026 17:37
@zerox80
zerox80 restored the codex/peer-udp-payload-limit branch October 16, 2026 17:40
@zerox80 zerox80 reopened this Oct 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Benchmark results

No significant performance differences relative to ad7d629.

All results
transfer/1-conn/1-100mb-req (aka. Upload)/mtu-1500
       time:   [50.784 ms 50.842 ms 50.901 ms]
       thrpt:  [1.9186 GiB/s 1.9208 GiB/s 1.9230 GiB/s]
transfer/1-conn/1-100mb-resp (aka. Download)/mtu-1500
       time:   [49.474 ms 49.514 ms 49.555 ms]
       thrpt:  [1.9707 GiB/s 1.9723 GiB/s 1.9739 GiB/s]
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high mild
transfer/1-conn/1-1b-resp (aka. HPS)/mtu-1500
       time:   [2.9595 ms 2.9605 ms 2.9616 ms]
       thrpt:  [337.66   B/s 337.78   B/s 337.89   B/s]
Found 4 outliers among 100 measurements (4.00%)
1 (1.00%) low mild
2 (2.00%) high mild
1 (1.00%) high severe
transfer/1-conn/10_000-parallel-1b-resp (aka. RPS)/mtu-1500
       time:   [66.262 ms 66.588 ms 66.923 ms]
       thrpt:  [149.43 Kelem/s 150.18 Kelem/s 150.92 Kelem/s]
streams-flow-controlled/walltime/1-streams/each-4194304-bytes
       time:   [10.459 ms 10.462 ms 10.464 ms]
       thrpt:  [382.26 MiB/s 382.34 MiB/s 382.43 MiB/s]
Found 3 outliers among 100 measurements (3.00%)
2 (2.00%) low mild
1 (1.00%) high mild
streams-flow-controlled/walltime/10-streams/each-1048576-bytes
       time:   [29.057 ms 29.066 ms 29.075 ms]
       thrpt:  [343.93 MiB/s 344.04 MiB/s 344.15 MiB/s]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
streams/walltime/1-streams/each-1000-bytes
       time:   [130.51 µs 131.03 µs 131.70 µs]
       thrpt:  [7.2412 MiB/s 7.2783 MiB/s 7.3074 MiB/s]
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high mild
streams/walltime/1000-streams/each-1-bytes
       time:   [3.7985 ms 3.8113 ms 3.8237 ms]
       thrpt:  [255.40 KiB/s 256.23 KiB/s 257.09 KiB/s]
streams/walltime/1000-streams/each-1000-bytes
       time:   [12.403 ms 12.410 ms 12.417 ms]
       thrpt:  [76.804 MiB/s 76.847 MiB/s 76.890 MiB/s]
Found 2 outliers among 100 measurements (2.00%)
1 (1.00%) low mild
1 (1.00%) high mild
transfer/walltime/pacing-false/same-seed
       time:   [2.0326 ms 2.0343 ms 2.0365 ms]
       thrpt:  [1.9182 GiB/s 1.9202 GiB/s 1.9218 GiB/s]
transfer/walltime/pacing-false/varying-seeds
       time:   [2.0273 ms 2.0281 ms 2.0291 ms]
       thrpt:  [1.9251 GiB/s 1.9261 GiB/s 1.9268 GiB/s]
transfer/walltime/pacing-true/same-seed
       time:   [2.0763 ms 2.0781 ms 2.0803 ms]
       thrpt:  [1.8777 GiB/s 1.8797 GiB/s 1.8813 GiB/s]
transfer/walltime/pacing-true/varying-seeds
       time:   [2.0238 ms 2.0248 ms 2.0261 ms]
       thrpt:  [1.9279 GiB/s 1.9292 GiB/s 1.9302 GiB/s]

Instructions per cycle

Criterion reported no significant timing changes.

All benchmarks
Benchmark IPC before IPC after ΔIPC
streams/walltime/1000-streams/each-1000-bytes 2.99 3.02 +1.0%
transfer/1-conn/1-100mb-resp (aka. Download)/mtu-1500 3.20 3.23 +0.9%
streams/walltime/1000-streams/each-1-bytes 3.23 3.26 +0.9%
transfer/1-conn/10_000-parallel-1b-resp (aka. RPS)/mtu-1500 3.20 3.18 -0.6%
transfer/1-conn/1-100mb-req (aka. Upload)/mtu-1500 3.16 3.15 -0.4%
streams-flow-controlled/walltime/10-streams/each-1048576-bytes 3.17 3.16 -0.3%
transfer/walltime/pacing-true/varying-seeds 2.86 2.87 +0.2%
streams-flow-controlled/walltime/1-streams/each-4194304-bytes 3.00 3.01 +0.1%
transfer/1-conn/1-1b-resp (aka. HPS)/mtu-1500 2.25 2.24 -0.1%
transfer/walltime/pacing-false/same-seed 2.87 2.87 +0.1%
transfer/walltime/pacing-true/same-seed 2.86 2.86 -0.1%
transfer/walltime/pacing-false/varying-seeds 2.88 2.87 -0.0%
streams/walltime/1-streams/each-1000-bytes 2.54 2.54 +0.0%
Profiles for profiler.firefox.com (64)

Download data for profiler.firefox.com or download performance comparison data.

@github-actions

Copy link
Copy Markdown
Contributor

Client/server transfer results

Performance differences relative to ad7d629.

Transfer of 33554432 bytes over loopback, min. 100 runs. All unit-less numbers are in milliseconds.

Client vs. server Mean±σ Min–Max Median±MAD MiB/s±σ ΔMedian
google-neqo-cubic 70.6 ± 0.3 69.9 – 71.5 70.5 ± 0.3 453.6 ± 2.2 💔 +1.6 (+2.3%)
neqo-neqo-cubic-nopacing 19.2 ± 0.2 18.8 – 19.9 19.2 ± 0.2 1667.3 ± 16.5 💚 -0.2 (-1.2%)
neqo-neqo-newreno 19.2 ± 0.1 18.9 – 19.7 19.2 ± 0.1 1664.2 ± 12.3 💚 -0.4 (-1.9%)
neqo-neqo-newreno-nopacing 19.0 ± 0.2 18.5 – 19.6 19.0 ± 0.2 1684.4 ± 18.1 💚 -0.3 (-1.4%)
quiche-neqo-cubic 30.4 ± 0.3 29.4 – 32.1 30.3 ± 0.3 1053.0 ± 11.8 💚 -0.3 (-1.0%)

Table above only shows statistically significant changes. See all results below.

All results

Transfer of 33554432 bytes over loopback, min. 100 runs. All unit-less numbers are in milliseconds.

Client vs. server Mean±σ Min–Max Median±MAD MiB/s±σ ΔMedian
google-google 133.8 ± 0.5 132.6 – 135.2 133.9 ± 0.6 239.1 ± 1.0
google-neqo-cubic 70.6 ± 0.3 69.9 – 71.5 70.5 ± 0.3 453.6 ± 2.2 💔 +1.6 (+2.3%)
neqo-google-cubic 243.1 ± 50.8 178.7 – 535.8 231.0 ± 33.3 131.6 ± 27.5 +5.2 (+2.3%)
neqo-neqo-cubic 19.5 ± 0.1 19.1 – 19.9 19.5 ± 0.1 1640.2 ± 12.2 -0.1 (-0.5%)
neqo-neqo-cubic-nopacing 19.2 ± 0.2 18.8 – 19.9 19.2 ± 0.2 1667.3 ± 16.5 💚 -0.2 (-1.2%)
neqo-neqo-newreno 19.2 ± 0.1 18.9 – 19.7 19.2 ± 0.1 1664.2 ± 12.3 💚 -0.4 (-1.9%)
neqo-neqo-newreno-nopacing 19.0 ± 0.2 18.5 – 19.6 19.0 ± 0.2 1684.4 ± 18.1 💚 -0.3 (-1.4%)
neqo-quiche-cubic 30.1 ± 0.2 29.4 – 30.9 30.1 ± 0.2 1064.6 ± 8.8 +0.2 (+0.6%)
neqo-s2n-cubic 38.3 ± 0.2 37.9 – 38.8 38.3 ± 0.2 835.3 ± 4.1 -0.1 (-0.2%)
quiche-neqo-cubic 30.4 ± 0.3 29.4 – 32.1 30.3 ± 0.3 1053.0 ± 11.8 💚 -0.3 (-1.0%)
quiche-quiche 35.8 ± 0.3 35.0 – 36.6 35.8 ± 0.3 893.6 ± 6.4
s2n-neqo-cubic 117.2 ± 0.3 116.5 – 118.1 117.2 ± 0.3 273.0 ± 0.7 +0.2 (+0.1%)
s2n-s2n ⚠️ 165.7 ± 23.0 136.9 – 259.7 160.5 ± 0.8 193.1 ± 26.8

Download data for profiler.firefox.com or download performance comparison data.

@github-actions

Copy link
Copy Markdown
Contributor

Failed Interop Tests

QUIC Interop Runner, client vs. server, differences relative to main at ad89cf0.

neqo-pr as clientneqo-pr as server
neqo-pr vs. go-x-net: BP BA
neqo-pr vs. haproxy: ⚠️M BP BA
neqo-pr vs. kwik: ⚠️C20 Z L1 C1 ⚠️6
neqo-pr vs. linuxquic: ⚠️C1
neqo-pr vs. lsquic: L1 C1
neqo-pr vs. msquic: Z A L1 C1
neqo-pr vs. mvfst: A
neqo-pr vs. neqo: Z A
neqo-pr vs. nginx: BP BA
neqo-pr vs. ngtcp2: Z L1 CM
neqo-pr vs. picoquic: Z A
neqo-pr vs. quic-go: A
neqo-pr vs. quiche: BP BA
neqo-pr vs. s2n-quic: CM
neqo-pr vs. tquic: S BP BA
neqo-pr vs. xquic: S R Z A L1 C1
go-x-net vs. neqo-pr: CM
kwik vs. neqo-pr: BP BA CM
msquic vs. neqo-pr: CM
mvfst vs. neqo-pr: Z L1 C1 CM
neqo vs. neqo-pr: Z A
openssl vs. neqo-pr: LR M A CM
quic-go vs. neqo-pr: CM
quic-zig vs. neqo-pr: CM
quiche vs. neqo-pr: CM
quinn vs. neqo-pr: V2 CM
s2n-quic vs. neqo-pr: ⚠️B CM
tquic vs. neqo-pr: CM
xquic vs. neqo-pr: M CM
All results

Succeeded Interop Tests

QUIC Interop Runner, client vs. server

neqo-pr as client

neqo-pr as server

Unsupported Interop Tests

QUIC Interop Runner, client vs. server

neqo-pr as client

neqo-pr as server

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants