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

Lines changed: 156 additions & 9 deletions

File tree

‎doc/api/net.md‎

Lines changed: 8 additions & 0 deletions
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‎

Lines 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-
functiongetFlags(ipv6Only){
168-
returnipv6Only===true ? TCPConstants.UV_TCP_IPV6ONLY : 0;
167+
functiongetFlags(options){
168+
letflags=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+
returnflags;
169176
}
170177

171178
functioncreateHandle(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-
returncreateServerHandle(DEFAULT_IPV4_ADDR,port);
1843+
returncreateServerHandle(DEFAULT_IPV4_ADDR,port,undefined,undefined,flags);
18371844
}
18381845
}elseif(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-
constflags=getFlags(options.ipv6Only);
2032+
constflags=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(typeofoptions.port==='number'||typeofoptions.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
returnthis;
20682078
}

‎src/tcp_wrap.cc‎

Lines 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
unsignedint 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‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
constnet=require('net');
3+
4+
constoptions={port: 0,reusePort: true};
5+
6+
functioncheckSupportReusePort(){
7+
returnnewPromise((resolve,reject)=>{
8+
constserver=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+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
const{ checkSupportReusePort, options }=require('../common/net');
4+
constassert=require('assert');
5+
constchild_process=require('child_process');
6+
constnet=require('net');
7+
8+
if(!process.env.isWorker){
9+
checkSupportReusePort().then(()=>{
10+
constserver=net.createServer();
11+
server.listen(options,common.mustCall(()=>{
12+
constport=server.address().port;
13+
constworkerOptions={env: { ...process.env,isWorker: 1, port }};
14+
letcount=2;
15+
for(leti=0;i<2;i++){
16+
constworker=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+
constserver=net.createServer();
32+
33+
server.listen({ ...options,port: +process.env.port},common.mustCall(()=>{
34+
server.close();
35+
})).on('error',common.mustNotCall());
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
4+
const{ checkSupportReusePort, options }=require('../common/net');
5+
constassert=require('assert');
6+
constcluster=require('cluster');
7+
constnet=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+
letwaiting=2;
21+
functionclose(){
22+
if(--waiting===0)
23+
cluster.worker.disconnect();
24+
}
25+
26+
constserver1=net.createServer();
27+
constserver2=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+
constport=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());
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
const{ checkSupportReusePort, options }=require('../common/net');
4+
constnet=require('net');
5+
6+
functiontest(host){
7+
constserver1=net.createServer();
8+
constserver2=net.createServer();
9+
server1.listen({ ...options, host },common.mustCall(()=>{
10+
constport=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)