Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit abb881e

Browse filesBrowse files
jasnelladuh95
authored andcommitted
quic: support multiple ALPN negotiation
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode:Opus 4.6 PR-URL: #62620 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Tim Perry <pimterry@gmail.com>
1 parent 476926c commit abb881e
Copy full SHA for abb881e

17 files changed

+274-210Lines changed: 274 additions & 210 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎doc/api/quic.md‎

Copy file name to clipboardExpand all lines: doc/api/quic.md
+17-3Lines changed: 17 additions & 3 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1141,12 +1141,26 @@ added: v23.8.0
11411141
#### `sessionOptions.alpn`
11421142

11431143
<!-- YAML
1144-
added: v23.8.0
1144+
added: REPLACEME
11451145
-->
11461146

1147-
* Type: {string}
1147+
* Type: {string} (client) | {string\[]} (server)
1148+
1149+
The ALPN (Application-Layer Protocol Negotiation) identifier(s).
1150+
1151+
For **client** sessions, this is a single string specifying the protocol
1152+
the client wants to use (e.g. `'h3'`).
1153+
1154+
For **server** sessions, this is an array of protocol names in preference
1155+
order that the server supports (e.g. `['h3', 'h3-29']`). During the TLS
1156+
handshake, the server selects the first protocol from its list that the
1157+
client also supports.
1158+
1159+
The negotiated ALPN determines which Application implementation is used
1160+
for the session. `'h3'` and `'h3-*'` variants select the HTTP/3
1161+
application; all other values select the default application.
11481162

1149-
The ALPN protocol identifier.
1163+
Default: `'h3'`
11501164

11511165
#### `sessionOptions.ca` (client only)
11521166

Collapse file

‎lib/internal/quic/quic.js‎

