From 24f06c594a8aeb46ca290f87edb5be17fa235dec Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 31 Aug 2017 01:13:04 +0800 Subject: [PATCH] net: check EADDRINUSE after binding localPort PR-URL: https://github.com/nodejs/node/pull/15097 Fixes: https://github.com/nodejs/node/issues/15084 Reviewed-By: Benjamin Gruenbaum Reviewed-By: James M Snell Reviewed-By: Ruben Bridgewater Reviewed-By: Refael Ackermann Reviewed-By: Luigi Pinca Reviewed-By: Colin Ihrig --- lib/net.js | 37 +++++++++++++-------- test/parallel/test-net-client-bind-twice.js | 26 +++++++++++++++ 2 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 test/parallel/test-net-client-bind-twice.js diff --git a/lib/net.js b/lib/net.js index 946cc80a99e0e8..80fd1f82e5375c 100644 --- a/lib/net.js +++ b/lib/net.js @@ -871,6 +871,27 @@ function afterWrite(status, handle, req, err) { } +function checkBindError(err, port, handle) { + // EADDRINUSE may not be reported until we call listen() or connect(). + // To complicate matters, a failed bind() followed by listen() or connect() + // will implicitly bind to a random port. Ergo, check that the socket is + // bound to the expected port before calling listen() or connect(). + // + // FIXME(bnoordhuis) Doesn't work for pipe handles, they don't have a + // getsockname() method. Non-issue for now, the cluster module doesn't + // really support pipes anyway. + if (err === 0 && port > 0 && handle.getsockname) { + var out = {}; + err = handle.getsockname(out); + if (err === 0 && port !== out.port) { + debug(`checkBindError, bound to ${out.port} instead of ${port}`); + err = uv.UV_EADDRINUSE; + } + } + return err; +} + + function internalConnect( self, address, port, addressType, localAddress, localPort) { // TODO return promise from Socket.prototype.connect which @@ -894,6 +915,7 @@ function internalConnect( debug('binding to localAddress: %s and localPort: %d (addressType: %d)', localAddress, localPort, addressType); + err = checkBindError(err, localPort, self._handle); if (err) { const ex = exceptionWithHostPort(err, 'bind', localAddress, localPort); self.destroy(ex); @@ -1382,20 +1404,7 @@ function listenInCluster(server, address, port, addressType, cluster._getServer(server, serverQuery, listenOnMasterHandle); function listenOnMasterHandle(err, handle) { - // EADDRINUSE may not be reported until we call listen(). To complicate - // matters, a failed bind() followed by listen() will implicitly bind to - // a random port. Ergo, check that the socket is bound to the expected - // port before calling listen(). - // - // FIXME(bnoordhuis) Doesn't work for pipe handles, they don't have a - // getsockname() method. Non-issue for now, the cluster module doesn't - // really support pipes anyway. - if (err === 0 && port > 0 && handle.getsockname) { - var out = {}; - err = handle.getsockname(out); - if (err === 0 && port !== out.port) - err = uv.UV_EADDRINUSE; - } + err = checkBindError(err, port, handle); if (err) { var ex = exceptionWithHostPort(err, 'bind', address, port); diff --git a/test/parallel/test-net-client-bind-twice.js b/test/parallel/test-net-client-bind-twice.js new file mode 100644 index 00000000000000..ca7eb502d85ba5 --- /dev/null +++ b/test/parallel/test-net-client-bind-twice.js @@ -0,0 +1,26 @@ +'use strict'; + +// This tests that net.connect() from a used local port throws EADDRINUSE. + +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const server1 = net.createServer(common.mustNotCall()); +server1.listen(0, common.localhostIPv4, common.mustCall(() => { + const server2 = net.createServer(common.mustNotCall()); + server2.listen(0, common.localhostIPv4, common.mustCall(() => { + const client = net.connect({ + host: common.localhostIPv4, + port: server1.address().port, + localAddress: common.localhostIPv4, + localPort: server2.address().port + }, common.mustNotCall()); + + client.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'EADDRINUSE'); + server1.close(); + server2.close(); + })); + })); +}));