From 59baab277691dcc7d788724020084414b96de22c Mon Sep 17 00:00:00 2001 From: Timothy J Fontaine Date: Mon, 17 Feb 2014 17:30:12 -0800 Subject: [PATCH] net: add localPort to connect options Expose localPort for binding to a specific port for outbound connections. If localAddress is not specified '0.0.0.0' is used for ip4 and '::' for ip6 connections. Fixes #7092 --- doc/api/net.markdown | 2 + lib/net.js | 50 ++++++++++++++++++++---- test/simple/test-net-localerror.js | 61 ++++++++++++++++++++++++++++++ test/simple/test-net-localport.js | 41 ++++++++++++++++++++ 4 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 test/simple/test-net-localerror.js create mode 100644 test/simple/test-net-localport.js diff --git a/doc/api/net.markdown b/doc/api/net.markdown index d942013118..3aad0940f4 100644 --- a/doc/api/net.markdown +++ b/doc/api/net.markdown @@ -64,6 +64,8 @@ For TCP sockets, `options` argument should be an object which specifies: - `localAddress`: Local interface to bind to for network connections. + - `localPort`: Local port to bind to for network connections. + - `family` : Version of IP stack. Defaults to `4`. For local domain sockets, `options` argument should be an object which diff --git a/lib/net.js b/lib/net.js index be5e6f6941..97e45f857c 100644 --- a/lib/net.js +++ b/lib/net.js @@ -776,20 +776,51 @@ function afterWrite(status, handle, req, err) { } -function connect(self, address, port, addressType, localAddress) { +function connect(self, address, port, addressType, localAddress, localPort) { // TODO return promise from Socket.prototype.connect which // wraps _connectReq. assert.ok(self._connecting); var err; - if (localAddress) { - if (addressType === 6) { - err = self._handle.bind6(localAddress); - } else { - err = self._handle.bind(localAddress); + if (localAddress || localPort) { + if (localAddress && !exports.isIP(localAddress)) + err = new TypeError( + 'localAddress should be a valid IP: ' + localAddress); + + if (localPort && !util.isNumber(localPort)) + err = new TypeError('localPort should be a number: ' + localPort); + + var bind; + + switch (addressType) { + case 4: + if (!localAddress) + localAddress = '0.0.0.0'; + bind = self._handle.bind; + break; + case 6: + if (!localAddress) + localAddress = '::'; + bind = self._handle.bind6; + break; + default: + err = new TypeError('Invalid addressType: ' + addressType); + break; + } + + if (err) { + self._destroy(err); + return; } + debug('binding to localAddress: %s and localPort: %d', + localAddress, + localPort); + + bind = bind.bind(self._handle); + err = bind(localAddress, localPort); + if (err) { self._destroy(errnoException(err, 'bind')); return; @@ -897,7 +928,12 @@ Socket.prototype.connect = function(options, cb) { // expects remoteAddress to have a meaningful value ip = ip || (addressType === 4 ? '127.0.0.1' : '0:0:0:0:0:0:0:1'); - connect(self, ip, options.port, addressType, options.localAddress); + connect(self, + ip, + options.port, + addressType, + options.localAddress, + options.localPort); } }); } diff --git a/test/simple/test-net-localerror.js b/test/simple/test-net-localerror.js new file mode 100644 index 0000000000..c4d04aa921 --- /dev/null +++ b/test/simple/test-net-localerror.js @@ -0,0 +1,61 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); +var net = require('net'); + +var server = net.createServer(function(socket) { + assert.ok(false, 'no clients should connect'); +}).listen(common.PORT).on('listening', function() { + server.unref(); + + function test1(next) { + connect({ + host: '127.0.0.1', + port: common.PORT, + localPort: 'foobar', + }, + 'localPort should be a number: foobar', + next); + } + + function test2(next) { + connect({ + host: '127.0.0.1', + port: common.PORT, + localAddress: 'foobar', + }, + 'localAddress should be a valid IP: foobar', + next) + } + + test1(test2); +}) + +function connect(opts, msg, cb) { + var client = net.connect(opts).on('connect', function() { + assert.ok(false, 'we should never connect'); + }).on('error', function(err) { + assert.strictEqual(err.message, msg); + if (cb) cb(); + }); +} diff --git a/test/simple/test-net-localport.js b/test/simple/test-net-localport.js new file mode 100644 index 0000000000..34fa377dc8 --- /dev/null +++ b/test/simple/test-net-localport.js @@ -0,0 +1,41 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); +var net = require('net'); + +var server = net.createServer(function(socket) { + console.log(socket.remotePort); + assert.strictEqual(socket.remotePort, common.PORT + 1); + socket.end(); + socket.on('close', function() { + server.close(); + }); +}).listen(common.PORT).on('listening', function() { + var client = net.connect({ + host: '127.0.0.1', + port: common.PORT, + localPort: common.PORT + 1, + }).on('connect', function() { + assert.strictEqual(client.localPort, common.PORT + 1); + }); +})