quico 0.4.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/server.js CHANGED
@@ -14,12 +14,20 @@
14
14
 
15
15
  import { EventEmitter } from 'node:events';
16
16
  import fs from 'node:fs';
17
- import http2 from 'node:http2';
18
- import nodeHttps from 'node:https';
19
- import nodeTls from 'node:tls';
17
+ import lemonTls from 'lemon-tls';
18
+ import crypto from 'node:crypto';
20
19
 
21
20
  import { createServer as createH3Server } from './h3_server.js';
22
- import { IncomingMessage, ServerResponse, _makeSocketStub } from './streams.js';
21
+ import { createServer as createH2Server } from './h2_server.js';
22
+ import { createServer as createH1Server } from './h1_server.js';
23
+ import { createTcpListener } from './tcp_listener.js';
24
+ import { IncomingMessage, ServerResponse, _makeSocketStub, adoptApp } from './streams.js';
25
+ import { creditAsRead } from './utils.js';
26
+ import { runEarlyDataFilter } from './http_common.js';
27
+ import { serverTlsOptions, pickTlsOptions } from './tls_options.js';
28
+ import { STATUS_CODES } from 'node:http';
29
+ import { ServerStats } from './server_stats.js';
30
+ import { createConnectUdp, isConnectUdp, datagramCapsule, STREAM_QUEUE_MAX } from './connect_udp.js';
23
31
 
24
32
 
25
33
  // ============================================================
@@ -29,6 +37,10 @@ import { IncomingMessage, ServerResponse, _makeSocketStub } from './streams.js';
29
37
  // Options:
30
38
  // http2: true (default) — TCP with node:http2
31
39
  // http1: true (default) — allowHTTP1 on TCP (or standalone if http2:false)
40
+ // ALPNProtocols — the node:tls option: ALPN names to accept, in
41
+ // preference order. Applies to TCP and QUIC,
42
+ // each taking the names that belong to it — see
43
+ // tcpAlpnList() and quicAlpnList().
32
44
  //
33
45
  // Combinations:
34
46
  // { } → H3 + H2 + H1 (default)
@@ -37,14 +49,114 @@ import { IncomingMessage, ServerResponse, _makeSocketStub } from './streams.js';
37
49
  // { http2: false, http1: false } → H3 only
38
50
  // ============================================================
39
51
 
