Skip to content

fix: gate SCONE Initial padding on connection parameter#3573

Merged
mxinden merged 2 commits intomozilla:v0.24from
mxinden:disable-scone-padding
Apr 24, 2026
Merged

fix: gate SCONE Initial padding on connection parameter#3573
mxinden merged 2 commits intomozilla:v0.24from
mxinden:disable-scone-padding

Conversation

@mxinden
Copy link
Copy Markdown
Member

@mxinden mxinden commented Apr 24, 2026

The scone connection parameter (off by default since #3492) only gated advertisement of the SCONE transport parameter. The Initial packet SCONE indication (0xc8 0x13) was still emitted unconditionally, so every default-config client was signaling SCONE on the wire without actually negotiating it.

This is suspected — not yet confirmed — to be the cause of a Firefox 150 regression where Facebook's edge reacts to the indicator and Bitdefender's HTTP/3 inspection then trips on the altered server-side
frames (bug 2034178). The QUIC handshake still completes, so the H3->H2 fallback never kicks in.

Gate both the build-time reservation and the pad-time indicator on scone_enabled(); when disabled, pad Initials with zeros as before SCONE was introduced.


Includes the version bump to v0.24.2.

Pull request targets the v0.24 branch, branched off of the v0.24.1 tag.

mxinden added 2 commits April 24, 2026 10:56
The `scone` connection parameter (off by default since mozilla#3492) only
gated advertisement of the SCONE transport parameter. The Initial-
packet SCONE indication (`0xc8 0x13`) was still emitted unconditionally,
so every default-config client was signaling SCONE on the wire without
actually negotiating it.

This is suspected — not yet confirmed — to be the cause of a Firefox
150 regression where Facebook's edge reacts to the indicator and
Bitdefender's HTTP/3 inspection then trips on the altered server-side
frames (bug 2034178). The QUIC handshake still completes, so the
H3->H2 fallback never kicks in.

Gate both the build-time reservation and the pad-time indicator on
`scone_enabled()`; when disabled, pad Initials with zeros as before
SCONE was introduced.
Copy link
Copy Markdown
Collaborator

@KershawChang KershawChang left a comment

Choose a reason for hiding this comment

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

Looks good

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 24, 2026

Codecov Report

❌ Patch coverage is 76.92308% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.30%. Comparing base (5b4e850) to head (5acfebb).
⚠️ Report is 2 commits behind head on v0.24.

Additional details and impacted files
@@           Coverage Diff           @@
##            v0.24    #3573   +/-   ##
=======================================
  Coverage   94.30%   94.30%           
=======================================
  Files         127      127           
  Lines       38739    38744    +5     
  Branches    38739    38744    +5     
=======================================
+ Hits        36532    36538    +6     
+ Misses       1369     1367    -2     
- Partials      838      839    +1     
Flag Coverage Δ
linux 94.30% <76.92%> (+<0.01%) ⬆️
macos 94.19% <76.92%> (+0.01%) ⬆️
windows 94.29% <76.92%> (+<0.01%) ⬆️

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

Components Coverage Δ
neqo-common 98.49% <ø> (ø)
neqo-crypto 86.90% <ø> (ø)
neqo-http3 93.91% <ø> (ø)
neqo-qpack 94.81% <ø> (ø)
neqo-transport 95.25% <76.92%> (+<0.01%) ⬆️
neqo-udp 82.97% <ø> (ø)
mtu 86.61% <ø> (ø)

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 24, 2026

Merging this PR will improve performance by 4.42%

⚠️ 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

⚡ 6 improved benchmarks
✅ 18 untouched benchmarks
⏩ 27 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation decode 4096 bytes, mask ff 17.9 µs 17.4 µs +3.24%
Simulation decode 1048576 bytes, mask ff 4.5 ms 4.4 ms +3.12%
Simulation decode 4096 bytes, mask 7f 24 µs 23.1 µs +3.69%
Simulation decode 1048576 bytes, mask 7f 6.1 ms 5.9 ms +3.72%
Simulation decode 4096 bytes, mask 3f 27.1 µs 26 µs +4.38%
Simulation decode 1048576 bytes, mask 3f 6.9 ms 6.6 ms +4.42%

Comparing mxinden:disable-scone-padding (5acfebb) with main (faa1437)2

Open in CodSpeed

Footnotes

  1. 27 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on v0.24 (5b4e850) during the generation of this report, so main (faa1437) was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results

Significant performance differences relative to 5b4e850.

transfer/1-conn/1-100mb-req (aka. Upload)/mtu-1504: 💔 Performance has regressed by +1.7809%.
       time:   [213.29 ms 213.69 ms 214.13 ms]
       thrpt:  [467.00 MiB/s 467.97 MiB/s 468.83 MiB/s]
change:
       time:   [+1.5083% +1.7809% +2.0702] (p = 0.00 < 0.05)
       thrpt:  [-2.0282% -1.7497% -1.4859]
       Performance has regressed.
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high severe
All results
transfer/1-conn/1-100mb-resp (aka. Download)/mtu-1504: Change within noise threshold.
       time:   [210.29 ms 210.69 ms 211.20 ms]
       thrpt:  [473.49 MiB/s 474.64 MiB/s 475.54 MiB/s]
change:
       time:   [+0.9479% +1.2313% +1.5149] (p = 0.00 < 0.05)
       thrpt:  [-1.4923% -1.2163% -0.9390]
       Change within noise threshold.
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high severe
transfer/1-conn/10_000-parallel-1b-resp (aka. RPS)/mtu-1504: Change within noise threshold.
       time:   [283.41 ms 285.34 ms 287.29 ms]
       thrpt:  [34.808 Kelem/s 35.046 Kelem/s 35.284 Kelem/s]
change:
       time:   [-2.0322% -1.1433% -0.2062] (p = 0.02 < 0.05)
       thrpt:  [+0.2067% +1.1565% +2.0744]
       Change within noise threshold.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
transfer/1-conn/1-1b-resp (aka. HPS)/mtu-1504: No change in performance detected.
       time:   [38.518 ms 38.635 ms 38.769 ms]
       thrpt:  [25.794   B/s 25.883   B/s 25.962   B/s]
change:
       time:   [-0.8107% -0.2416% +0.2935] (p = 0.40 > 0.05)
       thrpt:  [-0.2927% +0.2422% +0.8173]
       No change in performance detected.
Found 6 outliers among 100 measurements (6.00%)
4 (4.00%) high mild
2 (2.00%) high severe
transfer/1-conn/1-100mb-req (aka. Upload)/mtu-1504: 💔 Performance has regressed by +1.7809%.
       time:   [213.29 ms 213.69 ms 214.13 ms]
       thrpt:  [467.00 MiB/s 467.97 MiB/s 468.83 MiB/s]
change:
       time:   [+1.5083% +1.7809% +2.0702] (p = 0.00 < 0.05)
       thrpt:  [-2.0282% -1.7497% -1.4859]
       Performance has regressed.
Found 2 outliers among 100 measurements (2.00%)
2 (2.00%) high severe
streams/walltime/1-streams/each-1000-bytes: No change in performance detected.
       time:   [585.20 µs 587.24 µs 589.63 µs]
       change: [-0.7761% -0.3475% +0.1442] (p = 0.14 > 0.05)
       No change in performance detected.
Found 9 outliers among 100 measurements (9.00%)
1 (1.00%) high mild
8 (8.00%) high severe
streams/walltime/1000-streams/each-1-bytes: Change within noise threshold.
       time:   [12.445 ms 12.464 ms 12.483 ms]
       change: [+0.5105% +0.8797% +1.1947] (p = 0.00 < 0.05)
       Change within noise threshold.
streams/walltime/1000-streams/each-1000-bytes: Change within noise threshold.
       time:   [45.144 ms 45.185 ms 45.228 ms]
       change: [+0.9311% +1.0683% +1.2060] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
transfer/walltime/pacing-false/varying-seeds: Change within noise threshold.
       time:   [78.361 ms 78.417 ms 78.475 ms]
       change: [-1.4960% -1.3374% -1.2045] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 3 outliers among 100 measurements (3.00%)
3 (3.00%) high mild
transfer/walltime/pacing-true/varying-seeds: Change within noise threshold.
       time:   [79.772 ms 79.854 ms 79.957 ms]
       change: [-2.0362% -1.8822% -1.7222] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 11 outliers among 100 measurements (11.00%)
1 (1.00%) low mild
7 (7.00%) high mild
3 (3.00%) high severe
transfer/walltime/pacing-false/same-seed: Change within noise threshold.
       time:   [80.215 ms 80.297 ms 80.397 ms]
       change: [+0.3437% +0.5618% +0.7430] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 2 outliers among 100 measurements (2.00%)
1 (1.00%) high mild
1 (1.00%) high severe
transfer/walltime/pacing-true/same-seed: Change within noise threshold.
       time:   [79.933 ms 80.003 ms 80.084 ms]
       change: [-2.0771% -1.8793% -1.7084] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 3 outliers among 100 measurements (3.00%)
2 (2.00%) high mild
1 (1.00%) high severe

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

@mxinden mxinden marked this pull request as ready for review April 24, 2026 09:37
Copilot AI review requested due to automatic review settings April 24, 2026 09:38
@mxinden mxinden changed the title fix: gate SCONE Initial padding on the scone connection parameter fix: gate SCONE Initial padding on connection parameter Apr 24, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Client/server transfer results

Performance differences relative to 5b4e850.

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

Client vs. server (params) Mean ± σ Min Max MiB/s ± σ Δ baseline Δ baseline
msquic-neqo-cubic 178.8 ± 28.0 143.9 355.8 179.0 ± 1.1 💚 -15.8 -8.1%
neqo-neqo-cubic-nopacing 97.0 ± 4.7 86.8 107.7 329.9 ± 6.8 💔 1.7 1.8%
neqo-neqo-newreno-nopacing 96.2 ± 4.3 87.5 106.8 332.6 ± 7.4 💚 -1.4 -1.4%
neqo-quiche-cubic 190.3 ± 3.6 185.7 201.4 168.2 ± 8.9 💚 -1.2 -0.6%
neqo-s2n-cubic 218.5 ± 3.8 213.7 229.3 146.4 ± 8.4 💚 -3.8 -1.7%

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 (params) Mean ± σ Min Max MiB/s ± σ Δ baseline Δ baseline
google-google-nopacing 453.6 ± 4.0 447.8 465.2 70.5 ± 8.0
google-neqo-cubic 280.9 ± 4.3 273.8 289.3 113.9 ± 7.4 0.1 0.0%
msquic-msquic-nopacing 172.0 ± 68.2 135.8 654.2 186.1 ± 0.5
msquic-neqo-cubic 178.8 ± 28.0 143.9 355.8 179.0 ± 1.1 💚 -15.8 -8.1%
neqo-google-cubic 750.7 ± 4.7 742.5 770.9 42.6 ± 6.8 0.5 0.1%
neqo-msquic-cubic 158.6 ± 4.9 149.9 172.0 201.8 ± 6.5 0.3 0.2%
neqo-neqo-cubic 97.8 ± 4.3 91.0 107.5 327.1 ± 7.4 -0.4 -0.4%
neqo-neqo-cubic-nopacing 97.0 ± 4.7 86.8 107.7 329.9 ± 6.8 💔 1.7 1.8%
neqo-neqo-newreno 97.1 ± 5.0 86.8 125.1 329.5 ± 6.4 -0.2 -0.3%
neqo-neqo-newreno-nopacing 96.2 ± 4.3 87.5 106.8 332.6 ± 7.4 💚 -1.4 -1.4%
neqo-quiche-cubic 190.3 ± 3.6 185.7 201.4 168.2 ± 8.9 💚 -1.2 -0.6%
neqo-s2n-cubic 218.5 ± 3.8 213.7 229.3 146.4 ± 8.4 💚 -3.8 -1.7%
quiche-neqo-cubic 178.8 ± 5.1 171.1 203.9 179.0 ± 6.3 1.1 0.6%
quiche-quiche-nopacing 138.6 ± 4.0 131.8 147.6 230.9 ± 8.0
s2n-neqo-cubic 218.7 ± 5.1 210.3 239.8 146.3 ± 6.3 -1.0 -0.5%
s2n-s2n-nopacing 300.5 ± 29.7 281.8 398.0 106.5 ± 1.1

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes unintended on-wire signaling of SCONE by ensuring the SCONE Initial-padding indication is only emitted when the scone connection parameter is enabled, restoring zero-padding behavior when it is disabled. This aligns runtime packet formatting with the existing gating of the SCONE transport parameter and aims to prevent default clients from advertising SCONE implicitly.

Changes:

  • Gate Initial-packet SCONE space reservation on conn_params.scone_enabled().
  • Gate emission of the SCONE Initial-padding indication on conn_params.scone_enabled(); otherwise pad Initials with zero bytes.
  • Bump workspace version from 0.24.1 to 0.24.2 (and update Cargo.lock accordingly).

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated no comments.

File Description
neqo-transport/src/connection/mod.rs Gates SCONE reservation/indication on scone_enabled() and uses zero padding when disabled.
neqo-transport/src/connection/tests/handshake.rs Updates corruption test to treat padding as zero-filled when SCONE is disabled.
Cargo.toml Workspace version bump to 0.24.2.
Cargo.lock Updates workspace member package versions to 0.24.2.

@github-actions
Copy link
Copy Markdown
Contributor

Failed Interop Tests

QUIC Interop Runner, client vs. server, differences relative to v0.24 at 5b4e850.

neqo-pr as clientneqo-pr as server
neqo-pr vs. go-x-net: BP BA
neqo-pr vs. haproxy: BP BA
neqo-pr vs. kwik: BP BA
neqo-pr vs. lsquic: L1 C1
neqo-pr vs. msquic: A L1 C1
neqo-pr vs. mvfst: A BP BA
neqo-pr vs. neqo: A
neqo-pr vs. nginx: 🚀L1 BP BA
neqo-pr vs. ngtcp2: CM
neqo-pr vs. picoquic: A
neqo-pr vs. quic-go: A
neqo-pr vs. quiche: BP BA
neqo-pr vs. s2n-quic: BP BA CM
neqo-pr vs. tquic: S BP BA
neqo-pr vs. xquic: A L1 🚀C1
aioquic vs. neqo-pr: Z CM
go-x-net vs. neqo-pr: CM
kwik vs. neqo-pr: Z BP BA CM
lsquic vs. neqo-pr: Z
msquic vs. neqo-pr: Z CM
mvfst vs. neqo-pr: Z A L1 C1 CM
neqo vs. neqo-pr: Z A
openssl vs. neqo-pr: LR M A ⚠️BA CM
picoquic vs. neqo-pr: Z
quic-go vs. neqo-pr: CM
quiche vs. neqo-pr: Z CM
quinn vs. neqo-pr: Z 🚀L1 C1 V2 CM
s2n-quic vs. neqo-pr: CM
tquic vs. neqo-pr: Z 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

@larseggert
Copy link
Copy Markdown
Collaborator

CC @martinthomson since this was his change originally.

Copy link
Copy Markdown
Collaborator

@larseggert larseggert left a comment

Choose a reason for hiding this comment

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

clippy needs fixing

@mxinden
Copy link
Copy Markdown
Member Author

mxinden commented Apr 24, 2026

clippy needs fixing

@larseggert do you feel strongly about it? As far as I can tell, clippies complaints are unrelated. I would like to keep the change minimal:

  error: this `match` expression can be replaced with `?`
     --> neqo-transport/src/addr_valid.rs:188:9
      |
  188 | /         match dec.decode_uint::<u32>() {
  189 | |             Some(d) => {
  190 | |                 let end = self.start_time + Duration::from_millis(u64::from(d));
  191 | |                 if end < now {
  ...   |
  196 | |             None => return None,
  197 | |         }
      | |_________^
      |
      = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#question_mark
      = note: `-D clippy::question-mark` implied by `-D warnings`
      = help: to override `-D warnings` add `#[allow(clippy::question_mark)]`
  help: try instead
      |
  188 ~         {
  189 +             let d = dec.decode_uint::<u32>()?;
  190 +             let end = self.start_time + Duration::from_millis(u64::from(d));
  191 +             if end < now {
  192 +                 qtrace!("Expired token: {end:?} vs. {now:?}");
  193 +                 return None;
  194 +             }
  195 +         }
      |

@larseggert
Copy link
Copy Markdown
Collaborator

If unrelated, I'm ok. Do a different PR for them (or I can after lunch)

@mxinden
Copy link
Copy Markdown
Member Author

mxinden commented Apr 24, 2026

@larseggert note that this is targeting the v0.24 branch, branched off of v0.24.1 tag. In other words, this is not our most recent main with all Clippy lint fixed.

@larseggert
Copy link
Copy Markdown
Collaborator

Ah. Sorry. Jet lagged with 2h sleep and a cancelled plane in LHR. Am not fully coherent.

@mxinden mxinden merged commit 5acfebb into mozilla:v0.24 Apr 24, 2026
249 of 260 checks passed
lando-worker Bot pushed a commit to mozilla-firefox/firefox that referenced this pull request Apr 24, 2026
Update to Neqo v0.24.2 more specifically include the following fix.

- fix: gate SCONE Initial padding on connection parameter

See mozilla/neqo#3573 and
https://github.com/mozilla/neqo/releases/tag/v0.24.2 for details.

Differential Revision: https://phabricator.services.mozilla.com/D296306
Copy link
Copy Markdown
Member

@martinthomson martinthomson left a comment

Choose a reason for hiding this comment

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

If there were protocol police, I would be ringing them right now. We should be free to pad packets with whatever noise we like.

Comment on lines -809 to -810
.skip(1) // Skip the last byte, which might be a SCONE indicator.
.find(|&(_, &v)| v != Connection::SCONE_INDICATION[0]) // The SCONE padding value.
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 change is no good.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Note that this is simply a revert of #2814. Fine for now in tests?

if self.conn_params.scone_enabled() {
// This ensures that the last bytes are a SCONE indication, if there is enough space.
// This is not tracked, other than for congestion control (above)
if pad_amount >= Self::SCONE_INDICATION.len() {
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.

FWIW, this is not at all how I would have "fixed" this. I would have changed this to:

Suggested change
if pad_amount >= Self::SCONE_INDICATION.len() {
if pad_amount >= Self::SCONE_INDICATION.len() && self.conn_params.scone_enabled() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

At this point I don't know whether BitDefender trips over Self::SCONE_INDICATION[0] or Self::SCONE_INDICATION[1], thus I opted for disabling both on self.conn_params.scone_enabled(), using 0 padding instead as we did before.

@martinthomson is there much value in skipping Self::SCONE_INDICATION[0] but including Self::SCONE_INDICATION[1]?

Copy link
Copy Markdown
Member Author

@mxinden mxinden left a comment

Choose a reason for hiding this comment

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

If there were protocol police, I would be ringing them right now. We should be free to pad packets with whatever noise we like.

We are in touch with BitDefender. I assume they will fix their bug soon. Once fixed, we can re-enable SCONE. We expect a large amount of users to be affected by this BitDefender bug. I doubt the majority of users running into this bug associate it BitDefender instead of Firefox. Given that SCONE doesn't provide user value yet, I assumed temporarily disabling its padding is worth those users being able to access facebook.com.

Do you disagree with this strategy @martinthomson? What should we do instead?

if self.conn_params.scone_enabled() {
// This ensures that the last bytes are a SCONE indication, if there is enough space.
// This is not tracked, other than for congestion control (above)
if pad_amount >= Self::SCONE_INDICATION.len() {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

At this point I don't know whether BitDefender trips over Self::SCONE_INDICATION[0] or Self::SCONE_INDICATION[1], thus I opted for disabling both on self.conn_params.scone_enabled(), using 0 padding instead as we did before.

@martinthomson is there much value in skipping Self::SCONE_INDICATION[0] but including Self::SCONE_INDICATION[1]?

Comment on lines -809 to -810
.skip(1) // Skip the last byte, which might be a SCONE indicator.
.find(|&(_, &v)| v != Connection::SCONE_INDICATION[0]) // The SCONE padding value.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Note that this is simply a revert of #2814. Fine for now in tests?

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.

5 participants