Open Bug 1955877 Opened 9 months ago Updated 8 months ago

WebTransport connection rejected using self-signed certificates

Categories

(Core :: Networking, defect, P3)

Firefox 138
defect

Tracking

()

REOPENED

People

(Reporter: guest271314, Unassigned)

References

(Blocks 1 open bug)

Details

(Whiteboard: [necko-triaged])

Attachments

(13 files)

User Agent: Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0

Steps to reproduce:

In about:config set

security.pki.certificate_transparency.disable_for_hosts	https://localhost:4433/path

and

security.pki.certificate_transparency.disable_for_spki_hashes <hash>

In Chromium Version 136.0.7087.0 (Developer Build) (64-bit) I use the follow command line flag when launching chrome

--ignore-certificate-errors-spki-list=<hash>

Start local WebTransport server using self-signed certificates

deno -A --unstable-net wt-server.js
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// https://raw.githubusercontent.com/denoland/deno/dce204af32e4b56681be4f9a034256d979a3ce4b/tests/specs/run/webtransport/main.ts
//import { decodeBase64 } from "jsr:@std/encoding/base64";
//import { assertEqual } from "jsr:@std/assert";
const cert = Deno.readTextFileSync("./certificate.pem");
const key = Deno.readTextFileSync("./certificate.key");
const certBase64 = cert.split("\n").slice(1, -2).join("");
const certBytes =
  await (await fetch(`data:application/octet-stream;base64,${certBase64}`))
    .bytes();
const certHash = await crypto.subtle.digest("SHA-256", certBytes);
const server = new Deno.QuicEndpoint({
  hostname: "localhost",
  port: 4433,
});
const listener = server.listen({
  cert,
  key,
  alpnProtocols: ["h3"],
});
let requests = 0;
async function handle(wt) {
  try {
    console.log(wt);
    const encoder = new TextEncoder();
    await wt.ready;
    for await (
      const { readable, writable } of wt.incomingBidirectionalStreams
    ) {
      for await (const value of readable.pipeThrough(new TextDecoderStream())) {
        console.log(value);
        await new Response(value.toUpperCase()).body
          .pipeTo(writable, { preventClose: true });
      }
      await writable.close().then(() => console.log("writable close"));

      console.log(writable);
      await new Promise((r) => setTimeout(r));
      wt.close();
      break;
    }
  } catch (e) {
    console.log(e);
    console.trace();
  } finally {
    return wt.closed.then(() => ({
      code: 0,
      reason: `Done streaming request ${requests++}`,
    }));
  }
}

for await (const conn of listener) {
  const wt = await Deno.upgradeWebTransport(conn);
  try {
    handle(wt)
      .then((promise) => console.log(promise))
      .catch(console.error);
  } catch (e) {
    console.log(e);
  } finally {
    continue;
  }
}
server.close();

Make request from console

(async () => {
  try {
    const client = new WebTransport(
      `https://localhost:4433/path`,
    );

    await client.ready;
    const encoder = new TextEncoder();
    let controller;
    const abortable = new AbortController();
    const {
      signal,
    } = abortable;
    const {
      readable,
      writable
    } = await client.createBidirectionalStream();
    // const writer = writable.getWriter();
    const stream = readable.pipeThrough(new TextDecoderStream())
      .pipeTo(
        new WritableStream({
          start(c) {
            controller = c;
          },
          write(value, c) {
            //console.log(c.desiredSize);
            console.log(value);
          },
          close() {
            console.log("WritableStream close");
          },
          abort(reason) {
            console.log({
              reason
            });
          },
        }, {
          highWaterMark: 1
        }), //,
        {
          signal
        },
      ).catch((e) => e.message);

    let str = "abcdefghijklmnopqrstuvwxyz";

    /*
for (let i = 0; i < 26; i++) {
  let preventClose = i < str.length - 1;
  await new Response(encoder.encode(str.at(i))).body
  .pipeTo(writable, { preventClose })
}
  */
    await new Response(encoder.encode("x".repeat(1024 ** 2))).body
      .pipeTo(writable);

    if (!navigator.userAgent.includes("Deno")) {
      await Promise.allSettled([client.closed, stream])
        .then(console.log);
    } else {
      if (navigator.userAgent.includes("Deno")) {
        // Deno doesn't close on writer.close()
        await new Promise((r) => setTimeout(r, 7));
        //console.log(abortable);
        client.close();
        await Promise.allSettled([client.closed, stream])
          .then(console.log);
      }
    }
  } catch (e) {
    console.log(e);
  }

})().catch(console.log);