52
+ // ============================================================
53
+ // ALPN on TCP
54
+ //
55
+ // The http2 / http1 flags decide which HTTP versions run. ALPNProtocols —
56
+ // the node:tls option, same name and same shape — adds further names and
57
+ // sets the order in which they are preferred.
58
+ //
59
+ // An HTTP protocol that is being served but missing from the list is put in
60
+ // front of it rather than dropped. The list is the node:tls one, and code
61
+ // written against node:tls would name only what it adds; silently turning off
62
+ // HTTP/2 because a caller wrote ['acme-tls/1'] would be a failure nobody sees
63
+ // — browsers just fall back, a little slower. Turning a version off is what
64
+ // the flags are for.
65
+ //
66
+ // 'h3' is skipped here: it exists only over QUIC. A name the flags have
67
+ // disabled is an error rather than an omission, because it can only mean the
68
+ // two options disagree about what the server should do.
69
+ // ============================================================
70
+
71
+ function tcpAlpnList(requested, h2, h1) {
72
+ var served = [];
73
+ if (h2) served.push('h2');
74
+ if (h1) served.push('http/1.1');
75
+ if (requested === undefined || requested === null) return served;
76
+
77
+ if (!Array.isArray(requested)) throw new TypeError('ALPNProtocols must be an array of strings');
78
+
79
+ var listed = [];
80
+ var seen = {};
81
+ for (var i = 0; i < requested.length; i++) {
82
+ var name = requested[i];
83
+ if (typeof name !== 'string' || name.length === 0) {
84
+ throw new TypeError('ALPNProtocols entries must be non-empty strings');
85
+ }
86
+ if (name === 'h3') continue;
87
+ if (name === 'h2' && !h2) throw new Error("ALPNProtocols lists 'h2' but http2 is false");
88
+ if (name === 'http/1.1' && !h1) throw new Error("ALPNProtocols lists 'http/1.1' but http1 is false");
89
+ if (seen[name]) continue;
90
+ seen[name] = true;
91
+ listed.push(name);
92
+ }
93
+
94
+ var missing = [];
95
+ for (var j = 0; j < served.length; j++) if (!seen[served[j]]) missing.push(served[j]);
96
+ return missing.concat(listed);
97
+ }
98
+
99
+ // The QUIC side of the same list, by the same rule as TCP: 'h3' is always
100
+ // served — HTTP/3 is what quico is for — so it goes first when missing, and
101
+ // keeps the position it was given when listed. 'h2' and 'http/1.1' are
102
+ // skipped: they exist only over TCP.
103
+ function quicAlpnList(requested) {
104
+ if (requested === undefined || requested === null) return ['h3'];
105
+ var out = [];
106
+ for (var i = 0; i < requested.length; i++) {
107
+ var name = requested[i];
108
+ if (name === 'h2' || name === 'http/1.1') continue;
109
+ if (out.indexOf(name) === -1) out.push(name);
110
+ }
111
+ if (out.indexOf('h3') === -1) out.unshift('h3');
112
+ return out;
113
+ }
114
+
115
+ // A name accepted in the handshake that quico does not speak. The application
116
+ // asked only that the handshake agree to it — ACME tls-alpn-01 is the case in
117
+ // point: the CA checks the certificate the handshake presented, sends nothing
118
+ // afterwards, and closes. Ending our side too keeps the socket from lingering
119
+ // if a client does not.
120
+ function closeAfterHandshake(socket) {
121
+ try { socket.end(); } catch (e) {}
122
+ }
123
+
124
+
40
125
  function createServer(options, handler) {
41
126
  if (typeof options === 'function') { handler = options; options = {}; }
42
127
  options = options || {};
128
+ // An Express app runs on quico's req/res only once its request/response
129
+ // objects inherit from them — see adoptApp in streams.js.
130
+ adoptApp(handler);
43
131
 
44
132
  var enableH2 = options.http2 !== false;
45
133
  var enableH1 = options.http1 !== false;
46
134
  var needsTcp = enableH2 || enableH1;
47
135
 
136
+ // http3: false — run no HTTP/3 on this server.
137
+ //
138
+ // listen() used to start the QUIC listener unconditionally, so every
139
+ // server bound a UDP port alongside its TCP one. That is wrong whenever
140
+ // HTTP/3 is served somewhere else on the same port — a gateway that owns
141
+ // UDP 443 and hands QUIC packets to a dedicated server — because a
142
+ // TCP-only server then competes for the same UDP port. The loser's bind
143
+ // failed, and since nothing forwarded that error (see below), its listen()
144
+ // callback simply never fired. The failure was silent.
145
+ //
146
+ // Alt-Svc is still sent when this is off. The usual reason to disable
147
+ // HTTP/3 here is that it is served by another server on the same port, and
148
+ // Alt-Svc is how a browser on TCP finds it.
149
+ var enableH3 = options.http3 !== false;
150
+ // node:tls's TLS options, checked now so a bad one throws from
151
+ // createServer — for HTTP/3 too when it runs (TLS 1.3 only).
152
+ serverTlsOptions(options, enableH3);
153
+
154
+ // Computed here rather than in listen(), so a contradictory list throws
155
+ // from createServer — at the line that wrote it — and not later from a
156
+ // listen() call somewhere else.
157
+ var tcpAlpn = tcpAlpnList(options.ALPNProtocols, enableH2, enableH1);
158
+ var quicAlpn = quicAlpnList(options.ALPNProtocols);
159
+
48
160
  var ev = new EventEmitter();
49
161
  var port = null;
50
162
 
@@ -53,6 +165,31 @@ function createServer(options, handler) {
53
165
  var tlsCert = options.cert || null;
54
166
  var tlsCa = options.ca || null;
55
167
 
168
+ // Session tickets: one key for this server, TCP and QUIC alike — random
169
+ // unless the application passes ticketKeys it keeps across restarts. And
170
+ // the application's ticketData, carried inside each ticket (lemon-tls).
171
+ var ticketKeys = options.ticketKeys || crypto.randomBytes(48);
172
+ var ticketData = typeof options.ticketData === 'function' ? options.ticketData : null;
173
+ // Encrypted Client Hello keys (RFC 9849), TCP and QUIC alike:
174
+ // { keys: [{ config, privateKey, retry }] } — see lemon-tls generateEchKey.
175
+ // Checked now: a key that does not match its config would otherwise show
176
+ // only as every ECH client falling back.
177
+ var ech = options.ech || null;
178
+ if (ech) lemonTls.checkEchKeys(ech);
179
+
180
+ // OCSP stapling, node:tls's way: server.on('OCSPRequest', (certificate,
181
+ // issuer, callback) => callback(null, ocspResponse)) — asked when a client
182
+ // requests status, TCP and QUIC alike. Without a listener nothing is
183
+ // stapled and nothing waits.
184
+ function ocspRequest(certificate, issuer, callback) {
185
+ if (ev.listenerCount('OCSPRequest') === 0) return callback(null, null);
186
+ ev.emit('OCSPRequest', Buffer.from(certificate), issuer ? Buffer.from(issuer) : null, callback);
187
+ }
188
+ // node:tls's TLS options — client certificates, versions, ciphers, curves,
189
+ // signature schemes, the ticket lifetime, the handshake timeout — passed to
190
+ // both stacks (tls_options.js), each reading them for itself.
191
+ var tlsPassOn = pickTlsOptions(options);
192
+
56
193
  // Read from disk if paths were given (not PEM strings)
57
194
  if (typeof tlsKey === 'string' && tlsKey.indexOf('-----') === -1) {
58
195
  tlsKey = fs.readFileSync(tlsKey);
@@ -68,25 +205,188 @@ function createServer(options, handler) {
68
205
  // plain req/res objects into proper Node.js streams.
69
206
  // ============================================================
70
207
 
208
+ // node:http's shouldUpgradeCallback(request): whether an upgrade request —
209
+ // HTTP/1.1 Upgrade, or extended CONNECT over HTTP/2 and HTTP/3 — reaches
210
+ // 'upgrade' or is handled as an ordinary 'request'. Node's default: taken up
211
+ // when the application listens for 'upgrade'. Decided here, for every
212
+ // version, because the layers below always listen (to forward the event
213
+ // up): their own "nobody listens" check could never fire, and an upgrade
214
+ // with no listener was left hanging.
215
+ var userShouldUpgrade = typeof options.shouldUpgradeCallback === 'function' ? options.shouldUpgradeCallback : null;
216
+ function shouldUpgradeCallback(req) {
217
+ if (wantsConnectUdp(req)) return true;
218
+ return userShouldUpgrade ? !!userShouldUpgrade(req) : ev.listenerCount('upgrade') > 0;
219
+ }
220
+
221
+ // ---- CONNECT-UDP (RFC 9298): proxying UDP, on every version ----
222
+ //
223
+ // server.on('connectUdp', (req, tunnel) => tunnel.accept()) — see
224
+ // connect_udp.js. Off until something listens: a request for it then goes
225
+ // the way it always did (to 'upgrade' or the request handler), and none is
226
+ // proxied. options.connectUdp: { template, allowPrivate,
227
+ // maxTunnelsPerConnection, idleTimeout, lookup, proxyName }.
228
+ var udpProxy = createConnectUdp(options.connectUdp || {});
229
+ function wantsConnectUdp(req) { return ev.listenerCount('connectUdp') > 0 && isConnectUdp(req); }
230
+ function emitConnectUdp(tunnel) {
231
+ if (ev.listenerCount('connectUdp') === 0) return false;
232
+ ev.emit('connectUdp', tunnel.req, tunnel);
233
+ return true;
234
+ }
235
+
236
+ // HTTP/2's and HTTP/1.1's carrier: capsules on a byte stream — the
237
+ // extended CONNECT's Duplex (DATA frames), or the upgraded socket. Past
238
+ // STREAM_QUEUE_MAX waiting to be written, a payload is dropped, as a full
239
+ // UDP queue drops.
240
+ function byteStreamCarrier(req, stream, version, head, accept, reject) {
241
+ var closed = false;
242
+ var carrier = {
243
+ httpVersion: version,
244
+ connKey: req.socket || stream,
245
+ sink: null,
246
+ mode: function () { return 'capsule'; },
247
+ accept: function (h) { if (!closed) accept(h); },
248
+ reject: function (status, h) { if (closed) return; closed = true; reject(status, h); },
249
+ sendPayload: function (u8) {
250
+ if (closed || stream.destroyed || stream.writableLength > STREAM_QUEUE_MAX) return false;
251
+ stream.write(datagramCapsule(u8));
252
+ return true;
253
+ },
254
+ end: function () { if (closed) return; closed = true; try { stream.end(); } catch (e) {} },
255
+ abort: function () { if (closed) return; closed = true; try { stream.destroy(); } catch (e) {} }
256
+ };
257
+ stream.on('error', function () {});
258
+ udpProxy.handle(req, carrier, emitConnectUdp);
259
+ if (!carrier.sink) return;
260
+ stream.on('data', function (d) { carrier.sink.data(d); });
261
+ stream.on('end', function () { carrier.sink.end(); });
262
+ stream.on('close', function () { carrier.sink.abort(); });
263
+ if (head && head.length) carrier.sink.data(head);
264
+ }
265
+ function fieldLines(h) {
266
+ var out = '';
267
+ for (var n in (h || {})) out += n + ': ' + String(h[n]).replace(/[\r\n]/g, '') + '\r\n';
268
+ return out;
269
+ }
270
+ function connectUdpH2(req, stream) {
271
+ byteStreamCarrier(req, stream, '2.0', null,
272
+ function (h) { stream._h2Accept(h); },
273
+ function (status, h) { stream._h2Reject(status, null, h); });
274
+ }
275
+ // HTTP/1.1: a GET with Upgrade: connect-udp, answered 101 (§3.2).
276
+ function connectUdpH1(req, socket, head) {
277
+ byteStreamCarrier(req, socket, '1.1', head,
278
+ function (h) {
279
+ socket.write('HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: connect-udp\r\n' + fieldLines(h) + '\r\n');
280
+ },
281
+ function (status, h) {
282
+ try {
283
+ socket.end('HTTP/1.1 ' + status + ' ' + (STATUS_CODES[status] || 'Error') + '\r\nConnection: close\r\nContent-Length: 0\r\n' + fieldLines(h) + '\r\n');
284
+ } catch (e) {}
285
+ });
286
+ }
287
+
288
+ // The server's counters (server_stats.js): each layer counts into them
289
+ // where things happen; getStats() copies them out.
290
+ var stats = new ServerStats();
291
+
71
292
  var h3Options = {
293
+ stats: stats,
294
+ shouldUpgradeCallback: shouldUpgradeCallback,
295
+ // CONNECT-UDP over HTTP/3: h3_server builds the carrier (QUIC datagrams,
296
+ // or capsules in DATA frames); the request is wrapped as the others are.
297
+ connectUdpHandler: {
298
+ wants: wantsConnectUdp,
299
+ handle: function (plainReq, carrier) {
300
+ var req = new IncomingMessage({
301
+ httpVersion: '3.0', method: plainReq.method, url: plainReq.url,
302
+ headers: plainReq.headers || {}, streamId: plainReq.stream_id, socket: plainReq.socket || _makeSocketStub()
303
+ });
304
+ if (req.headers.host === undefined && req.headers[':authority'] !== undefined) req.headers.host = req.headers[':authority'];
305
+ req._endData();
306
+ udpProxy.handle(req, carrier, emitConnectUdp);
307
+ }
308
+ },
72
309
  key: tlsKey,
73
310
  cert: tlsCert,
74
311
  ca: tlsCa,
75
312
  SNICallback: options.SNICallback || null,
313
+ ticketKeys: ticketKeys,
314
+ ticketData: ticketData,
315
+ ech: ech,
316
+ ocspRequest: ocspRequest,
317
+ // The HTTP/3 side's stateless reset secret (see quic_server). Random when
318
+ // not given.
319
+ resetTokenSecret: options.resetTokenSecret || null,
320
+ // HTTP/3 address validation with Retry — node:quic's validateAddress.
321
+ // Off unless set; see setValidateAddress below.
322
+ validateAddress: options.validateAddress === true,
323
+ // Handshakes under way: the threshold of 'handshakeLoad', and the cap per
324
+ // client address (quic_server, "Handshakes under way").
325
+ handshakeLoadThreshold: options.handshakeLoadThreshold,
326
+ // 0-RTT (HTTP/3): off unless true; see h3_server for earlyDataFilter.
327
+ earlyData: options.earlyData, earlyDataMaxAge: options.earlyDataMaxAge,
328
+ earlyDataReplay: options.earlyDataReplay, earlyDataFilter: options.earlyDataFilter,
329
+ maxHandshakesPerAddress: options.maxHandshakesPerAddress,
330
+ // Every version's: how long a request may take to arrive (http_common).
331
+ headersTimeout: options.headersTimeout,
332
+ requestTimeout: options.requestTimeout,
333
+ maxRequestsPerSocket: options.maxRequestsPerSocket,
334
+ // Rapid Reset: cancelled streams a connection may send (http_common).
335
+ streamResetBurst: options.streamResetBurst, streamResetRate: options.streamResetRate,
336
+ // WebTransport sessions a connection may hold at once (SETTINGS_WT_MAX_
337
+ // SESSIONS); unset, no limit.
338
+ maxWebTransportSessions: options.maxWebTransportSessions,
339
+ // QUIC's transport parameters (tls_bridge): windows and stream limits.
340
+ transportParams: options.transportParams,
341
+ // The congestion window a connection starts with, in packets (20).
342
+ initialCongestionWindow: options.initialCongestionWindow,
343
+ // A returning client's connection starts from what the last one measured
344
+ // (RFC 9959 Careful Resume); on unless false.
345
+ carefulResume: options.carefulResume,
346
+ // Startup's cap on the congestion window: 'stall' (default), 'always',
347
+ // 'off' (quic_connection, startupCap); per connection by connectionOptions.
348
+ startupCap: options.startupCap,
349
+ // Path MTU discovery (off by default on Windows), the largest UDP
350
+ // payload, the RTT assumed before a sample, the reordering a packet is
351
+ // allowed (quic_connection's CONNECTION_OPTIONS).
352
+ pmtuDiscovery: options.pmtuDiscovery,
353
+ maxUdpPayloadSize: options.maxUdpPayloadSize,
354
+ initialRtt: options.initialRtt,
355
+ reorderWindow: options.reorderWindow,
356
+ // Per QUIC connection, asked once its ClientHello is in: (info, callback)
357
+ // → { transportParams, initialCongestionWindow, carefulResume, startupCap,
358
+ // pmtuDiscovery, maxUdpPayloadSize, initialRtt, reorderWindow }.
359
+ connectionOptions: options.connectionOptions,
360
+ // The QUIC versions accepted, in preference (quic_version.js): ['v1', 'v2'].
361
+ quicVersions: options.quicVersions,
362
+ alpn: quicAlpn,
76
363
  maxConnections: options.maxConnections || 10000,
77
364
  // External UDP sockets (shared UDP port scenario). When provided,
78
365
  // the UDP layer is owned externally — caller feeds packets via
79
366
  // server.handlePacket(msg, rinfo). See RFC 9443.
80
367
  socket: options.socket || null,
81
- socket6: options.socket6 || null
368
+ socket6: options.socket6 || null,
369
+ // grease_quic_bit (RFC 9287): on, except with external sockets — a port
370
+ // they share with other protocols may tell QUIC apart by that bit.
371
+ // true: the port is QUIC's alone, grease there too.
372
+ greaseQuicBit: options.greaseQuicBit
82
373
  };
374
+ // node:tls's options (tls_options.js) — to a QUIC server that will run:
375
+ // one that does not (http3: false) is still built, and would refuse what
376
+ // only HTTP/3 cannot do (maxVersion TLSv1.2, TLS 1.2-only ciphers).
377
+ if (enableH3) Object.assign(h3Options, tlsPassOn);
83
378
 
84
379
  var isExternalUdp = !!(options.socket || options.socket6);
85
380
 
86
381
  var h3srv = createH3Server(h3Options, function (plainReq, plainRes) {
87
- var socketStub = _makeSocketStub();
88
- socketStub.remoteAddress = plainReq.remoteAddress || null;
89
- socketStub.remotePort = plainReq.remotePort || null;
382
+ // h3_server already attached a socket stand-in carrying the QUIC
383
+ // connection's address and its TLS fingerprints. Building a fresh stub
384
+ // here and using that instead threw all of it away — the application saw
385
+ // an object with no getJA4 and concluded, reasonably, that this
386
+ // connection had no TLS.
387
+ var socketStub = plainReq.socket || _makeSocketStub();
388
+ if (!socketStub.remoteAddress) socketStub.remoteAddress = plainReq.remoteAddress || null;
389
+ if (!socketStub.remotePort) socketStub.remotePort = plainReq.remotePort || null;
90
390
 
91
391
  // ---- Wrap plain req into IncomingMessage (Readable) ----
92
392
  var req = new IncomingMessage({
@@ -98,9 +398,68 @@ function createServer(options, handler) {
98
398
  socket: socketStub
99
399
  });
100
400
 
101
- // Bridge data from plain req → IncomingMessage
102
- plainReq.on('data', function (chunk) { req._pushData(chunk); });
103
- plainReq.on('end', function () { req._endData(); });
401
+ // req.headers.host, from :authority — as HTTP/2 already provides it.
402
+ //
403
+ // HTTP/2 and HTTP/3 both carry the host as :authority and no Host header,
404
+ // and the HTTP/2 path has always synthesized `host` from it
405
+ // (pseudoHeadersToRequest in http_common). This path never did, so the
406
+ // same application saw headers.host on one protocol and undefined on the
407
+ // other — including for every WebTransport session, which only exists
408
+ // over HTTP/3.
409
+ //
410
+ // ADDED, not substituted: the pseudo-headers stay, since code routes on
411
+ // them (:protocol is how a WebTransport CONNECT is recognised). And added
412
+ // AFTER the IncomingMessage is built, so rawHeaders — the record of what
413
+ // the client actually sent, and what JA4H is computed from — is untouched.
414
+ if (req.headers.host === undefined && req.headers[':authority'] !== undefined) {
415
+ req.headers.host = req.headers[':authority'];
416
+ }
417
+
418
+ // Carried across the wrapper, like the socket above: built on the
419
+ // connection, and lost if only the fields listed in the constructor
420
+ // survive.
421
+ if (plainReq.h3Fingerprint) req.h3Fingerprint = plainReq.h3Fingerprint;
422
+ // Came in 0-RTT (and earlyDataFilter let it through): it may be a
423
+ // replay — nothing with an effect that must happen once.
424
+ req.earlyData = plainReq.earlyData === true;
425
+ if (plainReq.webTransportProtocols) req.webTransportProtocols = plainReq.webTransportProtocols;
426
+
427
+ // Bridge data from plain req → IncomingMessage.
428
+ //
429
+ // The body's flow-control credit goes back to the client as the
430
+ // application READS it (_read), not as it arrives — as over HTTP/2 (see
431
+ // h2_server). A handler that does not read at once — an authorization
432
+ // check first, a lookup — had the whole upload taken in and held in
433
+ // memory meanwhile, as fast as the client could send it. Now the client
434
+ // can be ahead of the reader by one window, and no more.
435
+ var holdsBody = typeof plainReq._holdBody === 'function';
436
+ var received = null;
437
+ if (holdsBody) {
438
+ plainReq._holdBody();
439
+ // Credited for what has been read (utils' creditAsRead).
440
+ received = creditAsRead(req, function (n) { plainReq._creditBody(n); });
441
+ // Read to the end, or abandoned (destroyed, aborted): nothing more to
442
+ // wait for.
443
+ req.on('close', function () { plainReq._releaseBody(); });
444
+ }
445
+ plainReq.on('data', function (chunk) {
446
+ // Nobody will read it: let it go, credit and all.
447
+ if (req.destroyed) { if (holdsBody) plainReq._releaseBody(); return; }
448
+ if (received) received(chunk.length);
449
+ req._pushData(chunk);
450
+ });
451
+ plainReq.on('end', function () {
452
+ // Its trailers, if it sent any — before 'end', where node:http has them.
453
+ if (plainReq.trailers) {
454
+ req.trailers = plainReq.trailers;
455
+ req.rawTrailers = [];
456
+ for (var tn in plainReq.trailers) req.rawTrailers.push(tn, String(plainReq.trailers[tn]));
457
+ }
458
+ req._endData();
459
+ });
460
+ // Once the response is complete: is the rest of the body still wanted?
461
+ // As node:http2 decides it — if the application has started reading it.
462
+ plainReq._wantsRest = function () { return !req.destroyed && req.readableFlowing !== null; };
104
463
 
105
464
  // ---- Wrap plain res into ServerResponse (Writable) ----
106
465
  // ServerResponse._write → plainRes.write
@@ -112,6 +471,24 @@ function createServer(options, handler) {
112
471
  });
113
472
  res.req = req;
114
473
 
474
+ // The stream was cancelled under this request (see releaseHttpStream in
475
+ // h3_server) — handled as h2_server's abortStream does: 'aborted' always,
476
+ // an Error only to a listener that asked for one, and 'close' on the
477
+ // response so a handler still writing can stop.
478
+ plainReq.on('aborted', function () {
479
+ if (req.aborted) return;
480
+ var err = new Error('stream reset by peer'); err.code = 'ERR_STREAM_RESET';
481
+ req._abort(err); // 'aborted', an Error only to a listener, 'close'
482
+ res._abortResponse();
483
+ });
484
+ // The application giving the exchange up — res.destroy([err]),
485
+ // req.destroy([err]), as on node:http2 where either ends the stream —
486
+ // reaches the client: h3_server's abort (RESET_STREAM and STOP_SENDING,
487
+ // or only STOP_SENDING once the response is complete).
488
+ var giveUp = function (err) { plainRes.abort(err ? 0x0102 : 0x010c); }; // H3_INTERNAL_ERROR : H3_REQUEST_CANCELLED
489
+ res._onDestroy(giveUp);
490
+ req._onDestroy(function (err) { if (!res.writableFinished && !res.destroyed) res.destroy(err); else giveUp(err); });
491
+
115
492
  // Store wrapper references for WebTransport event forwarding.
116
493
  // MUST come after `res` is created (was previously set while res was
117
494
  // still undefined due to var hoisting).
@@ -119,10 +496,12 @@ function createServer(options, handler) {
119
496
  plainRes._wrapper = res;
120
497
 
121
498
  // Override internal methods to delegate to the plain H3 res.
122
- // Key insight: we buffer one chunk behind so that _final can
123
- // send the last data + FIN together via plainRes.end(chunk),
124
- // instead of plainRes.write(chunk) + plainRes.end() separately.
125
- // This avoids FIN-only packets that can get lost without PTO.
499
+ // One chunk is held back so that _final can send the last data and the
500
+ // FIN together via plainRes.end(chunk): res.end('ok') is one packet, not
501
+ // the data and then a FIN-only packet. (A FIN-only packet is recovered
502
+ // like any other when lost — see fin_pns in quic_connection — so this is
503
+ // about packets, not correctness.) Backpressure therefore bounds what is
504
+ // held at the high-water mark plus that one chunk.
126
505
  var _pendingChunk = null;
127
506
 
128
507
  res._flushHeaders = function () {
@@ -140,28 +519,41 @@ function createServer(options, handler) {
140
519
  plainRes.write(_pendingChunk);
141
520
  }
142
521
  _pendingChunk = chunk;
143
- callback();
522
+ // Done with this write once the stream has caught up to within the
523
+ // high-water mark — not at once. Calling back at once told Writable
524
+ // nothing was ever buffered, so write() never returned false and a
525
+ // piped file was read into memory as fast as the disk allowed.
526
+ plainRes.whenSent(res.writableHighWaterMark, callback);
144
527
  };
145
528
 
146
529
  res._final = function (callback) {
147
530
  if (!res.headersSent) res._flushHeaders();
531
+ // res.addTrailers(): a HEADERS frame after the body, which carries the
532
+ // FIN instead (gRPC's grpc-status rides there).
533
+ var trailers = res.trailers && Object.keys(res.trailers).length ? res.trailers : null;
148
534
  if (_pendingChunk !== null) {
149
- plainRes.end(_pendingChunk); // data + FIN together
535
+ plainRes.end(_pendingChunk, trailers); // data + FIN together
150
536
  } else {
151
- plainRes.end(); // FIN only (no body written)
537
+ plainRes.end(null, trailers); // FIN only (no body written)
152
538
  }
153
539
  callback();
154
540
  };
155
541
 
156
- // ---- WebTransport method proxying ----
157
- if (plainRes.sendDatagram) {
158
- res.sendDatagram = plainRes.sendDatagram.bind(plainRes);
159
- }
160
- if (plainRes.createBidirectionalStream) {
161
- res.createBidirectionalStream = plainRes.createBidirectionalStream.bind(plainRes);
542
+ // 103 Early Hints — the HTTP/3 one, written on the same stream.
543
+ if (plainRes.writeEarlyHints) {
544
+ res.writeEarlyHints = function (hints, callback) {
545
+ if (res.headersSent) { if (typeof callback === 'function') process.nextTick(callback); return; }
546
+ plainRes.writeEarlyHints(hints, callback);
547
+ };
162
548
  }
163
- if (plainRes.createUnidirectionalStream) {
164
- res.createUnidirectionalStream = plainRes.createUnidirectionalStream.bind(plainRes);
549
+
550
+ // ---- Methods of the HTTP/3 response: its priority, and WebTransport's ----
551
+ ['setPriority', 'sendDatagram', 'maxDatagramSize', 'createBidirectionalStream', 'createUnidirectionalStream',
552
+ 'createSendGroup', 'canOpenStream', 'setWebTransportProtocol', 'getStats', 'closeSession', 'drainSession'].forEach(function (m) {
553
+ if (typeof plainRes[m] === 'function') res[m] = plainRes[m].bind(plainRes);
554
+ });
555
+ if ('datagrams' in plainRes) {
556
+ Object.defineProperty(res, 'datagrams', { enumerable: true, get: function () { return plainRes.datagrams; } });
165
557
  }
166
558
 
167
559
  if (typeof handler === 'function') handler(req, res);
@@ -169,92 +561,264 @@ function createServer(options, handler) {
169
561
  });
170
562
 
171
563
 
564
+
565
+ // RFC 9220: WebSocket over HTTP/3 arrives as extended CONNECT on a QUIC
566
+ // stream and surfaces on the same 'upgrade' event as HTTP/1.1 and HTTP/2.
567
+ // Three transports, one event, one WebSocketServer.
568
+ h3srv.on('upgrade', function (req, stream, head) { ev.emit('upgrade', req, stream, head); });
569
+ h3srv.on('stream_open', function (conn, id) { ev.emit('stream_open', conn, id); });
570
+ h3srv.on('stream_reset', function (conn, id, code) { ev.emit('stream_reset', conn, id, code); });
571
+
572
+ // A new QUIC connection, before its handshake. On a server that runs all
573
+ // three protocols this shares the event with TCP connections, so a listener
574
+ // may receive either a net.Socket or a QUIC connection handle. Both carry
575
+ // remoteAddress, destroyed and destroy() — the three things screening code
576
+ // uses — and the handle's httpVersion ('3.0') tells them apart where it
577
+ // matters.
578
+ h3srv.on('connection', function (conn) { ev.emit('connection', conn); });
579
+ h3srv.on('handshakeLoad', function (e) { ev.emit('handshakeLoad', e); });
580
+ h3srv.on('secureConnection', function (conn) { ev.emit('secureConnection', conn); });
581
+ h3srv.on('tlsClientError', function (err, conn) { ev.emit('tlsClientError', err, conn); });
582
+
172
583
  // ============================================================
173
- // TCP — HTTP/2 and/or HTTP/1.1
584
+ // TCP — HTTP/2 and HTTP/1.1, both ours.
585
+ //
586
+ // Previously node:http2 and node:https. They are gone: h2_server and
587
+ // h1_server implement the same two protocols, and tcp_listener owns the
588
+ // socket and the ALPN choice between them — one port, one certificate, one
589
+ // preference order, decided in one place.
590
+ //
591
+ // The three request paths now converge completely. h3_server, h2_server and
592
+ // h1_server each hand the application the same IncomingMessage /
593
+ // ServerResponse pair from streams.js, so `handler` cannot tell which
594
+ // protocol produced a request except by reading req.httpVersion — which is
595
+ // exactly the property that lets Express and Fastify run unchanged over all
596
+ // three.
174
597
  // ============================================================
175
598
 
176
- var tcpServer = null;
599
+ // The QUIC layer's errors — a UDP bind that fails, most importantly — are
600
+ // this server's errors. h3_server emits them, and nothing here listened,
601
+ // so a port that could not be bound left listen() waiting forever with no
602
+ // sign of why.
603
+ h3srv.on('error', function (err) {
604
+ if (err && !err.transport) err.transport = 'udp';
605
+ ev.emit('error', err);
606
+ });
177
607
 
178
- function startTcpServer(listenPort, host, callback) {
179
- var hasTlsCredentials = (tlsKey && tlsCert) || options.SNICallback;
180
- if (!needsTcp || !hasTlsCredentials) {
181
- if (callback) callback();
182
- return;
183
- }
608
+ // ============================================================
609
+ // Building versus binding.
610
+ //
611
+ // Everything a request needs — the TLS layer, ALPN, HTTP/2, HTTP/1.1,
612
+ // HTTP/3 — is built HERE, when the server is created. listen() only binds
613
+ // the ports this server owns.
614
+ //
615
+ // It used to be the other way round: the protocol layers were built inside
616
+ // listen(), so a server whose sockets belonged to someone else still had to
617
+ // call listen() to exist — a listen() that listened to nothing — and a
618
+ // separate flag told it not to bind. Now the rule is simply:
619
+ //
620
+ // listen() not called → nothing bound; feed it through
621
+ // handlePacket() (UDP) and handleConnection()
622
+ // (TCP), from whoever owns the ports
623
+ // listen(port) called → binds TCP, and UDP unless `socket` was given
624
+ //
625
+ // A server can also be built and bound — the ordinary case — with nothing
626
+ // changing for callers that always called listen().
627
+ // ============================================================
184
628
 
185
- // Bridge SNICallback: detect context type and route accordingly.
186
- // quico context (_nodeContext) → use pre-built node:tls context
187
- // quico context (_rawKey) → build node:tls context from stored creds
188
- // node:tls context → use directly (someone used tls.createSecureContext)
189
- var tcpSniCallback = null;
190
- if (options.SNICallback) {
191
- tcpSniCallback = function (servername, cb) {
192
- options.SNICallback(servername, function (err, ctx) {
193
- if (err) return cb(err);
194
- if (!ctx) return cb(new Error('SNICallback returned null'));
195
-
196
- // 1. Pre-built node:tls context (fastest path)
197
- if (ctx._nodeContext) {
198
- return cb(null, ctx._nodeContext);
199
- }
200
- // 2. Raw key/cert stored — build node:tls context
201
- if (ctx._rawKey && ctx._rawCert) {
202
- var nodeOpts = { key: ctx._rawKey, cert: ctx._rawCert };
203
- if (ctx._rawCa) nodeOpts.ca = ctx._rawCa;
204
- try { return cb(null, nodeTls.createSecureContext(nodeOpts)); }
205
- catch (e) { return cb(e); }
206
- }
207
- // 3. Assume it's already a node:tls context (tls.createSecureContext)
208
- cb(null, ctx);
209
- });
210
- };
211
- }
629
+ // The port Alt-Svc advertises. listen(port) sets it; a server that is fed
630
+ // from outside never calls listen(), and takes it from `options.port`
631
+ // instead — quico cannot otherwise know which port its clients see.
632
+ var altSvcPort = (options.port !== undefined && options.port !== null) ? options.port : 443;
212
633
 
213
- var altSvcHeader = 'h3=":' + listenPort + '"; ma=86400';
634
+ // A function, so HTTP/1.1 and HTTP/2 read the value per response. They are
635
+ // built before listen() can say which port is real.
636
+ //
637
+ // Alt-Svc advertises HTTP/3 on the same authority (RFC 9114 §3.3), which is
638
+ // how a browser that arrived over TCP learns to try QUIC next time.
639
+ //
640
+ // Only for an HTTP/3 this server runs. With http3:false it was sent anyway,
641
+ // on the guess that HTTP/3 was served elsewhere on the same port — and when
642
+ // it was not, every browser was sent to try a QUIC endpoint that does not
643
+ // exist, failing over back to TCP each time the advertisement was renewed.
644
+ // HTTP/3 served by something else is said with `altSvc`, the header value
645
+ // as is (`'h3=":443"; ma=86400'`); `altSvc: false` sends none at all.
646
+ function altSvcValue() { return 'h3=":' + altSvcPort + '"; ma=86400'; }
647
+ var altSvcOption = options.altSvc;
648
+ if (altSvcOption !== undefined && altSvcOption !== false && altSvcOption !== null && typeof altSvcOption !== 'string') {
649
+ throw new TypeError('options.altSvc must be a string or false');
650
+ }
651
+ function altSvcHeader() {
652
+ if (typeof altSvcOption === 'string') return altSvcOption;
653
+ if (altSvcOption === false || altSvcOption === null || !enableH3) return null;
654
+ return altSvcValue();
655
+ }
214
656
 
215
- if (enableH2) {
216
- // ---- HTTP/2 (+ optional HTTP/1.1) via node:http2 ----
217
- var tcpOpts = {
218
- allowHTTP1: enableH1,
219
- settings: { enablePush: false }
220
- };
221
- if (tlsKey) tcpOpts.key = tlsKey;
222
- if (tlsCert) tcpOpts.cert = tlsCert;
223
- if (tlsCa) tcpOpts.ca = tlsCa;
224
- if (tcpSniCallback) tcpOpts.SNICallback = tcpSniCallback;
657
+ var tcpServer = null;
658
+ var hasTlsCredentials = (tlsKey && tlsCert) || options.SNICallback;
225
659
 
226
- tcpServer = http2.createSecureServer(tcpOpts);
660
+ function onRequest(req, res) {
661
+ if (req.earlyData === true) { earlyGate(req, res); return; }
662
+ runHandler(req, res);
663
+ }
664
+ function runHandler(req, res) {
665
+ if (typeof handler === 'function') handler(req, res);
666
+ ev.emit('request', req, res);
667
+ }
227
668
 
228
- tcpServer.on('request', function (req, res) {
229
- res.setHeader('alt-svc', altSvcHeader);
230
- if (typeof handler === 'function') handler(req, res);
231
- ev.emit('request', req, res);
232
- });
669
+ // A request read from TLS early data (HTTP/2, HTTP/1.1; HTTP/3's is
670
+ // h3_server's): handled now only if earlyDataFilter lets it — by default a
671
+ // GET, HEAD or OPTIONS — or answered 425 Too Early, which the client
672
+ // sends again after the handshake (RFC 8470 §5.2). No answer within
673
+ // 100 ms: 425.
674
+ function earlyGate(req, res) {
675
+ runEarlyDataFilter(options.earlyDataFilter, req, function (allow) {
676
+ if (res.writableEnded || res.destroyed) return;
677
+ if (allow) { runHandler(req, res); return; }
678
+ req.resume();
679
+ stats.earlyData.tooEarly++;
680
+ try { res.writeHead(425, { 'content-length': '0' }); res.end(); } catch (e) {}
681
+ });
682
+ }
233
683
 
234
- } else if (enableH1) {
235
- // ---- HTTP/1.1 only via node:https ----
236
- var tcpOpts = {};
237
- if (tlsKey) tcpOpts.key = tlsKey;
238
- if (tlsCert) tcpOpts.cert = tlsCert;
239
- if (tlsCa) tcpOpts.ca = tlsCa;
240
- if (tcpSniCallback) tcpOpts.SNICallback = tcpSniCallback;
684
+ if (needsTcp) {
685
+ var shared = {
686
+ shouldUpgradeCallback: shouldUpgradeCallback,
687
+ // Not over cleartext: HTTP/3 is always TLS, and browsers take Alt-Svc
688
+ // only from an https origin — on http:// it advertised a service that
689
+ // could not exist there.
690
+ altSvc: hasTlsCredentials && altSvcHeader() !== null ? altSvcHeader : null,
691
+ maxHeaderListSize: options.maxHeaderListSize,
692
+ headersTimeout: options.headersTimeout,
693
+ keepAliveTimeout: options.keepAliveTimeout,
694
+ requestTimeout: options.requestTimeout,
695
+ // node:http's: requests per connection (HTTP/1.1: the Nth closes it;
696
+ // HTTP/2 and HTTP/3: GOAWAY after it).
697
+ maxRequestsPerSocket: options.maxRequestsPerSocket,
698
+ // Rapid Reset, HTTP/2's (http_common streamResetLimiter).
699
+ streamResetBurst: options.streamResetBurst, streamResetRate: options.streamResetRate,
700
+ maxHeadSize: options.maxHeadSize || options.maxHeaderSize,
701
+ maxHeaderCount: options.maxHeaderCount,
702
+ // These were being dropped: an application could set them and nothing
703
+ // downstream ever saw them, which is worse than not offering the option
704
+ // at all — it looks configured and is not.
705
+ maxConcurrentStreams: options.maxConcurrentStreams,
706
+ maxSessionMemory: options.maxSessionMemory,
707
+ initialWindowSize: options.initialWindowSize,
708
+ connectionWindowSize: options.connectionWindowSize,
709
+ maxFrameSize: options.maxFrameSize,
710
+ headerTableSize: options.headerTableSize,
711
+ enableConnectProtocol: options.enableConnectProtocol,
712
+ stats: stats
713
+ };
714
+
715
+ tcpServer = createTcpListener(Object.assign({
716
+ // Cleartext when there are no credentials: a dev server, or a service
717
+ // behind a terminating proxy. Without TLS there is no ALPN, so
718
+ // tcp_listener tells h2c from HTTP/1.1 by looking for the connection
719
+ // preface instead.
720
+ tls: hasTlsCredentials ? lemonTls : null,
721
+ key: tlsKey,
722
+ cert: tlsCert,
723
+ ca: tlsCa,
724
+ SNICallback: options.SNICallback,
725
+ ticketKeys: ticketKeys,
726
+ ticketData: ticketData,
727
+ ech: ech,
728
+ ocspRequest: ocspRequest,
729
+ // node:http's TCP options, with its defaults: Nagle off, keep-alive off.
730
+ noDelay: options.noDelay,
731
+ keepAlive: options.keepAlive,
732
+ keepAliveInitialDelay: options.keepAliveInitialDelay,
733
+ // 0-RTT over TLS 1.3 (HTTP/2, HTTP/1.1): the same options as HTTP/3's,
734
+ // as they are at each connection (setOptions changes them).
735
+ earlyDataNow: function () { var o = h3srv.getOptions(); return { on: o.earlyData === true, maxAge: o.earlyDataMaxAge }; },
736
+ earlyDataReplay: options.earlyDataReplay,
737
+ stats: stats
738
+ }, tlsPassOn)); // node:tls's options (tls_options.js)
241
739
 
242
- tcpServer = nodeHttps.createServer(tcpOpts);
740
+ var h2srv = null;
741
+ var h1srv = null;
243
742
 
244
- tcpServer.on('request', function (req, res) {
245
- res.setHeader('alt-svc', altSvcHeader);
246
- if (typeof handler === 'function') handler(req, res);
247
- ev.emit('request', req, res);
743
+ if (enableH2) {
744
+ h2srv = createH2Server(shared, onRequest);
745
+ h2srv.on('clientError', function (err, sock) { ev.emit('clientError', err, sock); });
746
+ h2srv.on('sessionError', function (err, session) { ev.emit('sessionError', err, session); });
747
+ // Extended CONNECT (RFC 8441) arrives on the same 'upgrade' event as an
748
+ // HTTP/1.1 Upgrade, so a WebSocketServer attached to this server serves
749
+ // both without knowing which carried the connection.
750
+ h2srv.on('upgrade', function (req, stream, head) {
751
+ if (wantsConnectUdp(req)) { connectUdpH2(req, stream); return; }
752
+ ev.emit('upgrade', req, stream, head);
753
+ });
754
+ // Stream lifecycle, for rate limiting. `conn` carries the address to key
755
+ // on and the two ways to refuse; see h2_server.
756
+ h2srv.on('stream_open', function (conn, id) { ev.emit('stream_open', conn, id); });
757
+ h2srv.on('stream_reset', function (conn, id, code) { ev.emit('stream_reset', conn, id, code); });
758
+ }
759
+ if (enableH1) {
760
+ h1srv = createH1Server(shared, onRequest);
761
+ h1srv.on('clientError', function (err, sock) { ev.emit('clientError', err, sock); });
762
+ // 'upgrade' is how WebSocket (and anything else that switches protocols)
763
+ // gets the raw socket. Same signature as node:http, so the `ws` package
764
+ // works over this server unchanged.
765
+ h1srv.on('upgrade', function (req, socket, head) {
766
+ if (wantsConnectUdp(req)) { connectUdpH1(req, socket, head); return; }
767
+ ev.emit('upgrade', req, socket, head);
248
768
  });
249
769
  }
250
770
 
251
- if (tcpServer) {
252
- tcpServer.on('error', function (err) { ev.emit('error', err); });
253
- var tcpHost = host || '0.0.0.0';
254
- tcpServer.listen(listenPort, tcpHost, callback);
255
- } else {
256
- if (callback) callback();
771
+ // Registration order is the server's ALPN preference — tcpAlpn, which is
772
+ // h2 then HTTP/1.1 unless ALPNProtocols says otherwise. Names beyond HTTP
773
+ // are only meaningful with TLS: without it there is no ALPN to carry them.
774
+ for (var a = 0; a < tcpAlpn.length; a++) {
775
+ var proto = tcpAlpn[a];
776
+ if (proto === 'h2') tcpServer.register('h2', h2srv.handleConnection);
777
+ else if (proto === 'http/1.1') tcpServer.register('http/1.1', h1srv.handleConnection);
778
+ else if (hasTlsCredentials) tcpServer.register(proto, closeAfterHandshake);
257
779
  }
780
+
781
+ tcpServer.on('error', function (err) { ev.emit('error', err); });
782
+ tcpServer.on('clientError', function (err, sock) { ev.emit('clientError', err, sock); });
783
+ tcpServer.on('tlsClientError', function (err, sock) { ev.emit('tlsClientError', err, sock); });
784
+ // Fires before the TLS handshake — see tcp_listener. Same name and same
785
+ // argument as node:net, so an IP screen written for node:http works here.
786
+ tcpServer.on('connection', function (sock) {
787
+ armSocketTimeout(sock);
788
+ ev.emit('connection', sock);
789
+ });
790
+ tcpServer.on('secureConnection', function (sock) { ev.emit('secureConnection', sock); });
791
+ }
792
+
793
+ // External UDP: the sockets are the caller's, and so is reading them. What
794
+ // the QUIC layer still needs is its own housekeeping — the idle sweep, and
795
+ // larger kernel buffers — and that starts now, since listen() may never be
796
+ // called. h3srv.listen() in external mode binds nothing; see quic_server.
797
+ if (enableH3 && isExternalUdp) {
798
+ h3srv.listen(altSvcPort, null, function () {});
799
+ }
800
+
801
+
802
+ /**
803
+ * Accept a TCP connection accepted by someone else.
804
+ *
805
+ * For a server whose TCP port belongs to another component — typically one
806
+ * that demultiplexes it by first byte, sending TLS here and other protocols
807
+ * elsewhere, the way RFC 9443 splits a UDP port. The socket is treated
808
+ * exactly as one this server accepted itself: 'connection' fires before the
809
+ * TLS handshake, and ALPN routes it to HTTP/2 or HTTP/1.1.
810
+ *
811
+ * Works without listen(): the layers exist from createServer. Bytes the
812
+ * owner read to classify the connection must be put back first —
813
+ * socket.unshift(chunk) — or the TLS layer starts mid-record.
814
+ */
815
+ function handleConnection(socket) {
816
+ if (!tcpServer) {
817
+ // HTTP/1.1 and HTTP/2 are both off; there is nothing to hand it to.
818
+ try { socket.destroy(); } catch (e) {}
819
+ return;
820
+ }
821
+ tcpServer.handleConnection(socket);
258
822
  }
259
823
 
260
824
 
@@ -264,52 +828,67 @@ function createServer(options, handler) {
264
828
 
265
829
  function listen(listenPort, host, callback) {
266
830
  if (typeof host === 'function') { callback = host; host = null; }
267
- port = listenPort || 443;
268
831
 
269
- var remaining = needsTcp ? 2 : 1;
832
+ // `|| 443` turned port 0 into 443. Port 0 is not "no port": it is the
833
+ // documented way to ask the OS for a free one, which every test harness
834
+ // and every process that binds more than one server relies on. The old
835
+ // form silently bound 443 instead — and on a machine where that needs
836
+ // privileges, failed with EACCES for a caller that never mentioned 443.
837
+ port = (listenPort === undefined || listenPort === null) ? 443 : listenPort;
838
+ if (port !== 0) altSvcPort = port;
839
+
840
+ // Only what this server owns: UDP unless it was handed sockets, TCP unless
841
+ // both HTTP/1.1 and HTTP/2 are off.
842
+ var bindUdp = enableH3 && !isExternalUdp;
843
+ var remaining = (bindUdp ? 1 : 0) + (needsTcp ? 1 : 0);
270
844
  var called = false;
271
845
 
272
846
  function done() {
273
847
  remaining--;
274
848
  if (remaining <= 0 && !called) {
275
849
  called = true;
850
+ // After listen(0) the OS chose the port; Alt-Svc must name that one.
851
+ if (port === 0 && tcpServer && tcpServer.address()) altSvcPort = tcpServer.address().port;
276
852
  ev.emit('listening');
277
853
  if (typeof callback === 'function') callback();
278
854
  }
279
855
  }
280
856
 
281
- // H3 — always. In external mode h3srv.listen() wires up the external
282
- // sockets' message handler and starts the sweep timer, but does not
283
- // bind anything new.
284
- h3srv.listen(port, host, done);
857
+ // Nothing to bind — every socket is external. Still answer, and still
858
+ // asynchronously, like a real bind would.
859
+ if (remaining === 0) { remaining = 1; setImmediate(done); return server; }
285
860
 
286
- // TCP — if needed
287
- if (needsTcp) {
288
- startTcpServer(port, host, done);
289
- }
861
+ if (bindUdp) h3srv.listen(port, host, done);
862
+ if (needsTcp) tcpServer.listen(port, host || '0.0.0.0', done);
290
863
 
291
864
  return server;
292
865
  }
293
866
 
294
867
  function close(callback) {
295
- var remaining = 1 + (tcpServer ? 1 : 0);
868
+ // h3srv is closed whenever HTTP/3 is on, bound or not: in external mode it
869
+ // still runs a sweep timer, started at creation.
870
+ var remaining = (enableH3 ? 1 : 0) + (tcpServer ? 1 : 0);
296
871
 
297
872
  function onClose() {
298
873
  remaining--;
299
874
  if (remaining <= 0 && typeof callback === 'function') callback();
300
875
  }
301
876
 
302
- h3srv.close(onClose);
877
+ if (remaining === 0) { if (typeof callback === 'function') callback(); return; }
878
+ if (enableH3) h3srv.close(onClose);
879
+ // tcp_listener.close() ends idle connections, including ones handed in
880
+ // through handleConnection, and closes its port only if it bound one.
303
881
  if (tcpServer) { tcpServer.close(onClose); tcpServer = null; }
304
- else if (remaining <= 1 && typeof callback === 'function') callback();
305
882
  }
306
883
 
307
884
  function address() {
885
+ // The TCP listener knows the port the OS actually assigned, which is the
886
+ // only useful answer after listen(0).
308
887
  if (tcpServer && typeof tcpServer.address === 'function') {
309
888
  var addr = tcpServer.address();
310
889
  if (addr) return addr;
311
890
  }
312
- return { address: '0.0.0.0', family: 'IPv4', port: port };
891
+ return { address: '0.0.0.0', family: 'IPv4', port: altSvcPort };
313
892
  }
314
893
 
315
894
 
@@ -317,21 +896,170 @@ function createServer(options, handler) {
317
896
  // Public API
318
897
  // ============================================================
319
898
 
899
+ // ---- server.setTimeout / server.timeout — node:http's ----
900
+ //
901
+ // Inactivity on a connection's socket: after `serverTimeout` ms with nothing
902
+ // read or written, 'timeout' is emitted with the socket — and if nothing
903
+ // listens for it, the socket is destroyed. 0 (the default) disables it.
904
+ // Applied when a connection is accepted, so a change affects new
905
+ // connections only — as in Node. TCP connections (HTTP/1.1, HTTP/2); an
906
+ // HTTP/3 connection has QUIC's own idle timeout. This used to only register
907
+ // the callback: no connection ever timed out.
908
+ var serverTimeout = 0;
909
+ function armSocketTimeout(sock) {
910
+ if (!(serverTimeout > 0) || typeof sock.setTimeout !== 'function') return;
911
+ sock.setTimeout(serverTimeout);
912
+ sock.on('timeout', function () {
913
+ if (ev.listenerCount('timeout') > 0) ev.emit('timeout', sock);
914
+ else { try { sock.destroy(); } catch (e) {} }
915
+ });
916
+ }
917
+
320
918
  var server = {
321
919
  listen: listen,
322
920
  close: close,
323
921
  address: address,
324
- on: function (name, fn) { ev.on(name, fn); return server; },
325
- once: function (name, fn) { ev.once(name, fn); return server; },
922
+ handleConnection: handleConnection,
923
+
924
+ /**
925
+ * The session ticket keys: 48 bytes, the key every ticket is sealed with
926
+ * — TCP and QUIC alike. tls.Server's names and rules: setTicketKeys takes
927
+ * a 48-byte Buffer and throws on anything else.
928
+ *
929
+ * A server that keeps its keys across restarts (a file, say) passes them
930
+ * as options.ticketKeys, and can replace them while running: connections
931
+ * from then on use the new key. Tickets sealed with the old one no longer
932
+ * open — each of those clients does one full handshake and gets a new
933
+ * ticket, as after a restart with random keys.
934
+ */
935
+ getTicketKeys: function () { return ticketKeys; },
936
+ setTicketKeys: function (keys) {
937
+ if (!Buffer.isBuffer(keys) || keys.length !== 48) {
938
+ throw new TypeError('ticket keys must be a 48-byte Buffer');
939
+ }
940
+ ticketKeys = keys;
941
+ if (tcpServer) tcpServer.setTicketKeys(keys);
942
+ h3srv.setTicketKeys(keys);
943
+ },
944
+
945
+ /**
946
+ * The Encrypted Client Hello keys, TCP and QUIC alike. Rotation without a
947
+ * restart: publish the new config in DNS (ech= in the HTTPS record), then
948
+ * setEchKeys with the new key and the old one kept as retry: false until
949
+ * the old record's TTL has run out — clients still holding it connect
950
+ * meanwhile. New connections use the new set; a bad key throws and the
951
+ * old set stays.
952
+ */
953
+ getEchKeys: function () { return ech; },
954
+ setEchKeys: function (keys) {
955
+ if (keys) lemonTls.checkEchKeys(keys);
956
+ ech = keys || null;
957
+ if (tcpServer) tcpServer.setEchKeys(ech);
958
+ h3srv.setEchKeys(ech);
959
+ },
960
+
961
+ /**
962
+ * The secret HTTP/3 stateless reset tokens are derived from (RFC 9000
963
+ * §10.3) — node:quic's resetTokenSecret, at least 16 bytes. Kept across
964
+ * restarts (options.resetTokenSecret, or set here once read), it lets a
965
+ * restarted server tell each browser still holding a connection to the
966
+ * old one that it is gone, instead of leaving it to time out.
967
+ */
968
+ /**
969
+ * HTTP/3 address validation with Retry (RFC 9000 §8.1.2): while on, each
970
+ * new client proves its address with one extra round trip before any
971
+ * connection state or key exchange is spent on it — the defence against
972
+ * a flood of Initials from spoofed addresses. quico never turns it on by
973
+ * itself; the application decides when (always, never, under load) and
974
+ * can switch it at any moment. Applies from the next new client on.
975
+ */
976
+ getValidateAddress: function () { return h3srv.getValidateAddress(); },
977
+ setValidateAddress: function (on) { h3srv.setValidateAddress(on); },
978
+
979
+ /**
980
+ * HTTP/3 handshakes under way: connections made and not yet complete
981
+ * (the client's Finished verified). The application decides what load
982
+ * is — 'handshakeLoad' ({ pending, high }) tells it when the count
983
+ * reaches handshakeLoadThreshold (high: true) and when it is back under
984
+ * half of it (high: false) — and what to do: setOptions({
985
+ * validateAddress: true }), say. maxHandshakesPerAddress caps them per
986
+ * client address; past it an Initial makes no connection. 0 turns either
987
+ * off. Both change with setOptions, at once.
988
+ */
989
+ getPendingHandshakes: function () { return h3srv.getPendingHandshakes(); },
990
+ /**
991
+ * The options that can change while the server runs, by createServer's
992
+ * names — one call, a partial object, checked whole before anything
993
+ * changes (a bad value: TypeError / RangeError, nothing changed):
994
+ * validateAddress, handshakeLoadThreshold, maxHandshakesPerAddress
995
+ * the server's (from the next client; the threshold at once);
996
+ * startupCap, initialCongestionWindow, carefulResume, pmtuDiscovery,
997
+ * maxUdpPayloadSize, initialRtt, reorderWindow
998
+ * what new HTTP/3 connections are made with — open ones keep
999
+ * theirs (conn.setOptions, or connectionOptions, for one).
1000
+ * A name that cannot change this way says what can (cert:
1001
+ * setSecureContext()). getOptions(): all of them, as they are now.
1002
+ */
1003
+ getOptions: function () { return h3srv.getOptions(); },
1004
+ setOptions: function (o) { h3srv.setOptions(o); return server; },
1005
+
1006
+ getResetTokenSecret: function () { return h3srv.getResetTokenSecret(); },
1007
+ setResetTokenSecret: function (secret) { h3srv.setResetTokenSecret(secret); },
1008
+
1009
+ /**
1010
+ * Carry another protocol over this server's TLS port, beside HTTP.
1011
+ *
1012
+ * A client that negotiates `name` by ALPN is handed to `handler(socket)`
1013
+ * after the TLS handshake, as a decrypted Duplex. `options.sniff(bytes)`
1014
+ * also claims connections that negotiated no ALPN, by their first bytes —
1015
+ * how TURN over TLS on 443 is recognised from a browser, which offers no
1016
+ * ALPN for it. `options.paused` hands the socket over paused for the
1017
+ * handler to resume. See register() in tcp_listener.
1018
+ *
1019
+ * The name is added to the ALPN list the server advertises, after the
1020
+ * HTTP versions, so an HTTPS client still gets h2 or HTTP/1.1.
1021
+ */
1022
+ addProtocol: function (name, handler, options) {
1023
+ if (!tcpServer) throw new Error('addProtocol needs the TCP side (http1 or http2 enabled)');
1024
+ if (!hasTlsCredentials) throw new Error('addProtocol needs TLS: without it there is no ALPN, and nothing to sniff after');
1025
+ tcpServer.register(name, handler, options);
1026
+ return server;
1027
+ },
1028
+
1029
+ /**
1030
+ * CONNECT-UDP's figures, across this server's tunnels: { active,
1031
+ * accepted, rejected, closed, packetsFromClient, bytesFromClient,
1032
+ * packetsToClient, bytesToClient, droppedFromClient, droppedToClient }
1033
+ * — those of tunnels still open included. Each tunnel has its own
1034
+ * (tunnel.stats, and the 'close' event's argument).
1035
+ */
1036
+ getConnectUdpStats: function () { return udpProxy.stats(); },
1037
+
1038
+ /**
1039
+ * The server's counters since it was made, a copy: connections and TLS
1040
+ * handshakes over QUIC and TCP, the protocols they spoke, 0-RTT taken,
1041
+ * refused and answered 425, how connections ended by code (QUIC and
1042
+ * HTTP/3 CONNECTION_CLOSE, HTTP/2 GOAWAY, HTTP/1.1 requests refused),
1043
+ * streams reset by code, datagrams held at the anti-amplification limit,
1044
+ * internal errors caught. Counts only — no addresses, no names.
1045
+ * See server_stats.js.
1046
+ */
1047
+ getStats: function () { return stats.snapshot(); },
1048
+
1049
+ // server.on('request', app) — the other way an app is attached.
1050
+ on: function (name, fn) { if (name === 'request') adoptApp(fn); ev.on(name, fn); return server; },
1051
+ once: function (name, fn) { if (name === 'request') adoptApp(fn); ev.once(name, fn); return server; },
326
1052
  off: function (name, fn) { ev.off(name, fn); return server; },
327
1053
  removeListener: function (name, fn) { ev.removeListener(name, fn); return server; },
328
1054
  emit: function () { ev.emit.apply(ev, arguments); return server; },
329
1055
  setTimeout: function (msecs, callback) {
330
- if (tcpServer && typeof tcpServer.setTimeout === 'function') {
331
- tcpServer.setTimeout(msecs, callback);
332
- }
1056
+ if (typeof msecs === 'function') { callback = msecs; msecs = undefined; }
1057
+ if (msecs !== undefined) serverTimeout = Math.max(0, Number(msecs) || 0);
1058
+ if (typeof callback === 'function') ev.on('timeout', callback);
333
1059
  return server;
334
1060
  },
1061
+ get timeout() { return serverTimeout; },
1062
+ set timeout(ms) { serverTimeout = Math.max(0, Number(ms) || 0); },
335
1063
 
336
1064
  /** Feed an incoming UDP packet from a shared/external socket.
337
1065
  * rinfo must be in Node's dgram format: { address, port, family, size }.