Skip to content

Commit d87bc5f

Browse files
committed
cluster,net: fix random port keying
This commit fixes an issue where sockets that are bound to a random port are keyed on port 0 instead of the actual port that was assigned to the socket.
1 parent d06820c commit d87bc5f

7 files changed

+168
-42
lines changed

lib/cluster.js

+69-17
Original file line numberDiff line numberDiff line change
@@ -473,14 +473,21 @@ function masterInit() {
473473
// Stop processing if worker already disconnecting
474474
if (worker.exitedAfterDisconnect)
475475
return;
476-
var args = [message.address,
477-
message.port,
478-
message.addressType,
479-
message.fd,
480-
message.index];
481-
var key = args.join(':');
476+
477+
const port = +message.port;
478+
var key = [message.address,
479+
message.port,
480+
message.addressType,
481+
message.fd,
482+
message.index].join(':');
482483
var handle = handles[key];
483-
if (handle === undefined) {
484+
485+
// Keys containing port zero are special cases in that they represent
486+
// temporary placeholders in the list of bound handles until the OS actually
487+
// assigns the real port number. In these cases we must always create a new
488+
// handle since there is no way the handle could be shared until the real
489+
// port number is received.
490+
if (handle === undefined || port === 0) {
484491
var constructor = RoundRobinHandle;
485492
// UDP is exempt from round-robin connection balancing for what should
486493
// be obvious reasons: it's connectionless. There is nothing to send to
@@ -501,15 +508,45 @@ function masterInit() {
501508
if (!handle.data) handle.data = message.data;
502509

503510
// Set custom server data
504-
handle.add(worker, function(errno, reply, handle) {
511+
handle.add(worker, function(errno, reply, handle_) {
512+
var data;
513+
if (port === 0) {
514+
delete handles[key];
515+
var port_;
516+
if (reply && reply.sockname && reply.sockname.port) {
517+
port_ = reply.sockname.port;
518+
} else if (handle_ && handle_.getsockname) {
519+
const out = {};
520+
handle_.getsockname(out);
521+
port_ = out.port;
522+
} else {
523+
port_ = message.port;
524+
}
525+
key = [message.address,
526+
port_,
527+
message.addressType,
528+
message.fd,
529+
message.index].join(':');
530+
if (!errno)
531+
handles[key] = handle;
532+
data = handle.data;
533+
handle.key = key;
534+
} else {
535+
data = handles[key].data;
536+
}
537+
505538
reply = util._extend({
506539
errno: errno,
507540
key: key,
508541
ack: message.seq,
509-
data: handles[key].data
542+
data: data
510543
}, reply);
511-
if (errno) delete handles[key]; // Gives other workers a chance to retry.
512-
send(worker, reply, handle);
544+
545+
// Gives other workers a chance to retry.
546+
if (errno && port !== 0)
547+
delete handles[key];
548+
549+
send(worker, reply, handle_);
513550
});
514551
}
515552

@@ -571,18 +608,23 @@ function workerInit() {
571608

572609
// obj is a net#Server or a dgram#Socket object.
573610
cluster._getServer = function(obj, options, cb) {
574-
const indexesKey = [ options.address,
611+
var index;
612+
if (+options.port !== 0) {
613+
var indexesKey = [ options.address,
575614
options.port,
576615
options.addressType,
577616
options.fd ].join(':');
578-
if (indexes[indexesKey] === undefined)
579-
indexes[indexesKey] = 0;
580-
else
581-
indexes[indexesKey]++;
617+
if (indexes[indexesKey] === undefined)
618+
index = indexes[indexesKey] = 0;
619+
else
620+
index = ++indexes[indexesKey];
621+
} else {
622+
index = 0;
623+
}
582624

583625
const message = util._extend({
584626
act: 'queryServer',
585-
index: indexes[indexesKey],
627+
index: index,
586628
data: null
587629
}, options);
588630

@@ -591,6 +633,16 @@ function workerInit() {
591633
send(message, function(reply, handle) {
592634
if (obj._setServerData) obj._setServerData(reply.data);
593635

636+
if (+options.port === 0 && reply.sockname) {
637+
indexesKey = [ options.address,
638+
reply.sockname.port,
639+
options.addressType,
640+
options.fd ].join(':');
641+
if (indexes[indexesKey] === undefined)
642+
index = indexes[indexesKey] = 0;
643+
else
644+
index = ++indexes[indexesKey];
645+
}
594646
if (handle)
595647
shared(reply, handle, indexesKey, cb); // Shared listen socket.
596648
else

lib/net.js

+1
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,7 @@ Server.prototype._listen2 = function(address, port, addressType, backlog, fd) {
12591259
}
12601260

12611261
// generate connection key, this should be unique to the connection
1262+
port = this.address().port;
12621263
this._connectionKey = addressType + ':' + address + ':' + port;
12631264

12641265
// unref the handle if the server was unref'ed prior to listening

test/parallel/test-dgram-exclusive-implicit-bind.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ if (cluster.isMaster) {
7575
cluster.fork();
7676
cluster.fork();
7777
if (!common.isWindows) {
78-
cluster.fork({BOUND: 'y'});
79-
cluster.fork({BOUND: 'y'});
78+
cluster.fork({BOUND: '0'}).once('message', function(msg) {
79+
cluster.fork({BOUND: msg});
80+
});
8081
}
8182
});
8283

@@ -87,8 +88,11 @@ if (cluster.isMaster) {
8788

8889
var source = dgram.createSocket('udp4');
8990

90-
if (process.env.BOUND === 'y') {
91-
source.bind(0);
91+
if (process.env.BOUND) {
92+
source.bind(+process.env.BOUND, function() {
93+
if (process.env.BOUND === '0')
94+
process.send(this.address().port);
95+
});
9296
} else {
9397
// cluster doesn't know about exclusive sockets, so it won't close them. This
9498
// is expected, its the same situation for timers, outgoing tcp connections,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use strict';
2+
require('../common');
3+
var assert = require('assert');
4+
var cluster = require('cluster');
5+
var net = require('net');
6+
7+
function noop() {}
8+
9+
if (cluster.isMaster) {
10+
var worker1 = cluster.fork();
11+
var worker2 = cluster.fork();
12+
13+
worker1.on('message', onMessage);
14+
worker2.on('message', onMessage);
15+
function onMessage(obj) {
16+
assert.strictEqual(typeof obj, 'object');
17+
assert.strictEqual(obj.msg, 'success');
18+
assert.strictEqual(typeof obj.port, 'number');
19+
assert.ok(obj.port !== 0, 'Expected non-zero port number from worker');
20+
this.listens = (this.listens || 0) + 1;
21+
if (worker1.listens === 2 && worker2.listens === 2) {
22+
worker1.kill();
23+
worker2.kill();
24+
}
25+
}
26+
} else {
27+
net.createServer(noop).on('error', function(err) {
28+
// no errors expected
29+
process.send('server1:' + err.code);
30+
}).listen({
31+
host: 'localhost',
32+
port: 0,
33+
exclusive: false
34+
}, function() {
35+
process.send({
36+
msg: 'success',
37+
port: this.address().port,
38+
});
39+
})
40+
41+
net.createServer(noop).on('error', function(err) {
42+
// no errors expected
43+
process.send('server2:' + err.code);
44+
}).listen({
45+
host: 'localhost',
46+
port: 0,
47+
exclusive: false
48+
}, function() {
49+
process.send({
50+
msg: 'success',
51+
port: this.address().port,
52+
});
53+
});
54+
}
+18-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use strict';
2-
var common = require('../common');
2+
require('../common');
33
var assert = require('assert');
44
var cluster = require('cluster');
55
var net = require('net');
@@ -9,19 +9,25 @@ function noop() {}
99
if (cluster.isMaster) {
1010
var worker1 = cluster.fork();
1111

12-
worker1.on('message', function(msg) {
13-
assert.equal(msg, 'success');
14-
var worker2 = cluster.fork();
12+
worker1.on('message', function(obj) {
13+
assert.strictEqual(typeof obj, 'object');
14+
assert.strictEqual(obj.msg, 'success');
15+
var worker2 = cluster.fork({
16+
NODE_TEST_PORT1: obj.port1,
17+
NODE_TEST_PORT2: obj.port2
18+
});
1519

1620
worker2.on('message', function(msg) {
17-
assert.equal(msg, 'server2:EADDRINUSE');
21+
assert.strictEqual(msg, 'server2:EADDRINUSE');
1822
worker1.kill();
1923
worker2.kill();
2024
});
2125
});
2226
} else {
2327
var server1 = net.createServer(noop);
2428
var server2 = net.createServer(noop);
29+
const port1 = (+process.env.NODE_TEST_PORT1) || 0;
30+
const port2 = (+process.env.NODE_TEST_PORT2) || 0;
2531

2632
server1.on('error', function(err) {
2733
// no errors expected
@@ -35,12 +41,16 @@ if (cluster.isMaster) {
3541

3642
server1.listen({
3743
host: 'localhost',
38-
port: common.PORT,
44+
port: port1,
3945
exclusive: false
4046
}, function() {
41-
server2.listen({port: common.PORT + 1, exclusive: true}, function() {
47+
server2.listen({port: port2, exclusive: true}, function() {
4248
// the first worker should succeed
43-
process.send('success');
49+
process.send({
50+
msg: 'success',
51+
port1: server1.address().port,
52+
port2: server2.address().port
53+
});
4454
});
4555
});
4656
}

test/parallel/test-net-server-bind.js

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
'use strict';
2-
var common = require('../common');
2+
require('../common');
33
var assert = require('assert');
44
var net = require('net');
55

66

7+
function assertValidPort(port) {
8+
assert.strictEqual(typeof port, 'number');
9+
assert.ok(isFinite(port));
10+
assert.ok(port > 0);
11+
}
12+
713
// With only a callback, server should get a port assigned by the OS
814

915
var address0;
@@ -22,7 +28,7 @@ var address1;
2228
var connectionKey1;
2329
var server1 = net.createServer(function(socket) { });
2430

25-
server1.listen(common.PORT);
31+
server1.listen(0);
2632

2733
setTimeout(function() {
2834
address1 = server1.address();
@@ -37,7 +43,7 @@ setTimeout(function() {
3743
var address2;
3844
var server2 = net.createServer(function(socket) { });
3945

40-
server2.listen(common.PORT + 1, function() {
46+
server2.listen(0, function() {
4147
address2 = server2.address();
4248
console.log('address2 %j', address2);
4349
server2.close();
@@ -49,7 +55,7 @@ server2.listen(common.PORT + 1, function() {
4955
var address3;
5056
var server3 = net.createServer(function(socket) { });
5157

52-
server3.listen(common.PORT + 2, '0.0.0.0', 127, function() {
58+
server3.listen(0, '0.0.0.0', 127, function() {
5359
address3 = server3.address();
5460
console.log('address3 %j', address3);
5561
server3.close();
@@ -61,7 +67,7 @@ server3.listen(common.PORT + 2, '0.0.0.0', 127, function() {
6167
var address4;
6268
var server4 = net.createServer(function(socket) { });
6369

64-
server4.listen(common.PORT + 3, 127, function() {
70+
server4.listen(0, 127, function() {
6571
address4 = server4.address();
6672
console.log('address4 %j', address4);
6773
server4.close();
@@ -70,17 +76,16 @@ server4.listen(common.PORT + 3, 127, function() {
7076

7177
process.on('exit', function() {
7278
assert.ok(address0.port > 100);
73-
assert.equal(common.PORT, address1.port);
7479

80+
assertValidPort(address1.port);
7581
var expectedConnectionKey1;
76-
7782
if (address1.family === 'IPv6')
7883
expectedConnectionKey1 = '6::::' + address1.port;
7984
else
8085
expectedConnectionKey1 = '4:0.0.0.0:' + address1.port;
81-
8286
assert.equal(connectionKey1, expectedConnectionKey1);
83-
assert.equal(common.PORT + 1, address2.port);
84-
assert.equal(common.PORT + 2, address3.port);
85-
assert.equal(common.PORT + 3, address4.port);
87+
88+
assertValidPort(address2.port);
89+
assertValidPort(address3.port);
90+
assertValidPort(address4.port);
8691
});

test/parallel/test-regress-GH-5727.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ const net = require('net');
66
const invalidPort = -1 >>> 0;
77
const errorMessage = /"port" argument must be \>= 0 and \< 65536/;
88

9-
net.Server().listen(common.PORT, function() {
9+
net.Server().listen(0, function() {
1010
const address = this.address();
11-
const key = `${address.family.slice(-1)}:${address.address}:${common.PORT}`;
11+
const key = `${address.family.slice(-1)}:${address.address}:${address.port}`;
1212

1313
assert.equal(this._connectionKey, key);
1414
this.close();

0 commit comments

Comments
 (0)