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/README.md +1768 -155
- package/index.js +55 -36
- package/package.json +3 -3
- package/src/agent.js +306 -36
- package/src/client.js +447 -120
- package/src/client_profile.js +266 -0
- package/src/connect_udp.js +578 -0
- package/src/connect_udp_client.js +770 -0
- package/src/crypto.js +240 -140
- package/src/h1.js +953 -0
- package/src/h1_client.js +731 -0
- package/src/h1_server.js +624 -0
- package/src/h2.js +1325 -0
- package/src/h2_client.js +675 -0
- package/src/h2_fingerprint.js +403 -0
- package/src/h2_frames.js +582 -0
- package/src/h2_server.js +862 -0
- package/src/h3.js +1966 -469
- package/src/h3_client.js +467 -48
- package/src/h3_server.js +1349 -94
- package/src/hpack.js +455 -0
- package/src/hpack_primitives.js +520 -0
- package/src/http_common.js +901 -0
- package/src/ja4h.js +256 -0
- package/src/qpack.js +555 -0
- package/src/quic_connection.js +6517 -657
- package/src/quic_server.js +768 -73
- package/src/quic_socket.js +134 -9
- package/src/quic_version.js +143 -0
- package/src/server.js +843 -115
- package/src/server_stats.js +93 -0
- package/src/streams.js +361 -35
- package/src/tcp_listener.js +557 -0
- package/src/tls_bridge.js +474 -73
- package/src/tls_options.js +108 -0
- package/src/transport.js +455 -46
- package/src/utils.js +94 -12
- package/src/webtransport.js +433 -436
- package/src/ws.js +624 -0
- package/src/ws_client.js +641 -0
- package/src/ws_server.js +833 -0
- package/src/wt_stream.js +618 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* quico: HTTP/3 and QUIC implementation for Node.js
|
|
3
|
+
* Copyright 2025 colocohen
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at:
|
|
8
|
+
*
|
|
9
|
+
* https://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* This file is part of the open-source project hosted at:
|
|
12
|
+
* https://github.com/colocohen/quico
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import net from 'node:net';
|
|
16
|
+
import crypto from 'node:crypto';
|
|
17
|
+
import { Emitter } from './http_common.js';
|
|
18
|
+
import { serverTlsOptions } from './tls_options.js';
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// ============================================================
|
|
22
|
+
// TCP transport: sockets, TLS, and ALPN dispatch.
|
|
23
|
+
//
|
|
24
|
+
// The mirror image of quic_server.js. That file owns the UDP socket,
|
|
25
|
+
// demultiplexes datagrams and hands a raw QUICConnection to whatever
|
|
26
|
+
// application protocol layers on top; this one owns the TCP socket, runs the
|
|
27
|
+
// TLS handshake, and hands a ready Duplex to whichever protocol ALPN selected.
|
|
28
|
+
// Neither knows anything about HTTP.
|
|
29
|
+
//
|
|
30
|
+
// It exists because h1_server, h2_server and ws_server must share one port —
|
|
31
|
+
// a client picks between them with ALPN, and something has to make that
|
|
32
|
+
// choice. Putting the choice in each server means three copies of
|
|
33
|
+
// net.createServer, three SNICallback bridges, three ALPN lists that can
|
|
34
|
+
// disagree, and three close()/address() implementations. Here it is written
|
|
35
|
+
// once and the protocol layers become pure: they receive a socket and never
|
|
36
|
+
// create one.
|
|
37
|
+
//
|
|
38
|
+
// TLS comes from lemon-tls, passed in as `options.tls`. Without it the
|
|
39
|
+
// listener is cleartext, which is what a local dev server or a service behind
|
|
40
|
+
// a terminating proxy wants — and what h2c and ws:// need.
|
|
41
|
+
// ============================================================
|
|
42
|
+
|
|
43
|
+
// The HTTP/2 connection preface (RFC 9113 §3.4). Used here only to sniff a
|
|
44
|
+
// cleartext connection; the h2 layer validates it properly.
|
|
45
|
+
var H2_PREFACE = Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n', 'latin1');
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
function createTcpListener(options) {
|
|
49
|
+
options = options || {};
|
|
50
|
+
|
|
51
|
+
var ev = Emitter();
|
|
52
|
+
var server = null;
|
|
53
|
+
var handlers = {}; // alpn protocol -> handler(socket, protocol)
|
|
54
|
+
var handlerOpts = {}; // alpn protocol -> { sniff, paused }
|
|
55
|
+
// Session tickets: ONE key for every connection of this server, so a
|
|
56
|
+
// ticket issued on one connection opens on the next. Left to lemon-tls, each
|
|
57
|
+
// connection drew its own and no client ever resumed. Random when not given:
|
|
58
|
+
// tickets then last until this server restarts; pass ticketKeys (48 bytes)
|
|
59
|
+
// kept across restarts to have them outlive it.
|
|
60
|
+
var ticketKeys = options.ticketKeys || crypto.randomBytes(48);
|
|
61
|
+
// Encrypted Client Hello keys (lemon-tls), read per connection.
|
|
62
|
+
var ech = options.ech || null;
|
|
63
|
+
var ocspRequest = typeof options.ocspRequest === 'function' ? options.ocspRequest : null;
|
|
64
|
+
// node:tls's options, read once (tls_options.js).
|
|
65
|
+
var tlsOpts = serverTlsOptions(options, false);
|
|
66
|
+
|
|
67
|
+
var order = []; // registration order = ALPN preference order
|
|
68
|
+
var sockets = new Set();
|
|
69
|
+
var closing = false;
|
|
70
|
+
|
|
71
|
+
var tls = options.tls || null;
|
|
72
|
+
var cachedContext = null;
|
|
73
|
+
// The certificate's context made now, not at the first connection, and
|
|
74
|
+
// its chain compressed in the background (RFC 8879, lemon-tls
|
|
75
|
+
// precompressCertificate): the first client's handshake did both while it
|
|
76
|
+
// waited — brotli at quality 11 alone is 4–18 ms for a chain the first
|
|
77
|
+
// time. A bad key or certificate throws from createServer, as over QUIC.
|
|
78
|
+
if (tls && options.key && options.cert && !options.SNICallback && !options.secureContext) {
|
|
79
|
+
cachedContext = tls.createSecureContext({ key: options.key, cert: options.cert, ca: options.ca, passphrase: tlsOpts.passphrase });
|
|
80
|
+
if (typeof tls.precompressCertificate === 'function' && !(tlsOpts.certificateCompression && tlsOpts.certificateCompression.length === 0))
|
|
81
|
+
tls.precompressCertificate(cachedContext, tlsOpts.certificateCompression || undefined);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// TCP options, as node:http.createServer takes them and with its defaults:
|
|
85
|
+
// noDelay on (see handleConnection), keepAlive off.
|
|
86
|
+
// The server's counters (server_stats.js), when it keeps them.
|
|
87
|
+
var stats = options.stats || null;
|
|
88
|
+
var noDelay = options.noDelay !== false;
|
|
89
|
+
var keepAlive = options.keepAlive === true;
|
|
90
|
+
var keepAliveInitialDelay = options.keepAliveInitialDelay || 0;
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Register a protocol handler. Registration order is the server's ALPN
|
|
95
|
+
* preference: the first registered protocol wins when the client offers
|
|
96
|
+
* several, which is how a server expresses "h2 if you can, h1 if you must".
|
|
97
|
+
*/
|
|
98
|
+
//
|
|
99
|
+
// opts (both optional):
|
|
100
|
+
// sniff(bytes) Recognise this protocol from the first decrypted bytes of
|
|
101
|
+
// a TLS connection that negotiated NO ALPN. Return true
|
|
102
|
+
// (mine), false (not mine), or null (need more bytes).
|
|
103
|
+
// A browser connecting to turns:host:443 is the case: it
|
|
104
|
+
// offers no ALPN at all, while every HTTPS request from a
|
|
105
|
+
// browser offers h2 / http/1.1 — so this only ever runs for
|
|
106
|
+
// connections that would otherwise have been HTTP/1.1 by
|
|
107
|
+
// default, and costs HTTPS nothing.
|
|
108
|
+
// paused Hand the socket over paused, with any sniffed bytes put
|
|
109
|
+
// back, and let the handler resume it. For a handler that
|
|
110
|
+
// attaches its reader asynchronously — turn-server waits
|
|
111
|
+
// for its 'accept' hook — and would otherwise lose the
|
|
112
|
+
// first message to a stream already flowing.
|
|
113
|
+
function register(protocol, handler, opts) {
|
|
114
|
+
if (!handlers[protocol]) order.push(protocol);
|
|
115
|
+
handlers[protocol] = handler;
|
|
116
|
+
handlerOpts[protocol] = opts || {};
|
|
117
|
+
return listener;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
function dispatch(socket, protocol) {
|
|
122
|
+
if (closing) { try { socket.destroy(); } catch (e) {} return; }
|
|
123
|
+
|
|
124
|
+
var handler = handlers[protocol];
|
|
125
|
+
if (!handler) {
|
|
126
|
+
// Nothing registered for what was negotiated. Should be impossible —
|
|
127
|
+
// we only advertise what is registered — but a client can also connect
|
|
128
|
+
// with no ALPN at all, and then `protocol` is whatever the fallback
|
|
129
|
+
// decided.
|
|
130
|
+
ev.emit('clientError', new Error('no handler for protocol ' + JSON.stringify(protocol)), socket);
|
|
131
|
+
try { socket.destroy(); } catch (e) {}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (stats) stats.protocol(protocol);
|
|
135
|
+
|
|
136
|
+
sockets.add(socket);
|
|
137
|
+
socket.on('close', function () { sockets.delete(socket); });
|
|
138
|
+
if (handlerOpts[protocol] && handlerOpts[protocol].paused) socket.pause();
|
|
139
|
+
handler(socket, protocol);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Decide which protocol a TLS connection WITHOUT ALPN is speaking, from its
|
|
145
|
+
* first decrypted bytes — the encrypted counterpart of sniffCleartext.
|
|
146
|
+
*
|
|
147
|
+
* Only protocols registered with a sniff() take part. Bytes are gathered
|
|
148
|
+
* until each of them has answered, or a cap is reached; the first to claim
|
|
149
|
+
* the bytes wins, and if none does, the connection goes to `fallback`
|
|
150
|
+
* exactly as it would have before. Whatever was read is put back at the
|
|
151
|
+
* front of the stream, so the chosen handler sees the connection from its
|
|
152
|
+
* first byte.
|
|
153
|
+
*/
|
|
154
|
+
function sniffSecure(socket, fallback) {
|
|
155
|
+
var sniffers = [];
|
|
156
|
+
for (var i = 0; i < order.length; i++) {
|
|
157
|
+
var o = handlerOpts[order[i]];
|
|
158
|
+
if (o && typeof o.sniff === 'function') sniffers.push(order[i]);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
var seen = Buffer.alloc(0);
|
|
162
|
+
var decided = false;
|
|
163
|
+
var SNIFF_CAP = 64; // bytes: enough for any header-shaped test
|
|
164
|
+
var SNIFF_TIMEOUT = 10000; // a client that says nothing goes to the fallback
|
|
165
|
+
|
|
166
|
+
function decide(protocol) {
|
|
167
|
+
if (decided) return;
|
|
168
|
+
decided = true;
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
socket.removeListener('data', onData);
|
|
171
|
+
socket.removeListener('close', onClose);
|
|
172
|
+
// Paused before the bytes go back and the handler is chosen: nothing may
|
|
173
|
+
// flow until a reader exists. dispatch() leaves a 'paused' handler
|
|
174
|
+
// paused; any other is resumed below, as the connection would have been.
|
|
175
|
+
socket.pause();
|
|
176
|
+
if (seen.length) socket.unshift(seen);
|
|
177
|
+
dispatch(socket, protocol);
|
|
178
|
+
if (!(handlerOpts[protocol] && handlerOpts[protocol].paused) && !socket.destroyed) socket.resume();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function onData(chunk) {
|
|
182
|
+
seen = seen.length ? Buffer.concat([seen, chunk]) : Buffer.from(chunk);
|
|
183
|
+
var pending = false;
|
|
184
|
+
for (var k = 0; k < sniffers.length; k++) {
|
|
185
|
+
var r = null;
|
|
186
|
+
try { r = handlerOpts[sniffers[k]].sniff(seen); } catch (e) { r = false; }
|
|
187
|
+
if (r === true) { decide(sniffers[k]); return; }
|
|
188
|
+
if (r === null) pending = true;
|
|
189
|
+
}
|
|
190
|
+
if (!pending || seen.length >= SNIFF_CAP) decide(fallback);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function onClose() { decided = true; clearTimeout(timer); }
|
|
194
|
+
|
|
195
|
+
var timer = setTimeout(function () { decide(fallback); }, SNIFF_TIMEOUT);
|
|
196
|
+
if (timer.unref) timer.unref();
|
|
197
|
+
socket.on('data', onData);
|
|
198
|
+
socket.on('close', onClose);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Decide which protocol a CLEARTEXT connection is speaking.
|
|
204
|
+
*
|
|
205
|
+
* There is no ALPN without TLS, so the two cleartext protocols have to be
|
|
206
|
+
* told apart another way. RFC 9113 §3.3 calls this "prior knowledge": an h2c
|
|
207
|
+
* client simply opens with the connection preface, and the preface was
|
|
208
|
+
* designed to be something no HTTP/1.1 request can begin with. Peeking at
|
|
209
|
+
* the first bytes is therefore unambiguous — and the bytes are pushed back
|
|
210
|
+
* so the chosen layer sees the whole stream.
|
|
211
|
+
*/
|
|
212
|
+
function sniffCleartext(socket) {
|
|
213
|
+
if (order.length === 1) { dispatch(socket, order[0]); return; }
|
|
214
|
+
if (!handlers['h2'] && !handlers['h2c']) { dispatch(socket, order[0]); return; }
|
|
215
|
+
|
|
216
|
+
var seen = Buffer.alloc(0);
|
|
217
|
+
var decided = false;
|
|
218
|
+
|
|
219
|
+
function onData(chunk) {
|
|
220
|
+
if (decided) return;
|
|
221
|
+
seen = Buffer.concat([seen, chunk]);
|
|
222
|
+
|
|
223
|
+
var n = Math.min(seen.length, H2_PREFACE.length);
|
|
224
|
+
var matches = seen.subarray(0, n).equals(H2_PREFACE.subarray(0, n));
|
|
225
|
+
|
|
226
|
+
if (!matches) { decide(handlers['http/1.1'] ? 'http/1.1' : order[0]); return; }
|
|
227
|
+
if (seen.length >= H2_PREFACE.length) { decide(handlers['h2'] ? 'h2' : 'h2c'); }
|
|
228
|
+
// Otherwise a prefix of the preface so far: wait for more.
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function decide(protocol) {
|
|
232
|
+
decided = true;
|
|
233
|
+
socket.removeListener('data', onData);
|
|
234
|
+
socket.removeListener('error', onError);
|
|
235
|
+
socket.removeListener('close', onClose);
|
|
236
|
+
// Put the sniffed bytes back at the FRONT of the stream. unshift is the
|
|
237
|
+
// documented way to do this; without it the protocol layer never sees
|
|
238
|
+
// the request line (or the preface) it is waiting for and the connection
|
|
239
|
+
// hangs until a timeout — a failure that looks like a parser bug.
|
|
240
|
+
if (seen.length) socket.unshift(seen);
|
|
241
|
+
dispatch(socket, protocol);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function onError(err) { if (!decided) { decided = true; ev.emit('clientError', err, socket); } }
|
|
245
|
+
function onClose() { decided = true; }
|
|
246
|
+
|
|
247
|
+
socket.on('data', onData);
|
|
248
|
+
socket.on('error', onError);
|
|
249
|
+
socket.on('close', onClose);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Accept a socket the caller already has — a shared port, a test harness, or
|
|
255
|
+
* a listener owned elsewhere. The equivalent of quic_server's handlePacket.
|
|
256
|
+
*/
|
|
257
|
+
function handleConnection(socket) {
|
|
258
|
+
if (closing) { try { socket.destroy(); } catch (e) {} return; }
|
|
259
|
+
if (stats) stats.connections.tcp++;
|
|
260
|
+
|
|
261
|
+
// TCP_NODELAY, as node:http sets it on every socket it accepts. With
|
|
262
|
+
// Nagle's algorithm on, a write smaller than one segment waits while
|
|
263
|
+
// anything sent before it is unacknowledged — a full round trip. The TLS
|
|
264
|
+
// server flight is several such writes (ServerHello, then Certificate,
|
|
265
|
+
// CertificateVerify, Finished): the ServerHello went out, the rest sat
|
|
266
|
+
// until the client's ACK came back, and TLS 1.3's one-round-trip
|
|
267
|
+
// handshake took two. The same stall hits the tail of every response.
|
|
268
|
+
// Applied here rather than only on net.createServer, because sockets are
|
|
269
|
+
// also handed in from a listener owned elsewhere.
|
|
270
|
+
if (noDelay && typeof socket.setNoDelay === 'function') socket.setNoDelay(true);
|
|
271
|
+
if (keepAlive && typeof socket.setKeepAlive === 'function') socket.setKeepAlive(true, keepAliveInitialDelay);
|
|
272
|
+
|
|
273
|
+
// Emitted BEFORE the TLS handshake, so a consumer that screens by address
|
|
274
|
+
// (an IP blacklist, a rate limiter) can drop the connection without paying
|
|
275
|
+
// for a handshake first. node:net and node:http both call this event
|
|
276
|
+
// 'connection' and hand over the socket, so a screening function written
|
|
277
|
+
// against either works unchanged.
|
|
278
|
+
ev.emit('connection', socket);
|
|
279
|
+
if (socket.destroyed) return;
|
|
280
|
+
|
|
281
|
+
if (!tls) { sniffCleartext(socket); return; }
|
|
282
|
+
|
|
283
|
+
var tlsOpts = buildTlsOptions(socket);
|
|
284
|
+
var secure;
|
|
285
|
+
var settled = false;
|
|
286
|
+
|
|
287
|
+
// The error listener has to be in place BEFORE the handshake can produce
|
|
288
|
+
// one, and the constructor starts reading immediately — so it is attached
|
|
289
|
+
// to the underlying socket first and moved up as soon as the TLSSocket
|
|
290
|
+
// exists.
|
|
291
|
+
//
|
|
292
|
+
// The gap is not theoretical. A TLS handshake can fail asynchronously:
|
|
293
|
+
// an SNI lookup that answers "no certificate for that name" fires its
|
|
294
|
+
// alert from a callback, long after the constructor returned. With no
|
|
295
|
+
// listener at that moment the error is unhandled, and an unhandled error
|
|
296
|
+
// on an EventEmitter takes the process down — meaning any scanner that
|
|
297
|
+
// connects to the IP without SNI, which is a continuous background event
|
|
298
|
+
// on a public address, can stop the server.
|
|
299
|
+
// Reported as 'tlsClientError' (err, tlsSocket), node:tls's event for a
|
|
300
|
+
// handshake that failed; 'clientError' is the HTTP layer's, for a
|
|
301
|
+
// connection that is up. It was reported as 'clientError'.
|
|
302
|
+
function onHandshakeError(err) {
|
|
303
|
+
if (!settled) {
|
|
304
|
+
settled = true;
|
|
305
|
+
countHandshake(false);
|
|
306
|
+
ev.emit('tlsClientError', err, secure || socket);
|
|
307
|
+
}
|
|
308
|
+
// The alert that says why (no_application_protocol, bad_certificate,
|
|
309
|
+
// ...) is written to the socket just before this; destroy() threw it
|
|
310
|
+
// away with whatever else was still queued, and the client saw only a
|
|
311
|
+
// reset. The socket is ended instead, which sends what is queued, and
|
|
312
|
+
// destroyed if it has not closed shortly after.
|
|
313
|
+
if (socket.destroyed) return;
|
|
314
|
+
try { socket.end(); } catch (e) {}
|
|
315
|
+
var t = setTimeout(function () { try { socket.destroy(); } catch (e) {} }, 1000);
|
|
316
|
+
if (t.unref) t.unref();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
socket.on('error', onHandshakeError);
|
|
320
|
+
|
|
321
|
+
// The handshake's end, counted once (server_stats.js): completed, or
|
|
322
|
+
// failed — an error, or the client gone before it finished.
|
|
323
|
+
var hsCounted = false;
|
|
324
|
+
function countHandshake(ok, resumed) {
|
|
325
|
+
if (hsCounted || !stats) return;
|
|
326
|
+
hsCounted = true;
|
|
327
|
+
stats.handshake('tcp', ok, resumed);
|
|
328
|
+
}
|
|
329
|
+
if (stats) socket.on('close', function () { countHandshake(false); });
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
// The TCP socket underneath stays open for writing when the client
|
|
333
|
+
// half-closes: the TLS socket over it decides — it ends both with the
|
|
334
|
+
// client's (allowHalfOpen off, HTTP/1.1, as node:http), unless the
|
|
335
|
+
// protocol above keeps it open to finish its responses (h2_server).
|
|
336
|
+
socket.allowHalfOpen = true;
|
|
337
|
+
secure = new tls.TLSSocket(socket, tlsOpts);
|
|
338
|
+
} catch (err) {
|
|
339
|
+
onHandshakeError(err);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
secure.on('error', onHandshakeError);
|
|
344
|
+
|
|
345
|
+
// Early data taken: the protocol above gets the connection now, to read
|
|
346
|
+
// it as it comes (socket.isEarlyData) and answer as soon as the server's
|
|
347
|
+
// Finished is out — not one round trip later, at 'secure'. The ALPN is
|
|
348
|
+
// decided by then (the ticket's, or there is no early data).
|
|
349
|
+
var dispatchedEarly = false;
|
|
350
|
+
secure.on('earlyData', function (accepted) {
|
|
351
|
+
if (stats) stats.earlyData[accepted ? 'accepted' : 'rejected']++;
|
|
352
|
+
if (!accepted || settled || dispatchedEarly) return;
|
|
353
|
+
var p = secure.alpnProtocol;
|
|
354
|
+
if (!p || !handlers[p]) return;
|
|
355
|
+
dispatchedEarly = true;
|
|
356
|
+
dispatch(secure, p);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// 'secure' rather than 'secureConnect': lemon-tls documents that
|
|
360
|
+
// secureConnect is not emitted for a socket built with new TLSSocket(),
|
|
361
|
+
// matching Node, while 'secure' fires on both sides and in both
|
|
362
|
+
// construction paths. Listening for the wrong one is a server that accepts
|
|
363
|
+
// connections and then never does anything with them.
|
|
364
|
+
secure.on('secure', function () {
|
|
365
|
+
if (settled) return;
|
|
366
|
+
settled = true;
|
|
367
|
+
countHandshake(true, typeof secure.isSessionReused === 'function' && secure.isSessionReused());
|
|
368
|
+
|
|
369
|
+
// 'secureConnection': the handshake is done — the name and the moment
|
|
370
|
+
// node:tls uses. 'connection' fired before any TLS, knowing only an
|
|
371
|
+
// address; here the connection can be asked what the handshake decided
|
|
372
|
+
// (isSessionReused(), getTicketInfo()), which is what a caller needs to
|
|
373
|
+
// tell a returning client from a new one. A listener may destroy() it.
|
|
374
|
+
ev.emit('secureConnection', secure);
|
|
375
|
+
if (secure.destroyed || dispatchedEarly) return;
|
|
376
|
+
|
|
377
|
+
var protocol = secure.alpnProtocol;
|
|
378
|
+
if (!protocol) {
|
|
379
|
+
// The client offered nothing, or offered nothing we advertise. RFC
|
|
380
|
+
// 7301 lets the server proceed without ALPN, and HTTP/1.1 is the only
|
|
381
|
+
// safe assumption — an h2 client always offers 'h2'. Unless a
|
|
382
|
+
// registered protocol can tell itself apart by its first bytes: then
|
|
383
|
+
// those decide (see sniffSecure).
|
|
384
|
+
var fallback = handlers['http/1.1'] ? 'http/1.1' : order[0];
|
|
385
|
+
for (var sp = 0; sp < order.length; sp++) {
|
|
386
|
+
var so = handlerOpts[order[sp]];
|
|
387
|
+
if (so && typeof so.sniff === 'function') { sniffSecure(secure, fallback); return; }
|
|
388
|
+
}
|
|
389
|
+
protocol = fallback;
|
|
390
|
+
}
|
|
391
|
+
dispatch(secure, protocol);
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Who this connection is, for an SNICallback that wants to know.
|
|
398
|
+
*
|
|
399
|
+
* A certificate can only be chosen by name when the client sends one, and
|
|
400
|
+
* a client connecting to a bare IP address never does — RFC 6066 §3 forbids
|
|
401
|
+
* an IP literal in SNI, so browsers leave the extension out entirely. The
|
|
402
|
+
* callback then sees an empty servername and nothing else, with no way to
|
|
403
|
+
* tell which of the server's addresses was dialled. That is the one thing
|
|
404
|
+
* it needs to serve a certificate issued for an IP.
|
|
405
|
+
*
|
|
406
|
+
* The raw socket knows. A dual-stack listener reports an IPv4 peer as
|
|
407
|
+
* ::ffff:a.b.c.d on an IPv6 socket; that is unwrapped here so the family
|
|
408
|
+
* reported is the one the client actually used.
|
|
409
|
+
*/
|
|
410
|
+
function connectionInfo(socket) {
|
|
411
|
+
var local = socket.localAddress || null;
|
|
412
|
+
var family = socket.localFamily || (local && net.isIPv6(local) ? 'IPv6' : 'IPv4');
|
|
413
|
+
if (local && local.indexOf('::ffff:') === 0 && net.isIPv4(local.substring(7))) {
|
|
414
|
+
local = local.substring(7);
|
|
415
|
+
family = 'IPv4';
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
transport: 'tcp',
|
|
419
|
+
localAddress: local,
|
|
420
|
+
localPort: socket.localPort || null,
|
|
421
|
+
family: family,
|
|
422
|
+
remoteAddress: socket.remoteAddress || null,
|
|
423
|
+
remotePort: socket.remotePort || null
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
function buildTlsOptions(socket) {
|
|
429
|
+
var o = {
|
|
430
|
+
isServer: true,
|
|
431
|
+
// Advertised in registration order, which is the server's preference.
|
|
432
|
+
ALPNProtocols: order.slice()
|
|
433
|
+
};
|
|
434
|
+
if (options.ca) o.ca = options.ca;
|
|
435
|
+
|
|
436
|
+
// Credentials reach a server-side TLSSocket through SNICallback.
|
|
437
|
+
//
|
|
438
|
+
// Passing key/cert (or secureContext) straight through is what node:tls
|
|
439
|
+
// documents and what this did first — and the handshake simply never
|
|
440
|
+
// completed, with no error on either side. lemon-tls resolves a server
|
|
441
|
+
// certificate through its SNI path, so a plain key/cert pair has to be
|
|
442
|
+
// wrapped in a callback that hands back the same context every time. The
|
|
443
|
+
// context is built ONCE here rather than per connection: compiling the PEM
|
|
444
|
+
// on the handshake path is measurable, and the hand-written SNICallback
|
|
445
|
+
// most servers end up with usually gets that wrong.
|
|
446
|
+
if (options.SNICallback) {
|
|
447
|
+
// Bound per connection, with the connection's details as a third
|
|
448
|
+
// argument. Third, after cb, so a callback written against the
|
|
449
|
+
// (servername, cb) signature node:tls documents keeps working
|
|
450
|
+
// unchanged — it just never reads it.
|
|
451
|
+
var userSni = options.SNICallback;
|
|
452
|
+
var info = socket ? connectionInfo(socket) : null;
|
|
453
|
+
o.SNICallback = function (servername, cb) { return userSni(servername, cb, info); };
|
|
454
|
+
} else if (options.secureContext) {
|
|
455
|
+
var fixed = options.secureContext;
|
|
456
|
+
o.SNICallback = function (servername, cb) { cb(null, fixed); };
|
|
457
|
+
} else if (options.key && options.cert) {
|
|
458
|
+
if (!cachedContext) {
|
|
459
|
+
cachedContext = tls.createSecureContext({
|
|
460
|
+
key: options.key, cert: options.cert, ca: options.ca, passphrase: tlsOpts.passphrase
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
var ctx = cachedContext;
|
|
464
|
+
o.SNICallback = function (servername, cb) { cb(null, ctx); };
|
|
465
|
+
}
|
|
466
|
+
o.ticketKeys = ticketKeys;
|
|
467
|
+
if (options.ticketData) o.ticketData = options.ticketData;
|
|
468
|
+
if (ech) o.ech = ech;
|
|
469
|
+
if (ocspRequest) o.ocspRequest = ocspRequest;
|
|
470
|
+
if (tlsOpts.sessionTimeout !== undefined) o.ticketLifetime = tlsOpts.sessionTimeout;
|
|
471
|
+
if (tlsOpts.requestCert) {
|
|
472
|
+
o.requestCert = true;
|
|
473
|
+
o.rejectUnauthorized = tlsOpts.rejectUnauthorized;
|
|
474
|
+
}
|
|
475
|
+
if (tlsOpts.minVersion) o.minVersion = tlsOpts.minVersion;
|
|
476
|
+
if (tlsOpts.maxVersion) o.maxVersion = tlsOpts.maxVersion;
|
|
477
|
+
// node:tls's default, 120 s: lemon-tls's own is none, and a client that
|
|
478
|
+
// sent half a ClientHello held its socket for as long as it liked.
|
|
479
|
+
o.handshakeTimeout = tlsOpts.handshakeTimeout !== undefined ? tlsOpts.handshakeTimeout : 120000;
|
|
480
|
+
if (tlsOpts.cipherSuites) o.allowedCipherSuites = tlsOpts.cipherSuites;
|
|
481
|
+
if (tlsOpts.groups) o.groups = tlsOpts.groups;
|
|
482
|
+
if (tlsOpts.sigalgs) o.signatureAlgorithms = tlsOpts.sigalgs;
|
|
483
|
+
if (tlsOpts.certificateCompression) o.certificateCompression = tlsOpts.certificateCompression;
|
|
484
|
+
if (tlsOpts.honorCipherOrder) o.honorCipherOrder = true;
|
|
485
|
+
// 0-RTT (RFC 8446 §4.2.10), as the server's options say now (they change
|
|
486
|
+
// while it runs): tickets that allow it, and taking it — under its own
|
|
487
|
+
// context, so a ticket of the QUIC side's carries none here.
|
|
488
|
+
var ed = typeof options.earlyDataNow === 'function' ? options.earlyDataNow() : null;
|
|
489
|
+
if (ed && ed.on) {
|
|
490
|
+
o.earlyData = true;
|
|
491
|
+
o.earlyDataMaxAge = ed.maxAge;
|
|
492
|
+
o.earlyDataReplay = options.earlyDataReplay;
|
|
493
|
+
o.earlyDataContext = 'tcp';
|
|
494
|
+
}
|
|
495
|
+
return o;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
function listen(port, host, callback) {
|
|
500
|
+
if (typeof host === 'function') { callback = host; host = undefined; }
|
|
501
|
+
if (order.length === 0) throw new Error('no protocol handlers registered');
|
|
502
|
+
|
|
503
|
+
server = net.createServer({ allowHalfOpen: false }, handleConnection);
|
|
504
|
+
server.on('error', function (err) { ev.emit('error', err); });
|
|
505
|
+
server.listen(port, host, function () {
|
|
506
|
+
ev.emit('listening');
|
|
507
|
+
if (typeof callback === 'function') callback();
|
|
508
|
+
});
|
|
509
|
+
return listener;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
function close(callback) {
|
|
514
|
+
closing = true;
|
|
515
|
+
|
|
516
|
+
// Idle sockets would hold the close open for their full keep-alive
|
|
517
|
+
// timeout. Protocol layers that have work in flight mark the socket busy;
|
|
518
|
+
// theirs are left to finish.
|
|
519
|
+
//
|
|
520
|
+
// Done whether or not this listener owns a port. With an external owner
|
|
521
|
+
// — sockets handed in through handleConnection — there is no net.Server
|
|
522
|
+
// here to close, and returning early (as this did) left every one of
|
|
523
|
+
// those connections open after shutdown.
|
|
524
|
+
for (var socket of sockets) {
|
|
525
|
+
if (!socket._quicoBusy) { try { socket.end(); } catch (e) {} }
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (!server) { if (callback) callback(); return; }
|
|
529
|
+
server.close(function () { if (typeof callback === 'function') callback(); });
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
var listener = {
|
|
534
|
+
register: register,
|
|
535
|
+
listen: listen,
|
|
536
|
+
close: close,
|
|
537
|
+
handleConnection: handleConnection,
|
|
538
|
+
address: function () { return server && server.address ? server.address() : null; },
|
|
539
|
+
on: function (n, f) { ev.on(n, f); return listener; },
|
|
540
|
+
off: function (n, f) { ev.off(n, f); return listener; },
|
|
541
|
+
get protocols() { return order.slice(); },
|
|
542
|
+
get connections() { return sockets.size; },
|
|
543
|
+
|
|
544
|
+
// The session ticket keys — tls.Server's names. A new key takes effect
|
|
545
|
+
// for the next connection; tickets sealed with the one before no longer
|
|
546
|
+
// open (those clients do a full handshake once, and get a new ticket).
|
|
547
|
+
getTicketKeys: function () { return ticketKeys; },
|
|
548
|
+
setTicketKeys: function (keys) { ticketKeys = keys; },
|
|
549
|
+
getEchKeys: function () { return ech; },
|
|
550
|
+
setEchKeys: function (keys) { ech = keys || null; }
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
return listener;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
export { createTcpListener, H2_PREFACE };
|