Skip to content

Commit ec867ac

Browse files
theanarkhaduh95
authored andcommitted
net: add UV_TCP_REUSEPORT for tcp
PR-URL: #55408 Refs: libuv/libuv#4407 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
1 parent dcbc5fb commit ec867ac

7 files changed

+156
-9
lines changed

doc/api/net.md

+8
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,9 @@ Listening on a file descriptor is not supported on Windows.
471471
<!-- YAML
472472
added: v0.11.14
473473
changes:
474+
- version: REPLACEME
475+
pr-url: https://github.com/nodejs/node/pull/55408
476+
description: The `reusePort` option is supported.
474477
- version: v15.6.0
475478
pr-url: https://github.com/nodejs/node/pull/36623
476479
description: AbortSignal support was added.
@@ -487,6 +490,11 @@ changes:
487490
* `ipv6Only` {boolean} For TCP servers, setting `ipv6Only` to `true` will
488491
disable dual-stack support, i.e., binding to host `::` won't make
489492
`0.0.0.0` be bound. **Default:** `false`.
493+
* `reusePort` {boolean} For TCP servers, setting `reusePort` to `true` allows
494+
multiple sockets on the same host to bind to the same port. Incoming connections
495+
are distributed by the operating system to listening sockets. This option is
496+
available only on some platforms, such as Linux 3.9+, DragonFlyBSD 3.6+, FreeBSD 12.0+,
497+
Solaris 11.4, and AIX 7.2.5+. **Default:** `false`.
490498
* `path` {string} Will be ignored if `port` is specified. See
491499
[Identifying paths for IPC connections][].
492500
* `port` {number}

lib/net.js

+16-6
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,15 @@ const {
164164
} = require('internal/perf/observe');
165165
const { getDefaultHighWaterMark } = require('internal/streams/state');
166166

