Skip to content

Commit c20aa4c

Browse files
jasnellsxa
authored andcommitted
quic: add reusePort option to QuicEndpoint
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode:Opus 4.6 PR-URL: #63267 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 26a30d8 commit c20aa4c

5 files changed

Lines changed: 30 additions & 1 deletion

File tree

doc/api/quic.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,23 @@ added: v23.8.0
10231023

10241024
When `true`, indicates that the endpoint should bind only to IPv6 addresses.
10251025

1026+
#### `endpointOptions.reusePort`
1027+
1028+
<!-- YAML
1029+
added: REPLACEME
1030+
-->
1031+
1032+
* Type: {boolean}
1033+
* Default: `false`
1034+
1035+
When `true`, allows multiple endpoints (across separate processes) to bind to
1036+
the same address and port. The kernel will load-balance incoming UDP datagrams
1037+
across all sockets bound with this option. This enables horizontal scaling of
1038+
QUIC servers by running multiple Node.js processes on the same port.
1039+
1040+
Supported on Linux 3.9+ and DragonFlyBSD 3.6+. On unsupported platforms, the
1041+
bind will fail with an error.
1042+
10261043
#### `endpointOptions.maxConnectionsPerHost`
10271044

10281045
<!-- YAML

lib/internal/quic/quic.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ const onSessionHandshakeChannel = dc.channel('quic.session.handshake');
195195
* @property {string|SocketAddress} [address] The local address to bind to
196196
* @property {bigint|number} [addressLRUSize] The size of the address LRU cache
197197
* @property {boolean} [ipv6Only] Use IPv6 only
198+
* @property {boolean} [reusePort] Enable SO_REUSEPORT for multi-process load balancing
198199
* @property {bigint|number} [maxConnectionsPerHost] The maximum number of connections per host
199200
* @property {bigint|number} [maxConnectionsTotal] The maximum number of total connections
200201
* @property {bigint|number} [maxRetries] The maximum number of retries
@@ -1559,6 +1560,7 @@ class QuicEndpoint {
15591560
udpTTL,
15601561
validateAddress,
15611562
ipv6Only,
1563+
reusePort,
15621564
cc,
15631565
resetTokenSecret,
15641566
tokenSecret,
@@ -1592,6 +1594,7 @@ class QuicEndpoint {
15921594
udpTTL,
15931595
validateAddress,
15941596
ipv6Only,
1597+
reusePort,
15951598
cc,
15961599
resetTokenSecret,
15971600
tokenSecret,

src/quic/bindingdata.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class Packet;
8484
V(initial_max_streams_bidi, "initialMaxStreamsBidi") \
8585
V(initial_max_streams_uni, "initialMaxStreamsUni") \
8686
V(ipv6_only, "ipv6Only") \
87+
V(reuse_port, "reusePort") \
8788
V(keylog, "keylog") \
8889
V(keys, "keys") \
8990
V(logstream, "LogStream") \

src/quic/endpoint.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ Maybe<Endpoint::Options> Endpoint::Options::From(Environment* env,
191191
!SET(max_connections_per_host) || !SET(max_connections_total) ||
192192
!SET(max_stateless_resets) || !SET(address_lru_size) ||
193193
!SET(max_retries) || !SET(validate_address) ||
194-
!SET(disable_stateless_reset) || !SET(ipv6_only) ||
194+
!SET(disable_stateless_reset) || !SET(ipv6_only) || !SET(reuse_port) ||
195195
#ifdef DEBUG
196196
!SET(rx_loss) || !SET(tx_loss) ||
197197
#endif
@@ -262,6 +262,7 @@ std::string Endpoint::Options::ToString() const {
262262
res += prefix + "reset token secret: " + reset_token_secret.ToString();
263263
res += prefix + "token secret: " + token_secret.ToString();
264264
res += prefix + "ipv6 only: " + boolToString(ipv6_only);
265+
res += prefix + "reuse port: " + boolToString(reuse_port);
265266
res += prefix +
266267
"udp receive buffer size: " + std::to_string(udp_receive_buffer_size);
267268
res +=
@@ -376,6 +377,7 @@ int Endpoint::UDP::Bind(const Options& options) {
376377
int flags = 0;
377378
if (options.local_address->family() == AF_INET6 && options.ipv6_only)
378379
flags |= UV_UDP_IPV6ONLY;
380+
if (options.reuse_port) flags |= UV_UDP_REUSEPORT;
379381
int err = uv_udp_bind(&impl_->handle_, options.local_address->data(), flags);
380382
int size;
381383

src/quic/endpoint.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
140140
// flag on the underlying uv_udp_t.
141141
bool ipv6_only = false;
142142

143+
// When true, multiple endpoints (across separate processes) can bind to
144+
// the same address:port and the kernel will load-balance incoming UDP
145+
// datagrams across them. This sets the UV_UDP_REUSEPORT flag on the
146+
// underlying uv_udp_t. Supported on Linux 3.9+ and DragonFlyBSD 3.6+.
147+
bool reuse_port = false;
148+
143149
uint32_t udp_receive_buffer_size = 0;
144150
uint32_t udp_send_buffer_size = 0;
145151

0 commit comments

Comments
 (0)