Skip to content

Commit

Permalink
cluster,net: fix random port keying
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mscdex committed Jun 13, 2016
1 parent d06820c commit d87bc5f
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 42 deletions.
86 changes: 69 additions & 17 deletions lib/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -473,14 +473,21 @@ function masterInit() {
// Stop processing if worker already disconnecting
if (worker.exitedAfterDisconnect)
return;
var args = [message.address,
message.port,
message.addressType,
message.fd,
message.index];
var key = args.join(':');

const port = +message.port;
var key = [message.address,
message.port,
message.addressType,
message.fd,
message.index].join(':');
var handle = handles[key];
if (handle === undefined) {

// Keys containing port zero are special cases in that they represent
// temporary placeholders in the list of bound handles until the OS actually
// assigns the real port number. In these cases we must always create a new
// handle since there is no way the handle could be shared until the real
// port number is received.
if (handle === undefined || port === 0) {
var constructor = RoundRobinHandle;
// UDP is exempt from round-robin connection balancing for what should
// be obvious reasons: it's connectionless. There is nothing to send to
Expand All @@ -501,15 +508,45 @@ function masterInit() {
if (!handle.data) handle.data = message.data;

// Set custom server data
handle.add(worker, function(errno, reply, handle) {
handle.add(worker, function(errno, reply, handle_) {
var data;
if (port === 0) {
delete handles[key];
var port_;
if (reply && reply.sockname && reply.sockname.port) {
port_ = reply.sockname.port;
} else if (handle_ && handle_.getsockname) {
const out = {};
handle_.getsockname(out);
port_ = out.port;
} else {
port_ = message.port;
}
key = [message.address,
port_,
message.addressType,
message.fd,
message.index].join(':');
if (!errno)
handles[key] = handle;
data = handle.data;
handle.key = key;
} else {
data = handles[key].data;
}

reply = util._extend({
errno: errno,
key: key,
ack: message.seq,
data: handles[key].data
data: data
}, reply);
if (errno) delete handles[key]; // Gives other workers a chance to retry.
send(worker, reply, handle);

// Gives other workers a chance to retry.
if (errno && port !== 0)
delete handles[key];

send(worker, reply, handle_);
});
}

Expand Down Expand Up @@ -571,18 +608,23 @@ function workerInit() {

// obj is a net#Server or a dgram#Socket object.
cluster._getServer = function(obj, options, cb) {
const indexesKey = [ options.address,
var index;
if (+options.port !== 0) {
var indexesKey = [ options.address,
options.port,
options.addressType,
options.fd ].join(':');
if (indexes[indexesKey] === undefined)
indexes[indexesKey] = 0;
else
indexes[indexesKey]++;
if (indexes[indexesKey] === undefined)
index = indexes[indexesKey] = 0;
else
index = ++indexes[indexesKey];
} else {
index = 0;
}

const message = util._extend({
act: 'queryServer',
index: indexes[indexesKey],
index: index,
data: null
}, options);

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

if (+options.port === 0 && reply.sockname) {
indexesKey = [ options.address,
reply.sockname.port,
options.addressType,
options.fd ].join(':');
if (indexes[indexesKey] === undefined)
index = indexes[indexesKey] = 0;
else
index = ++indexes[indexesKey];
}
if (handle)
shared(reply, handle, indexesKey, cb); // Shared listen socket.
else
Expand Down
1 change: 1 addition & 0 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,7 @@ Server.prototype._listen2 = function(address, port, addressType, backlog, fd) {
}

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

// unref the handle if the server was unref'ed prior to listening
Expand Down
12 changes: 8 additions & 4 deletions test/parallel/test-dgram-exclusive-implicit-bind.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ if (cluster.isMaster) {
cluster.fork();
cluster.fork();
if (!common.isWindows) {
cluster.fork({BOUND: 'y'});
cluster.fork({BOUND: 'y'});
cluster.fork({BOUND: '0'}).once('message', function(msg) {
cluster.fork({BOUND: msg});
});
}
});

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

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

if (process.env.BOUND === 'y') {
source.bind(0);
if (process.env.BOUND) {
source.bind(+process.env.BOUND, function() {
if (process.env.BOUND === '0')
process.send(this.address().port);
});
} else {
// cluster doesn't know about exclusive sockets, so it won't close them. This
// is expected, its the same situation for timers, outgoing tcp connections,
Expand Down
54 changes: 54 additions & 0 deletions test/parallel/test-net-listen-multiple-random-ports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use strict';
require('../common');
var assert = require('assert');
var cluster = require('cluster');
var net = require('net');

function noop() {}

if (cluster.isMaster) {
var worker1 = cluster.fork();
var worker2 = cluster.fork();

worker1.on('message', onMessage);
worker2.on('message', onMessage);
function onMessage(obj) {
assert.strictEqual(typeof obj, 'object');
assert.strictEqual(obj.msg, 'success');
assert.strictEqual(typeof obj.port, 'number');
assert.ok(obj.port !== 0, 'Expected non-zero port number from worker');
this.listens = (this.listens || 0) + 1;
if (worker1.listens === 2 && worker2.listens === 2) {
worker1.kill();
worker2.kill();
}
}
} else {
net.createServer(noop).on('error', function(err) {
// no errors expected
process.send('server1:' + err.code);
}).listen({
host: 'localhost',
port: 0,
exclusive: false
}, function() {
process.send({
msg: 'success',
port: this.address().port,
});
})

net.createServer(noop).on('error', function(err) {
// no errors expected
process.send('server2:' + err.code);
}).listen({
host: 'localhost',
port: 0,
exclusive: false
}, function() {
process.send({
msg: 'success',
port: this.address().port,
});
});
}
26 changes: 18 additions & 8 deletions test/parallel/test-net-listen-shared-ports.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use strict';
var common = require('../common');
require('../common');
var assert = require('assert');
var cluster = require('cluster');
var net = require('net');
Expand All @@ -9,19 +9,25 @@ function noop() {}
if (cluster.isMaster) {
var worker1 = cluster.fork();

worker1.on('message', function(msg) {
assert.equal(msg, 'success');
var worker2 = cluster.fork();
worker1.on('message', function(obj) {
assert.strictEqual(typeof obj, 'object');
assert.strictEqual(obj.msg, 'success');
var worker2 = cluster.fork({
NODE_TEST_PORT1: obj.port1,
NODE_TEST_PORT2: obj.port2
});

worker2.on('message', function(msg) {
assert.equal(msg, 'server2:EADDRINUSE');
assert.strictEqual(msg, 'server2:EADDRINUSE');
worker1.kill();
worker2.kill();
});
});
} else {
var server1 = net.createServer(noop);
var server2 = net.createServer(noop);
const port1 = (+process.env.NODE_TEST_PORT1) || 0;
const port2 = (+process.env.NODE_TEST_PORT2) || 0;

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

server1.listen({
host: 'localhost',
port: common.PORT,
port: port1,
exclusive: false
}, function() {
server2.listen({port: common.PORT + 1, exclusive: true}, function() {
server2.listen({port: port2, exclusive: true}, function() {
// the first worker should succeed
process.send('success');
process.send({
msg: 'success',
port1: server1.address().port,
port2: server2.address().port
});
});
});
}
27 changes: 16 additions & 11 deletions test/parallel/test-net-server-bind.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
'use strict';
var common = require('../common');
require('../common');
var assert = require('assert');
var net = require('net');


function assertValidPort(port) {
assert.strictEqual(typeof port, 'number');
assert.ok(isFinite(port));
assert.ok(port > 0);
}

// With only a callback, server should get a port assigned by the OS

var address0;
Expand All @@ -22,7 +28,7 @@ var address1;
var connectionKey1;
var server1 = net.createServer(function(socket) { });

server1.listen(common.PORT);
server1.listen(0);

setTimeout(function() {
address1 = server1.address();
Expand All @@ -37,7 +43,7 @@ setTimeout(function() {
var address2;
var server2 = net.createServer(function(socket) { });

server2.listen(common.PORT + 1, function() {
server2.listen(0, function() {
address2 = server2.address();
console.log('address2 %j', address2);
server2.close();
Expand All @@ -49,7 +55,7 @@ server2.listen(common.PORT + 1, function() {
var address3;
var server3 = net.createServer(function(socket) { });

server3.listen(common.PORT + 2, '0.0.0.0', 127, function() {
server3.listen(0, '0.0.0.0', 127, function() {
address3 = server3.address();
console.log('address3 %j', address3);
server3.close();
Expand All @@ -61,7 +67,7 @@ server3.listen(common.PORT + 2, '0.0.0.0', 127, function() {
var address4;
var server4 = net.createServer(function(socket) { });

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

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

assertValidPort(address1.port);
var expectedConnectionKey1;

if (address1.family === 'IPv6')
expectedConnectionKey1 = '6::::' + address1.port;
else
expectedConnectionKey1 = '4:0.0.0.0:' + address1.port;

assert.equal(connectionKey1, expectedConnectionKey1);
assert.equal(common.PORT + 1, address2.port);
assert.equal(common.PORT + 2, address3.port);
assert.equal(common.PORT + 3, address4.port);

assertValidPort(address2.port);
assertValidPort(address3.port);
assertValidPort(address4.port);
});
4 changes: 2 additions & 2 deletions test/parallel/test-regress-GH-5727.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ const net = require('net');
const invalidPort = -1 >>> 0;
const errorMessage = /"port" argument must be \>= 0 and \< 65536/;

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

assert.equal(this._connectionKey, key);
this.close();
Expand Down

0 comments on commit d87bc5f

Please sign in to comment.