Actual results:

Uncaught (in promise) 
WebTransportError { source: "session", streamErrorCode: null, name: "WebTransportError", message: "WebTransport connection rejected", code: 0, result: 0, filename: "", lineNumber: 0, columnNumber: 0, data: null }
​
code: 0
​
columnNumber: 0
​
data: null
​
filename: ""
​
lineNumber: 0
​
message: "WebTransport connection rejected"
​
name: "WebTransportError"
​
result: 0
​
source: "session"
​
stack: ""
​
streamErrorCode: null
​
<prototype>: WebTransportErrorPrototype { source: Getter, streamErrorCode: Getter, stack: "", … }

Expected results:

Chromium 136 at console

... XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
VM37:30 
{reason: WebTransportError: Connection lost.}
(2) [{…}, {…}]
0
: 
{status: 'rejected', reason: WebTransportError: Connection lost.}
1
: 
{status: 'fulfilled', value: 'Connection lost.'}
length
: 
2
[[Prototype]]
: 
Array(0)

The Bugbug bot thinks this bug should belong to the 'Core::Security: PSM' component, and is moving the bug to that component. Please correct in case you think the bot is wrong.

Component: Untriaged → Security: PSM
Product: Firefox → Core

Firefox doesn't have an equivalent to Chrome's --ignore-certificate-errors-spki-list option. You'll need to use something like mkcert (https://github.com/FiloSottile/mkcert) to create a trusted certificate authority to issue your server's certificate.

Status: UNCONFIRMED → RESOLVED
Closed: 9 months ago
Resolution: --- → INVALID

So there's basically no way to use WebTransport on Firefox for exclusive local usage without getting some remote "trusted certificate authority" involved?

Using this https://github.com/achingbrain/webtransport-echo-server/blob/main/index.js#L2-L37 approach with Deno's WebTransport server

import certificates from "./cert.json" with {type:"json"};

const serverCertificateHashes = [{
      algorithm: 'sha-256',
      value: Uint8Array.from(atob(btoa(String.fromCodePoint(...certificates[0].hash.digest))), (m) => m.codePointAt(0))
}];

const server = new Deno.QuicEndpoint({
  hostname: "localhost",
  port: 4433,
});
const listener = server.listen({
  cert: certificates[0].pem,
  key: certificates[0].privateKey,
  alpnProtocols: ["h3"],
});
const serverCertificateHashes = [{
      algorithm: 'sha-256',
      value: Uint8Array.from(atob(btoa(String.fromCodePoint(...certificates[0].hash.digest))), (m) => m.codePointAt(0))
}];

const client = new WebTransport(
  `https://localhost:4433/path`,
    {
      serverCertificateHashes,
    },
);

works just fine in Chromium Version 136.0.7088.0 (Developer Build) (64-bit). Not In Firefox Nightly 138.0a1 (2025-03-24) (64-bit).

WebTransport on Firefox appears to basically be unusable. At least without burrowing under or leaping over N hurdles.

mkcert creates a local certificate authority on your device - it's not remote.

I did that. Using the code @fail-components/webtransport uses. Firefox still fails to do anything but hand communicating with a Deno WebTransport server. Screenshot.

See https://github.com/fails-components/webtransport/issues/265#issuecomment-2750190849.

After I compiled the certificates.js to a single bundle I converted the resulting JavaScript object to JSON, so I only have to do that once. E.g.,