167-
function getFlags(ipv6Only) {
168-
return ipv6Only === true ? TCPConstants.UV_TCP_IPV6ONLY : 0;
167+
function getFlags(options) {
168+
let flags = 0;
169+
if (options.ipv6Only === true) {
170+
flags |= TCPConstants.UV_TCP_IPV6ONLY;
171+
}
172+
if (options.reusePort === true) {
173+
flags |= TCPConstants.UV_TCP_REUSEPORT;
174+
}
175+
return flags;
169176
}
170177

171178
function createHandle(fd, is_server) {
@@ -1833,12 +1840,12 @@ function createServerHandle(address, port, addressType, fd, flags) {
18331840
if (err) {
18341841
handle.close();
18351842
// Fallback to ipv4
1836-
return createServerHandle(DEFAULT_IPV4_ADDR, port);
1843+
return createServerHandle(DEFAULT_IPV4_ADDR, port, undefined, undefined, flags);
18371844
}
18381845
} else if (addressType === 6) {
18391846
err = handle.bind6(address, port, flags);
18401847
} else {
1841-
err = handle.bind(address, port);
1848+
err = handle.bind(address, port, flags);
18421849
}
18431850
}
18441851

@@ -2022,7 +2029,7 @@ Server.prototype.listen = function(...args) {
20222029
toNumber(args.length > 2 && args[2]); // (port, host, backlog)
20232030

20242031
options = options._handle || options.handle || options;
2025-
const flags = getFlags(options.ipv6Only);
2032+
const flags = getFlags(options);
20262033
// Refresh the id to make the previous call invalid
20272034
this._listeningId++;
20282035
// (handle[, backlog][, cb]) where handle is an object with a handle
@@ -2055,14 +2062,17 @@ Server.prototype.listen = function(...args) {
20552062
if (typeof options.port === 'number' || typeof options.port === 'string') {
20562063
validatePort(options.port, 'options.port');
20572064
backlog = options.backlog || backlogFromArgs;
2065+
if (options.reusePort === true) {
2066+
options.exclusive = true;
2067+
}
20582068
// start TCP server listening on host:port
20592069
if (options.host) {
20602070
lookupAndListen(this, options.port | 0, options.host, backlog,
20612071
options.exclusive, flags);
20622072
} else { // Undefined host, listens on unspecified address
20632073
// Default addressType 4 will be used to search for primary server
20642074
listenInCluster(this, null, options.port | 0, 4,
2065-
backlog, undefined, options.exclusive);
2075+
backlog, undefined, options.exclusive, flags);
20662076
}
20672077
return this;
20682078
}

src/tcp_wrap.cc

+7-3
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ void TCPWrap::Initialize(Local<Object> target,
123123
NODE_DEFINE_CONSTANT(constants, SOCKET);
124124
NODE_DEFINE_CONSTANT(constants, SERVER);
125125
NODE_DEFINE_CONSTANT(constants, UV_TCP_IPV6ONLY);
126+
NODE_DEFINE_CONSTANT(constants, UV_TCP_REUSEPORT);
126127
target->Set(context,
127128
env->constants_string(),
128129
constants).Check();
@@ -246,9 +247,12 @@ void TCPWrap::Bind(
246247
int port;
247248
unsigned int flags = 0;
248249
if (!args[1]->Int32Value(env->context()).To(&port)) return;
249-
if (family == AF_INET6 &&
250-
!args[2]->Uint32Value(env->context()).To(&flags)) {
251-
return;
250+
if (args.Length() >= 3 && args[2]->IsUint32()) {
251+
if (!args[2]->Uint32Value(env->context()).To(&flags)) return;
252+
// Can not set IPV6 flags on IPV4 socket
253+
if (family == AF_INET) {
254+
flags &= ~UV_TCP_IPV6ONLY;
255+
}
252256
}
253257

254258
T addr;

test/common/net.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
const net = require('net');
3+
4+
const options = { port: 0, reusePort: true };
5+
6+
function checkSupportReusePort() {
7+
return new Promise((resolve, reject) => {
8+
const server = net.createServer().listen(options);
9+
server.on('listening', () => {
10+
server.close(resolve);
11+
});
12+
server.on('error', (err) => {
13+
console.log('The `reusePort` option is not supported:', err.message);
14+
server.close();
15+
reject(err);
16+
});
17+
});
18+
}
19+
20+
module.exports = {
21+
checkSupportReusePort,
22+
options,
23+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
const common = require('../common');
3+
const { checkSupportReusePort, options } = require('../common/net');
4+
const assert = require('assert');
5+
const child_process = require('child_process');
6+
const net = require('net');
7+
8+
if (!process.env.isWorker) {
9+
checkSupportReusePort().then(() => {
10+
const server = net.createServer();
11+
server.listen(options, common.mustCall(() => {
12+
const port = server.address().port;
13+
const workerOptions = { env: { ...process.env, isWorker: 1, port } };
14+
let count = 2;
15+
for (let i = 0; i < 2; i++) {
16+
const worker = child_process.fork(__filename, workerOptions);
17+
worker.on('exit', common.mustCall((code) => {
18+
assert.strictEqual(code, 0);
19+
if (--count === 0) {
20+
server.close();
21+
}
22+
}));
23+
}
24+
}));
25+
}, () => {
26+
common.skip('The `reusePort` option is not supported');
27+
});
28+
return;
29+
}
30+
31+
const server = net.createServer();
32+
33+
server.listen({ ...options, port: +process.env.port }, common.mustCall(() => {
34+
server.close();
35+
})).on('error', common.mustNotCall());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
const common = require('../common');
3+
4+
const { checkSupportReusePort, options } = require('../common/net');
5+
const assert = require('assert');
6+
const cluster = require('cluster');
7+
const net = require('net');
8+
9+
if (cluster.isPrimary) {
10+
checkSupportReusePort().then(() => {
11+
cluster.fork().on('exit', common.mustCall((code) => {
12+
assert.strictEqual(code, 0);
13+
}));
14+
}, () => {
15+
common.skip('The `reusePort` option is not supported');
16+
});
17+
return;
18+
}
19+
20+
let waiting = 2;
21+
function close() {
22+
if (--waiting === 0)
23+
cluster.worker.disconnect();
24+
}
25+
26+
const server1 = net.createServer();
27+
const server2 = net.createServer();
28+
29+
// Test if the worker requests the main process to create a socket
30+
cluster._getServer = common.mustNotCall();
31+
32+
server1.listen(options, common.mustCall(() => {
33+
const port = server1.address().port;
34+
server2.listen({ ...options, port }, common.mustCall(() => {
35+
server1.close(close);
36+
server2.close(close);
37+
}));
38+
}));
39+
40+
server1.on('error', common.mustNotCall());
41+
server2.on('error', common.mustNotCall());

test/parallel/test-net-reuseport.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
const common = require('../common');
3+
const { checkSupportReusePort, options } = require('../common/net');
4+
const net = require('net');
5+
6+
function test(host) {
7+
const server1 = net.createServer();
8+
const server2 = net.createServer();
9+
server1.listen({ ...options, host }, common.mustCall(() => {
10+
const port = server1.address().port;
11+
server2.listen({ ...options, host, port }, common.mustCall(() => {
12+
server1.close();
13+
server2.close();
14+
}));
15+
}));
16+
server1.on('error', common.mustNotCall());
17+
server2.on('error', common.mustNotCall());
18+
}
19+
20+
checkSupportReusePort()
21+
.then(() => {
22+
test('127.0.0.1');
23+
common.hasIPv6 && test('::');
24+
}, () => {
25+
common.skip('The `reusePort` option is not supported');
26+
});

0 commit comments

Comments
 (0)