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 1628c48

Browse filesBrowse files
theanarkhruyadorno
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 337f61f commit 1628c48
Copy full SHA for 1628c48

7 files changed

+156-9Lines changed: 156 additions & 9 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎doc/api/net.md‎

Copy file name to clipboardExpand all lines: doc/api/net.md
+8Lines changed: 8 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
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}
Collapse file

‎lib/net.js‎

Copy file name to clipboardExpand all lines: lib/net.js
+16-6Lines changed: 16 additions & 6 deletions
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) {
@@ -1835,12 +1842,12 @@ function createServerHandle(address, port, addressType, fd, flags) {
18351842
if (err) {
18361843
handle.close();
18371844
// Fallback to ipv4
1838-
return createServerHandle(DEFAULT_IPV4_ADDR, port);
1845+
return createServerHandle(DEFAULT_IPV4_ADDR, port, undefined, undefined, flags);
18391846
}
18401847
} else if (addressType === 6) {
18411848
err = handle.bind6(address, port, flags);
18421849
} else {
1843-
err = handle.bind(address, port);
1850+
err = handle.bind(address, port, flags);
18441851
}
18451852
}
18461853

@@ -2024,7 +2031,7 @@ Server.prototype.listen = function(...args) {
20242031
toNumber(args.length > 2 && args[2]); // (port, host, backlog)
20252032

20262033
options = options._handle || options.handle || options;
2027-
const flags = getFlags(options.ipv6Only);
2034+
const flags = getFlags(options);
20282035
// Refresh the id to make the previous call invalid
20292036
this._listeningId++;
20302037
// (handle[, backlog][, cb]) where handle is an object with a handle
@@ -2057,14 +2064,17 @@ Server.prototype.listen = function(...args) {
20572064
if (typeof options.port === 'number' || typeof options.port === 'string') {
20582065
validatePort(options.port, 'options.port');
20592066
backlog = options.backlog || backlogFromArgs;
2067+
if (options.reusePort === true) {
2068+
options.exclusive = true;
2069+
}
20602070
// start TCP server listening on host:port
20612071
if (options.host) {
20622072
lookupAndListen(this, options.port | 0, options.host, backlog,
20632073
options.exclusive, flags);
20642074
} else { // Undefined host, listens on unspecified address
20652075
// Default addressType 4 will be used to search for primary server
20662076
listenInCluster(this, null, options.port | 0, 4,
2067-
backlog, undefined, options.exclusive);
2077+
backlog, undefined, options.exclusive, flags);
20682078
}
20692079
return this;
20702080
}
Collapse file

‎src/tcp_wrap.cc‎

Copy file name to clipboardExpand all lines: src/tcp_wrap.cc
+7-3Lines changed: 7 additions & 3 deletions
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;
Collapse file

‎test/common/net.js‎

Copy file name to clipboard
+23Lines changed: 23 additions & 0 deletions
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+
};
Collapse file
+35Lines changed: 35 additions & 0 deletions
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());
Collapse file
+41Lines changed: 41 additions & 0 deletions
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());
Collapse file
+26Lines changed: 26 additions & 0 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.