Copy file name to clipboardExpand all lines: lib/internal/quic/quic.js
+35-15Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ let debug = require('internal/util/debuglog').debuglog('quic', (fn) => {
3333

3434
const {
3535
Endpoint: Endpoint_,
36-
Http3Application: Http3,
3736
setCallbacks,
3837

3938
// The constants to be exposed to end users for various options.
@@ -115,7 +114,6 @@ const {
115114
const kEmptyObject = { __proto__: null };
116115

117116
const {
118-
kApplicationProvider,
119117
kBlocked,
120118
kConnect,
121119
kDatagram,
@@ -1787,7 +1785,6 @@ class QuicEndpoint {
17871785
* @param {{replace?: boolean}} [options]
17881786
*/
17891787
setSNIContexts(entries, options = kEmptyObject) {
1790-
QuicEndpoint.#assertIsQuicEndpoint(this);
17911788
if (this.#handle === undefined) {
17921789
throw new ERR_INVALID_STATE('Endpoint is destroyed');
17931790
}
@@ -2053,7 +2050,7 @@ function processIdentityOptions(identity, label) {
20532050
function processTlsOptions(tls, forServer) {
20542051
const {
20552052
servername,
2056-
protocol,
2053+
alpn,
20572054
ciphers = DEFAULT_CIPHERS,
20582055
groups = DEFAULT_GROUPS,
20592056
keylog = false,
@@ -2071,9 +2068,6 @@ function processTlsOptions(tls, forServer) {
20712068
if (servername !== undefined) {
20722069
validateString(servername, 'options.servername');
20732070
}
2074-
if (protocol !== undefined) {
2075-
validateString(protocol, 'options.protocol');
2076-
}
20772071
if (ciphers !== undefined) {
20782072
validateString(ciphers, 'options.ciphers');
20792073
}
@@ -2084,11 +2078,42 @@ function processTlsOptions(tls, forServer) {
20842078
validateBoolean(verifyClient, 'options.verifyClient');
20852079
validateBoolean(tlsTrace, 'options.tlsTrace');
20862080

2081+
// Encode the ALPN option to wire format (length-prefixed protocol names).
2082+
// Server: array of protocol names. Client: single protocol name.
2083+
// If not specified, the C++ default (h3) is used.
2084+
let encodedAlpn;
2085+
if (alpn !== undefined) {
2086+
const protocols = forServer ?
2087+
(ArrayIsArray(alpn) ? alpn : [alpn]) :
2088+
[alpn];
2089+
if (!forServer) {
2090+
validateString(alpn, 'options.alpn');
2091+
}
2092+
let totalLen = 0;
2093+
for (let i = 0; i < protocols.length; i++) {
2094+
validateString(protocols[i], `options.alpn[${i}]`);
2095+
if (protocols[i].length === 0 || protocols[i].length > 255) {
2096+
throw new ERR_INVALID_ARG_VALUE(`options.alpn[${i}]`, protocols[i],
2097+
'must be between 1 and 255 characters');
2098+
}
2099+
totalLen += 1 + protocols[i].length;
2100+
}
2101+
// Build wire format: [len1][name1][len2][name2]...
2102+
const buf = Buffer.allocUnsafe(totalLen);
2103+
let offset = 0;
2104+
for (let i = 0; i < protocols.length; i++) {
2105+
buf[offset++] = protocols[i].length;
2106+
buf.write(protocols[i], offset, 'ascii');
2107+
offset += protocols[i].length;
2108+
}
2109+
encodedAlpn = buf.toString('latin1');
2110+
}
2111+
20872112
// Shared TLS options (same for all identities on the endpoint).
20882113
const shared = {
20892114
__proto__: null,
20902115
servername,
2091-
protocol,
2116+
alpn: encodedAlpn,
20922117
ciphers,
20932118
groups,
20942119
keylog,
@@ -2191,13 +2216,8 @@ function processSessionOptions(options, forServer = false) {
21912216
maxStreamWindow,
21922217
maxWindow,
21932218
cc,
2194-
[kApplicationProvider]: provider,
21952219
} = options;
21962220

2197-
if (provider !== undefined) {
2198-
validateObject(provider, 'options[kApplicationProvider]');
2199-
}
2200-
22012221
if (cc !== undefined) {
22022222
validateString(cc, 'options.cc');
22032223
if (cc !== 'reno' || cc !== 'bbr' || cc !== 'cubic') {
@@ -2226,7 +2246,6 @@ function processSessionOptions(options, forServer = false) {
22262246
maxStreamWindow,
22272247
maxWindow,
22282248
sessionTicket,
2229-
provider,
22302249
cc,
22312250
};
22322251
}
@@ -2328,7 +2347,8 @@ module.exports = {
23282347
QuicEndpoint,
23292348
QuicSession,
23302349
QuicStream,
2331-
Http3,
2350+
DEFAULT_CIPHERS,
2351+
DEFAULT_GROUPS,
23322352
};
23332353

23342354
ObjectDefineProperties(module.exports, {
Collapse file

‎lib/internal/quic/state.js‎

Copy file name to clipboardExpand all lines: lib/internal/quic/state.js
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const {
6262
IDX_STATE_SESSION_STREAM_OPEN_ALLOWED,
6363
IDX_STATE_SESSION_PRIORITY_SUPPORTED,
6464
IDX_STATE_SESSION_WRAPPED,
65+
IDX_STATE_SESSION_APPLICATION_TYPE,
6566
IDX_STATE_SESSION_LAST_DATAGRAM_ID,
6667

6768
IDX_STATE_ENDPOINT_BOUND,
@@ -99,6 +100,7 @@ assert(IDX_STATE_SESSION_HANDSHAKE_CONFIRMED !== undefined);
99100
assert(IDX_STATE_SESSION_STREAM_OPEN_ALLOWED !== undefined);
100101
assert(IDX_STATE_SESSION_PRIORITY_SUPPORTED !== undefined);
101102
assert(IDX_STATE_SESSION_WRAPPED !== undefined);
103+
assert(IDX_STATE_SESSION_APPLICATION_TYPE !== undefined);
102104
assert(IDX_STATE_SESSION_LAST_DATAGRAM_ID !== undefined);
103105
assert(IDX_STATE_ENDPOINT_BOUND !== undefined);
104106
assert(IDX_STATE_ENDPOINT_RECEIVING !== undefined);
@@ -347,6 +349,12 @@ class QuicSessionState {
347349
return !!DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_WRAPPED);
348350
}
349351

352+
/** @type {number} */
353+
get applicationType() {
354+
if (this.#handle.byteLength === 0) return undefined;
355+
return DataViewPrototypeGetUint8(this.#handle, IDX_STATE_SESSION_APPLICATION_TYPE);
356+
}
357+
350358
/** @type {bigint} */
351359
get lastDatagramId() {
352360
if (this.#handle.byteLength === 0) return undefined;
@@ -407,6 +415,7 @@ class QuicSessionState {
407415
isStreamOpenAllowed: this.isStreamOpenAllowed,
408416
isPrioritySupported: this.isPrioritySupported,
409417
isWrapped: this.isWrapped,
418+
applicationType: this.applicationType,
410419
lastDatagramId: this.lastDatagramId,
411420
}, opts)}`;
412421
}
Collapse file

‎lib/internal/quic/symbols.js‎

Copy file name to clipboardExpand all lines: lib/internal/quic/symbols.js
-2Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ const {
2323
// Symbols used to hide various private properties and methods from the
2424
// public API.
2525

26-
const kApplicationProvider = Symbol('kApplicationProvider');
2726
const kBlocked = Symbol('kBlocked');
2827
const kConnect = Symbol('kConnect');
2928
const kDatagram = Symbol('kDatagram');
@@ -51,7 +50,6 @@ const kWantsHeaders = Symbol('kWantsHeaders');
5150
const kWantsTrailers = Symbol('kWantsTrailers');
5251

5352
module.exports = {
54-
kApplicationProvider,
5553
kBlocked,
5654
kConnect,
5755
kDatagram,
Collapse file

‎src/quic/application.cc‎

Copy file name to clipboardExpand all lines: src/quic/application.cc
+7-8Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,10 @@ class DefaultApplication final : public Session::Application {
464464
// of the namespace.
465465
using Application::Application; // NOLINT
466466

467+
Session::Application::Type type() const override {
468+
return Session::Application::Type::DEFAULT;
469+
}
470+
467471
error_code GetNoErrorCode() const override { return 0; }
468472

469473
bool ReceiveStreamData(int64_t stream_id,
@@ -601,14 +605,9 @@ class DefaultApplication final : public Session::Application {
601605
Stream::Queue stream_queue_;
602606
};
603607

604-
std::unique_ptr<Session::Application> Session::SelectApplication(
605-
Session* session, const Config& config) {
606-
if (config.options.application_provider) {
607-
return config.options.application_provider->Create(session);
608-
}
609-
610-
return std::make_unique<DefaultApplication>(session,
611-
Application_Options::kDefault);
608+
std::unique_ptr<Session::Application> CreateDefaultApplication(
609+
Session* session, const Session::Application_Options& options) {
610+
return std::make_unique<DefaultApplication>(session, options);
612611
}
613612

614613
} // namespace quic
Collapse file

‎src/quic/application.h‎

Copy file name to clipboardExpand all lines: src/quic/application.h
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,19 @@ class Session::Application : public MemoryRetainer {
1717
public:
1818
using Options = Session::Application_Options;
1919

20+
// The type of Application, exposed via the session state so JS
21+
// can observe which Application was selected after ALPN negotiation.
22+
enum class Type : uint8_t {
23+
NONE = 0, // Not yet selected (server pre-negotiation)
24+
DEFAULT = 1, // DefaultApplication (non-h3 ALPN)
25+
HTTP3 = 2, // Http3ApplicationImpl (h3 / h3-XX ALPN)
26+
};
27+
2028
Application(Session* session, const Options& options);
2129
DISALLOW_COPY_AND_MOVE(Application)
2230

31+
virtual Type type() const = 0;
32+
2333
virtual bool Start();
2434

2535
virtual error_code GetNoErrorCode() const = 0;
@@ -169,6 +179,10 @@ struct Session::Application::StreamData final {
169179
std::string ToString() const;
170180
};
171181

182+
// Create a DefaultApplication for the given session.
183+
std::unique_ptr<Session::Application> CreateDefaultApplication(
184+
Session* session, const Session::Application_Options& options);
185+
172186
} // namespace node::quic
173187

174188
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
Collapse file

‎src/quic/bindingdata.h‎

Copy file name to clipboardExpand all lines: src/quic/bindingdata.h
+2-4Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ class Packet;
2424
// The FunctionTemplates the BindingData will store for us.
2525
#define QUIC_CONSTRUCTORS(V) \
2626
V(endpoint) \
27-
V(http3application) \
2827
V(logstream) \
2928
V(session) \
3029
V(stream) \
@@ -59,7 +58,7 @@ class Packet;
5958
V(ack_delay_exponent, "ackDelayExponent") \
6059
V(active_connection_id_limit, "activeConnectionIDLimit") \
6160
V(address_lru_size, "addressLRUSize") \
62-
V(application_provider, "provider") \
61+
V(application, "application") \
6362
V(bbr, "bbr") \
6463
V(ca, "ca") \
6564
V(cc_algorithm, "cc") \
@@ -78,7 +77,6 @@ class Packet;
7877
V(groups, "groups") \
7978
V(handshake_timeout, "handshakeTimeout") \
8079
V(http3_alpn, &NGHTTP3_ALPN_H3[1]) \
81-
V(http3application, "Http3Application") \
8280
V(initial_max_data, "initialMaxData") \
8381
V(initial_max_stream_data_bidi_local, "initialMaxStreamDataBidiLocal") \
8482
V(initial_max_stream_data_bidi_remote, "initialMaxStreamDataBidiRemote") \
@@ -105,7 +103,7 @@ class Packet;
105103
V(max_window, "maxWindow") \
106104
V(min_version, "minVersion") \
107105
V(preferred_address_strategy, "preferredAddressPolicy") \
108-
V(protocol, "protocol") \
106+
V(alpn, "alpn") \
109107
V(qlog, "qlog") \
110108
V(qpack_blocked_streams, "qpackBlockedStreams") \
111109
V(qpack_encoder_max_dtable_capacity, "qpackEncoderMaxDTableCapacity") \
Collapse file

‎src/quic/endpoint.cc‎

Copy file name to clipboardExpand all lines: src/quic/endpoint.cc
+3-6Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ bool is_diagnostic_packet_loss(double probability) {
9292
return (static_cast<double>(c) / 255) < probability;
9393
}
9494

95-
template <typename Opt, double Opt::*member>
95+
template <typename Opt, double Opt::* member>
9696
bool SetOption(Environment* env,
9797
Opt* options,
9898
const Local<Object>& object,
@@ -113,7 +113,7 @@ bool SetOption(Environment* env,
113113
}
114114
#endif // DEBUG
115115

116-
template <typename Opt, uint8_t Opt::*member>
116+
template <typename Opt, uint8_t Opt::* member>
117117
bool SetOption(Environment* env,
118118
Opt* options,
119119
const Local<Object>& object,
@@ -140,7 +140,7 @@ bool SetOption(Environment* env,
140140
return true;
141141
}
142142

143-
template <typename Opt, TokenSecret Opt::*member>
143+
template <typename Opt, TokenSecret Opt::* member>
144144
bool SetOption(Environment* env,
145145
Opt* options,
146146
const Local<Object>& object,
@@ -511,7 +511,6 @@ JS_CONSTRUCTOR_IMPL(Endpoint, endpoint_constructor_template, {
511511

512512
void Endpoint::InitPerIsolate(IsolateData* data, Local<ObjectTemplate> target) {
513513
// TODO(@jasnell): Implement the per-isolate state
514-
Http3Application::InitPerIsolate(data, target);
515514
}
516515

517516
void Endpoint::InitPerContext(Realm* realm, Local<Object> target) {
@@ -567,8 +566,6 @@ void Endpoint::InitPerContext(Realm* realm, Local<Object> target) {
567566
NODE_DEFINE_CONSTANT(target, CLOSECONTEXT_SEND_FAILURE);
568567
NODE_DEFINE_CONSTANT(target, CLOSECONTEXT_START_FAILURE);
569568

570-
Http3Application::InitPerContext(realm, target);
571-
572569
SetConstructorFunction(realm->context(),
573570
target,
574571
"Endpoint",

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.