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 4ade02a

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 b2e2e64 commit 4ade02a
Copy full SHA for 4ade02a

18 files changed

+285-217Lines changed: 285 additions & 217 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
+33-14Lines changed: 33 additions & 14 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.
@@ -118,7 +117,6 @@ const {
118117
const kEmptyObject = { __proto__: null };
119118

120119
const {
121-
kApplicationProvider,
122120
kBlocked,
123121
kConnect,
124122
kDatagram,
@@ -2222,7 +2220,7 @@ function processIdentityOptions(identity, label) {
22222220
function processTlsOptions(tls, forServer) {
22232221
const {
22242222
servername,
2225-
protocol,
2223+
alpn,
22262224
ciphers = DEFAULT_CIPHERS,
22272225
groups = DEFAULT_GROUPS,
22282226
keylog = false,
@@ -2240,9 +2238,6 @@ function processTlsOptions(tls, forServer) {
22402238
if (servername !== undefined) {
22412239
validateString(servername, 'options.servername');
22422240
}
2243-
if (protocol !== undefined) {
2244-
validateString(protocol, 'options.protocol');
2245-
}
22462241
if (ciphers !== undefined) {
22472242
validateString(ciphers, 'options.ciphers');
22482243
}
@@ -2253,11 +2248,42 @@ function processTlsOptions(tls, forServer) {
22532248
validateBoolean(verifyClient, 'options.verifyClient');
22542249
validateBoolean(tlsTrace, 'options.tlsTrace');
22552250

2251+
// Encode the ALPN option to wire format (length-prefixed protocol names).
2252+
// Server: array of protocol names. Client: single protocol name.
2253+
// If not specified, the C++ default (h3) is used.
2254+
let encodedAlpn;
2255+
if (alpn !== undefined) {
2256+
const protocols = forServer ?
2257+
(ArrayIsArray(alpn) ? alpn : [alpn]) :
2258+
[alpn];
2259+
if (!forServer) {
2260+
validateString(alpn, 'options.alpn');
2261+
}
2262+
let totalLen = 0;
2263+
for (let i = 0; i < protocols.length; i++) {
2264+
validateString(protocols[i], `options.alpn[${i}]`);
2265+
if (protocols[i].length === 0 || protocols[i].length > 255) {
2266+
throw new ERR_INVALID_ARG_VALUE(`options.alpn[${i}]`, protocols[i],
2267+
'must be between 1 and 255 characters');
2268+
}
2269+
totalLen += 1 + protocols[i].length;
2270+
}
2271+
// Build wire format: [len1][name1][len2][name2]...
2272+
const buf = Buffer.allocUnsafe(totalLen);
2273+
let offset = 0;
2274+
for (let i = 0; i < protocols.length; i++) {
2275+
buf[offset++] = protocols[i].length;
2276+
buf.write(protocols[i], offset, 'ascii');
2277+
offset += protocols[i].length;
2278+
}
2279+
encodedAlpn = buf.toString('latin1');
2280+
}
2281+
22562282
// Shared TLS options (same for all identities on the endpoint).
22572283
const shared = {
22582284
__proto__: null,
22592285
servername,
2260-
protocol,
2286+
alpn: encodedAlpn,
22612287
ciphers,
22622288
groups,
22632289
keylog,
@@ -2360,17 +2386,12 @@ function processSessionOptions(options, config = {}) {
23602386
maxStreamWindow,
23612387
maxWindow,
23622388
cc,
2363-
[kApplicationProvider]: provider,
23642389
} = options;
23652390

23662391
const {
23672392
forServer = false,
23682393
} = config;
23692394

2370-
if (provider !== undefined) {
2371-
validateObject(provider, 'options[kApplicationProvider]');
2372-
}
2373-
23742395
if (cc !== undefined) {
23752396
validateOneOf(cc, 'options.cc', [CC_ALGO_RENO, CC_ALGO_BBR, CC_ALGO_CUBIC]);
23762397
}
@@ -2392,7 +2413,6 @@ function processSessionOptions(options, config = {}) {
23922413
maxStreamWindow,
23932414
maxWindow,
23942415
sessionTicket,
2395-
provider,
23962416
cc,
23972417
};
23982418
}
@@ -2494,7 +2514,6 @@ module.exports = {
24942514
QuicEndpoint,
24952515
QuicSession,
24962516
QuicStream,
2497-
Http3,
24982517
CC_ALGO_RENO,
24992518
CC_ALGO_CUBIC,
25002519
CC_ALGO_BBR,
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');
@@ -50,7 +49,6 @@ const kWantsHeaders = Symbol('kWantsHeaders');
5049
const kWantsTrailers = Symbol('kWantsTrailers');
5150

5251
module.exports = {
53-
kApplicationProvider,
5452
kBlocked,
5553
kConnect,
5654
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.