const certificates = [
  {
    "privateKey": "-----BEGIN PRIVATE KEY ...
import certificates from "./cert.json" with { type: "json" };

const serverCertificateHashes = [{
  algorithm: "sha-256",
  value: Uint8Array.from(
    atob(btoa(String.fromCodePoint(...certificates[0].hash.digest))),
    (m) => m.codePointAt(0),
  ),
}];

const server = new Deno.QuicEndpoint({
  hostname: "localhost",
  port: 4433,
});
const listener = server.listen({
  cert: certificates[0].pem,
  key: certificates[0].privateKey,
  alpnProtocols: ["h3"],
});

let requests = 0;
async function handle(wt) {
  try {
    console.log(wt);
    const encoder = new TextEncoder();
    await wt.ready;
    for await (
      const { readable, writable } of wt.incomingBidirectionalStreams
    ) {
      for await (const value of readable.pipeThrough(new TextDecoderStream())) {
        console.log(value);
        await new Response(value.toUpperCase()).body
          .pipeTo(writable, { preventClose: true });
      }
      await writable.close().then(() => console.log("writable close"));

      console.log(writable);
      await new Promise((r) => setTimeout(r));
      wt.close();
      break;
    }
  } catch (e) {
    console.log(e);
    console.trace();
  } finally {
    return wt.closed.then(() => ({
      code: 0,
      reason: `Done streaming request ${requests++}`,
    }));
  }
}

for await (const conn of listener) {
  const wt = await Deno.upgradeWebTransport(conn);
  try {
    handle(wt)
      .then((promise) => console.log(promise))
      .catch(console.error);
  } catch (e) {
    console.log(e);
  } finally {
    continue;
  }
}
server.close();

So, the issue is Firefox.

Well, you had to completely disable certificate verification in Chrome - I would call that a bit of a hurdle.

Well, yes. I'm using WebTransport locally, between the browser and local applications, so the certificate thing is N/A for my use cases.

When you run mkcert -install (you ran that, right?)

No.

I located this repository which dynamically generates the certificates in JavaScript https://github.com/achingbrain/webtransport-echo-server.

@fails-components/webtransport with node index.js works for the base echo case, without logging the fulfillment from Promise.allSetlled() at the end, on Firefox Nightly 138.

Deno's WebTransport server using the same certificate generation means doesn't do anything on Firefox. The same code works on Chromium 136.

Flags: needinfo?(guest271314)

Pardon, Promise.allSettled() is fulfilled on Nightly 138 (including closeCode and reason), when using @fails-components/webtransport and dynamically generating certificate in JavaScript.

I'll have to investigate why the Deno WebTransport server doesn't work on Firefox using the same certificate generation approach.

I'm sorry - I completely misunderstood what you were having problems with. If you use about:logging to log to a file with the log modules set to nsHttp:5, do you see any line with "AuthCertificateWithServerCertificateHashes failed"?

Status: RESOLVED → REOPENED
Component: Security: PSM → Networking
Ever confirmed: true
Flags: needinfo?(guest271314)
Resolution: INVALID → ---

No.

"Auth" appears 3 times.

2025-03-28 22:54:43.074618 UTC - [Parent 44535: Main Thread]: D/nsHttp nsHttpChannelAuthProvider::AddAuthorizationHeaders? [this=7f71d7d915e0 channel=7f71ce821ca0]
2025-03-28 22:54:43.074636 UTC - [Parent 44535: Main Thread]: D/nsHttp Skipping Authorization header for anonymous load

"webtransport" appears a few more times.

Really, this is unusable for local cases. Especially since Firefox blocks requests to localhost from console on most Web sites.

2025-03-28 22:54:43.075128 UTC - [Parent 44535: Main Thread]: V/nsHttp HttpBaseChannel::SetRequestHeader [this=7f71ce821600 header="Sec-Fetch-Dest" value="webtransport" merge=0]

2025-03-28 22:54:43.076702 UTC - [Parent 44535: Socket Thread]: D/nsSocketTransport PollableEvent::Clear PR_Read 1
2025-03-28 22:54:43.076729 UTC - [Parent 44535: Main Thread]: D/nsHttp triggering network rcwn=0
2025-03-28 22:54:43.076751 UTC - [Parent 44535: Main Thread]: D/nsHttp nsHttpChannel::DoConnect [this=7f71ce821600]
2025-03-28 22:54:43.076737 UTC - [Parent 44535: Socket Thread]: D/nsSocketTransport STS dispatch [7f71c6689640]
2025-03-28 22:54:43.076801 UTC - [Parent 44535: Socket Thread]: D/nsSocketTransport OnDispatchedEvent Same Thread Skip Signal
2025-03-28 22:54:43.076777 UTC - [Parent 44535: Main Thread]: D/nsHttp nsHttpChannel::DoConnectActual [this=7f71ce821600, aTransWithStickyConn=0]
2025-03-28 22:54:43.076847 UTC - [Parent 44535: Main Thread]: D/nsHttp nsHttpChannel::SetupChannelForTransaction [this=7f71ce821600, cos=0, inc=0 prio=0]
2025-03-28 22:54:43.076821 UTC - [Parent 44535: Socket Thread]: V/nsHttp nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=.SA......W[tlsflags0x00000000]localhost:4433 {NPN-TOKEN h3}{wId2}, mFetchHTTPSRR=1]
2025-03-28 22:54:43.076868 UTC - [Parent 44535: Main Thread]: V/nsHttp HttpBaseChannel::SetRequestHeader [this=7f71ce821600 header="Priority" value="u=4" merge=0]
2025-03-28 22:54:43.076894 UTC - [Parent 44535: Socket Thread]: V/nsHttp FindCoalescableConnection .SA......W[tlsflags0x00000000]localhost:4433 {NPN-TOKEN h3}{wId2}
2025-03-28 22:54:43.076953 UTC - [Parent 44535: Socket Thread]: V/nsHttp Don't coalesce a WebTransport conn
2025-03-28 22:54:43.076977 UTC - [Parent 44535: Socket Thread]: V/nsHttp GetH2orH3ActiveConn() request for ent 7f71ca129670 .SA......W[tlsflags0x00000000]localhost:4433 {NPN-TOKEN h3}{wId2} did not find an active connection

025-03-28 22:54:43.077173 UTC - [Parent 44535: Main Thread]: E/nsHttp http request [
2025-03-28 22:54:43.077192 UTC - [Parent 44535: Main Thread]: E/nsHttp GET /path HTTP/1.1
2025-03-28 22:54:43.077208 UTC - [Parent 44535: Main Thread]: E/nsHttp Host: localhost:4433
2025-03-28 22:54:43.077224 UTC - [Parent 44535: Main Thread]: E/nsHttp User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0
2025-03-28 22:54:43.077240 UTC - [Parent 44535: Main Thread]: E/nsHttp Accept: /
2025-03-28 22:54:43.077255 UTC - [Parent 44535: Main Thread]: E/nsHttp Accept-Language: en-US,en;q=0.5
2025-03-28 22:54:43.077270 UTC - [Parent 44535: Main Thread]: E/nsHttp Accept-Encoding: gzip, deflate, br, zstd
2025-03-28 22:54:43.077285 UTC - [Parent 44535: Main Thread]: E/nsHttp Sec-Webtransport-Http3-Draft02: 1
2025-03-28 22:54:43.077300 UTC - [Parent 44535: Main Thread]: E/nsHttp Origin: https://example.com
2025-03-28 22:54:43.077315 UTC - [Parent 44535: Main Thread]: E/nsHttp Connection: keep-alive
2025-03-28 22:54:43.077330 UTC - [Parent 44535: Main Thread]: E/nsHttp Sec-Fetch-Dest: webtransport
2025-03-28 22:54:43.077344 UTC - [Parent 44535: Main Thread]: E/nsHttp Sec-Fetch-Mode: no-cors
2025-03-28 22:54:43.077360 UTC - [Parent 44535: Main Thread]: E/nsHttp Sec-Fetch-Site: cross-site
2025-03-28 22:54:43.077374 UTC - [Parent 44535: Main Thread]: E/nsHttp Priority: u=4
2025-03-28 22:54:43.077388 UTC - [Parent 44535: Main Thread]: E/nsHttp Pragma: no-cache
2025-03-28 22:54:43.077402 UTC - [Parent 44535: Main Thread]: E/nsHttp Cache-Control: no-cache
2025-03-28 22:54:43.077426 UTC - [Parent 44535: Main Thread]: E/nsHttp
2025-03-28 22:54:43.077446 UTC - [Parent 44535: Main Thread]: E/nsHttp ]
2025-03-28 22:54:43.077180 UTC - [Parent 44535: Socket Thread]: D/nsHostResolver Using port prefixed host name [_4433._https.localhost]
2025-03-28 22:54:43.077485 UTC - [Parent 44535: Socket Thread]: D/nsHostResolver Subdomain [localhost] of host [_4433._https.localhost] Is Excluded From TRR via pref
2025-03-28 22:54:43.077507 UTC - [Parent 44535: Socket Thread]: D/nsHostResolver No usable record in cache for host [_4433._https.localhost] type 65.
2025-03-28 22:54:43.077526 UTC - [Parent 44535: Socket Thread]: D/nsHostResolver NameLookup host:_4433._https.localhost af:0
2025-03-28 22:54:43.077516 UTC - [Parent 44535: Main Thread]: D/nsHostResolver Resolving host [localhost]<> type 65. [this=7f71da280200]
2025-03-28 22:54:43.077559 UTC - [Parent 44535: Socket Thread]: D/nsHostResolver Subdomain [localhost] of host [_4433._https.localhost] Is Excluded From TRR via pref
2025-03-28 22:54:43.077579 UTC - [Parent 44535: Socket Thread]: D/nsHostResolver NameLookup: _4433._https.localhost effectiveTRRmode: 1 flags: 20200
2025-03-28 22:54:43.077592 UTC - [Parent 44535: Socket Thread]: D/nsHostResolver TRR service not enabled - off or disabled
2025-03-28 22:54:43.077617 UTC - [Parent 44535: Socket Thread]: V/nsHttp FindCoalescableConnection .SA......W[tlsflags0x00000000]localhost:4433 {NPN-TOKEN h3}{wId2}
2025-03-28 22:54:43.077632 UTC - [Parent 44535: Socket Thread]: V/nsHttp Don't coalesce a WebTransport conn
2025-03-28 22:54:43.077648 UTC - [Parent 44535: Socket Thread]: V/nsHttp GetH2orH3ActiveConn() request for ent 7f71ca129670 .SA......W[tlsflags0x00000000]localhost:4433 {NPN-TOKEN h3}{wId2} did not find an active connection
2025-03-28 22:54:43.077666 UTC - [Parent 44535: Socket Thread]: V/nsHttp Creating DnsAndConnectSocket [this=7f71cb7f7480 trans=7f71cb180120 ent=localhost key=.SA......W[tlsflags0x00000000]localhost:4433 {NPN-TOKEN h3}{wId2}]
2025-03-28 22:54:43.077686 UTC - [Parent 44535: Socket Thread]: V/nsHttp DnsAndConnectSocket::SetupDnsFlags [this=7f71cb7f7480]
2025-03-28 22:54:43.077702 UTC - [Parent 44535: Socket Thread]: V/nsHttp DnsAndConnectSocket::SetupDnsFlags flags=8192 flagsBackup=8224 [this=7f71cb7f7480]
2025-03-28 22:54:43.077718 UTC - [Parent 44535: Socket Thread]: V/nsHttp DnsAndConnectSocket::SetupEvent state=0 event=0 this=7f71cb7f7480
2025-03-28 22:54:43.077735 UTC - [Parent 44535: Socket Thread]: V/nsHttp DnsAndConnectSocket::TransportSetup::ResolveHost [this=7f71cb7f74e0 localhost]
2025-03-28 22:54:43.077620 UTC - [Parent 44535: Main Thread]: D/nsHostResolver Using port prefixed host name [_4433._https.localhost]
2025-03-28 22:54:43.077774 UTC - [Parent 44535: Main Thread]: D/nsHostResolver Subdomain [localhost] of host [_4433._https.localhost] Is Excluded From TRR via pref
2025-03-28 22:54:43.077762 UTC - [Parent 44535: Socket Thread]: D/nsHostResolver Resolving host [localhost]<> type 0. [this=7f71da280200]
2025-03-28 22:54:43.077794 UTC - [Parent 44535: Main Thread]: D/nsHostResolver No usable record in cache for host [_4433._https.localhost] type 65.
2025-03-28 22:54:43.077840 UTC - [Parent 44535: Main Thread]: D/nsHostResolver NameLookup host:_4433._https.localhost af:0
2025-03-28 22:54:43.077854 UTC - [Parent 44535: Main Thread]: D/nsHostResolver Subdomain [localhost] of host [_4433._https.localhost] Is Excluded From TRR via pref
2025-03-28 22:54:43.077866 UTC - [Parent 44535: Main Thread]: D/nsHostResolver NameLookup: _4433._https.localhost effectiveTRRmode: 1 flags: 20200
2025-03-28 22:54:43.077879 UTC - [Parent 44535: Main Thread]: D/nsHostResolver TRR service not enabled - off or disabled
2025-03-28 22:54:43.077898 UTC - [Parent 44535: Main Thread]: V/nsHttp nsHttpConnectionMgr::AddTransaction [trans=7f71c975de10 0]
2025-03-28 22:54:43.077917 UTC - [Parent 44535: Main Thread]: D/nsSocketTransport STS dispatch [7f71c5dc7100]
2025-03-28 22:54:43.077935 UTC - [Parent 44535: Main Thread]: D/nsSocketTransport PollableEvent::Signal
2025-03-28 22:54:43.077951 UTC - [Parent 44535: Main Thread]: D/nsSocketTransport PollableEvent::MarkFirstSignalTimestamp

2025-03-28 22:54:43.077904 UTC - [Parent 44535: Socket Thread]: D/nsHostResolver Subdomain [localhost] of host [localhost] Is Excluded From TRR via pref

2025-03-28 22:54:43.078724 UTC - [Parent 44535: Socket Thread]: V/nsHttp Don't coalesce a WebTransport conn

2025-03-28 22:54:43.079198 UTC - [Parent 44535: Socket Thread]: I/nsHttp Http3Session::Init origin=localhost, alpn=h3, selfAddr=0.0.0.0, peerAddr=127.0.0.1, qpack table size=65536, max blocked streams=20 webtransport=1 [this=7f71c783e100]

2025-03-28 22:54:43.080200 UTC - [Parent 44535: Socket Thread]: V/nsHttp Don't coalesce a WebTransport conn
2025-03-28 22:54:43.080212 UTC - [Parent 44535: Socket Thread]: V/nsHttp GetH2orH3ActiveConn() request for ent 7f71ca129670 .SA......W[tlsflags0x00000000]localhost:4433 {NPN-TOKEN h3}{wId2} did not find an active connection
2025-03-28 22:54:43.080230 UTC - [Parent 44535: Socket Thread]: V/nsHttp DnsAndConnectSocket::SetupConn null transaction will be used to finish SSL handshake on conn 7f71c668a430
2025-03-28 22:54:43.080244 UTC - [Parent 44535: Socket Thread]: V/nsHttp nsHttpConnectionMgr::ActivateTimeoutTick() this=7f71eb548cc0 mTimeoutTick=7f71d79d3440
2025-03-28 22:54:43.080262 UTC - [Parent 44535: Socket Thread]: V/nsHttp nsHttpConnectionMgr::DispatchAbstractTransaction [ci=.SA......W[tlsflags0x00000000]localhost:4433 {NPN-TOKEN h3}{wId2} trans=7f71cb180120 caps=411 conn=7f71c668a430]
2025-03-28 22:54:43.080277 UTC - [Parent 44535: Socket Thread]: E/nsHttp HttpConnectionUDP::Activate [this=7f71c668a430 trans=7f71cb180120 caps=411]
2025-03-28 22:54:43.080293 UTC - [Parent 44535: Socket Thread]: I/nsHttp Http3Session::AddStream 7f71c783e100 atrans=7f71cb180120.

2025-03-28 22:54:43.082972 UTC - [Parent 44535: Socket Thread]: V/nsHttp Don't coalesce a WebTransport conn

2025-03-28 22:54:43.083008 UTC - [Parent 44535: Socket Thread]: V/nsHttp Don't coalesce a WebTransport conn

2025-03-28 22:54:43.083206 UTC - [Parent 44535: Socket Thread]: V/nsHttp Don't coalesce a WebTransport conn

2025-03-28 22:54:43.083280 UTC - [Parent 44535: Socket Thread]: V/nsHttp Don't coalesce a WebTransport conn

2025-03-28 22:54:43.084366 UTC - [Parent 44535: Socket Thread]: V/nsHttp Don't coalesce a WebTransport conn

Flags: needinfo?(guest271314)
Severity: -- → S3
Priority: -- → P3
Whiteboard: [necko-triaged][necko-priority-new]

I am not able to reproduce this with this demo server (https://github.com/achingbrain/webtransport-echo-server).

Unfortunately, the log in comment #15 is not complete. Could you try to capture the log and send it to necko@mozilla.com?
Thanks.

Flags: needinfo?(guest271314)
Attached file server
The linked WebTransport echo server using @fails-components/webtransport does work, to an appreciable degree. To an appreciable degree meaning Firefox Nightly 138 blocks WebTransport communication to the local server on arbitrary Web pages. On Chromium the connection is established and works as expected. The Deno WebTransport implementation using the same certificate does not. Works as expected on Chromium 136 Developer Build from yesterday. I'm wondering why. I sent the net log. Here's the server and client. Here echoing 7 MB. wt-server.js ``` ```
Attached file client
The linked WebTransport echo server using @fails-components/webtransport does work, to an appreciable degree. To an appreciable degree meaning Firefox Nightly 138 blocks WebTransport communication to the local server on arbitrary Web pages. On Chromium the connection is established and works as expected. The Deno WebTransport implementation using the same certificate does not. Works as expected on Chromium 136 Developer Build from yesterday. I'm wondering why. I sent the net log. Here's the server and client. Here echoing 7 MB.

The linked WebTransport echo server using @fails-components/webtransport does work, to an appreciable degree.

To an appreciable degree meaning Firefox Nightly 138 blocks WebTransport communication to the local server on arbitrary Web pages. On Chromium the connection is established and works as expected.

The Deno WebTransport implementation using the same certificate does not. Works as expected on Chromium 136 Developer Build from yesterday. I'm wondering why.

I sent the net log.

Here's the server and client. Here echoing 7 MB.

Flags: needinfo?(guest271314)

ni myself to take a look.

Flags: needinfo?(kershaw)

Pardon. In the browser substitute this

    let data = encoder.encode("x".repeat((1024 ** 2) * 7));

for this

    let data = encoder.encode("x".repeat((1024 ** 2) * Deno.args.at(-1) || 1));

What I see in the console on Chromium 136 and Firefox Nightly 138

Could you show me how to reproduce this locally?
Sorry that I am not sure how to run the server code in comment #17. It appears that cert.json is missing and I am not familiar with Deno at all.

Flags: needinfo?(kershaw) → needinfo?(guest271314)

You used the linked echo server here

I am not able to reproduce this with this demo server (https://github.com/achingbrain/webtransport-echo-server).

Instead of dynamically creating the certificate every run, I converted the output certificate to JSON

[
  {
    "privateKey": "-----BEGIN PRIVATE KEY...",
    "pem": "-----BEGIN CERTIFICATE-----\r\...",
   "hash": {
      "code": 18,
      "size": 32,
      "digest": [
        242,
        98,
       ...
   ],
      "bytes": [...],
},
    "secret": "super-secret-shhhhhh"
  }
]

Thisis how I get Deno

wget --show-progress --progress=bar --output-document deno.zip https://dl.deno.land/canary/$(wget -qO- https://dl.deno.land/canary-latest.txt)/deno-x86_64-unknown-linux-gnu.zip && unzip deno.zip && rm deno.zip

This is how Irun the server

deno -A --unstable-net wt-server.js

Running the client using Deno

deno -A --unstable-net wt-client.js
Flags: needinfo?(guest271314)

Here's the certificate bundling code from the linked echo server repository bundled to a single script with bun build.

Here's how to use nodeto generate the certificate, once, to test the Deno WebTransport server. I'm using node nightly v24.0.0-nightly202504023db54912fa from today. If youare using node v23.11.0+ ECMAScript Modules are supported by default after syntax analyzation, see https://nodejs.org/api/cli.html#--input-typetype at

  1. Run the input as CommonJS.
  2. If step 1 fails, run the input as an ES module.
  3. If step 2 fails with a SyntaxError, strip the types.
  4. If step 3 fails with an error code ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX or ERR_INVALID_TYPESCRIPT_SYNTAX, throw the error from step 2, including the TypeScript error in the message, else run as CommonJS.
  5. If step 4 fails, run the input as an ES module.

node generate-webtransport-certificate.js

import { writeFileSync } from "node:fs";
// https://github.com/achingbrain/webtransport-echo-server
import { generateWebTransportCertificates } from "./wt-certificate-bundle.js";
const certificates = await generateWebTransportCertificates([
  { shortName: "C", value: "US" },
  { shortName: "ST", value: "Los Angeles" },
  { shortName: "L", value: "Los Angeles" },
  { shortName: "O", value: "webtransport Test Server" },
  { shortName: "CN", value: "127.0.0.1" },
], [{
  // can be max 14 days according to the spec
  days: 13,
}]);
// Generate certificate, once, serialize to JSON, write to files ystem
certificates[0].hash.digest = [...new Uint8Array(certificates[0].hash.digest)];
certificates[0].hash.bytes = [...certificates[0].hash.bytes];

writeFileSync("cert.json", JSON.stringify(certificates, null, 2));

The use as demonstrated in the attached wt-server.js and wt-client.js

wt-server.js

import certificates from "./cert.json" with { type: "json" };

const serverCertificateHashes = [{
  algorithm: "sha-256",
  value: Uint8Array.from(
    atob(btoa(String.fromCodePoint(...certificates[0].hash.digest))),
    (m) => m.codePointAt(0),
  ),
}];

wt-client.js. I just hardcode the JSON from cert.json, where the values passed to Uint8Array.of() are the values of the "digest" array in cert.json. Use an anonymous async function, because Firefox will say top-level await is used if not, execute wt-client.js in console on a site where Firefox will not throw some kind of CSP/CORP/COOP/COEP error, e.g., maybe example.com.

(async () => {
  try {
    const serverCertificateHashes = [
      {
        "algorithm": "sha-256",
        "value": Uint8Array.of(
          0,
          1,
          2,
          3,
          ...
        ),
      },
    ];
    const client = new WebTransport(
      `https://localhost:4433/path`,
      {
        serverCertificateHashes,
      },
    );
    // ...
})();

I've managed to reproduce this issue locally, and I believe it might be related to the speculative connections Firefox is making.
Could you try setting the pref network.http.speculative-parallel-limit to 0 and see if Firefox works as expected?

If it does, this might indicate a problem on the server side.

Flags: needinfo?(guest271314)
Attached image Firefox logs nothing

Nothing changed. Firefox request doesn't succeed, nothing is logged.

Flags: needinfo?(guest271314)

Could you try capturing an HTTP log again with network.http.speculative-parallel-limit set to 0?
I'd suggest to set MOZ-LOG to timestamp,nsHttp:5,nsWebTransport:5,WebTransport:5,neqo_http3::*:5
Thanks!

Flags: needinfo?(guest271314)

Got it working. Looks like the issue is setting the hostname to "localhost" instead of 127.0.0.1 in the server.

Flags: needinfo?(guest271314)

Can you reproduce successfully echoing data using Deno's WebTransport server on Firefox when the hostname is set to 127.0.0.1?

Deno version 2.3.0-rc.0+5bee292 WebTrasnport server now only works as expected when Deno is the client. For an unknown reason Chromium and Firefox are not getting all the bytes from the server.

@fails-components/webtransport server works as expected.

Whiteboard: [necko-triaged][necko-priority-new] → [necko-triaged]
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: