net: add localPort to connect options
authorTimothy J Fontaine <tjfontaine@gmail.com>
Tue, 18 Feb 2014 01:30:12 +0000 (17:30 -0800)
committerFedor Indutny <fedor.indutny@gmail.com>
Tue, 18 Feb 2014 11:55:04 +0000 (15:55 +0400)
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
lib/net.js
test/simple/test-net-localerror.js [new file with mode: 0644]
test/simple/test-net-localport.js [new file with mode: 0644]

index d942013..3aad094 100644 (file)
@@ -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
index be5e6f6..97e45f8 100644 (file)
@@ -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 (file)
index 0000000..c4d04aa
--- /dev/null
@@ -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 (file)
index 0000000..34fa377
--- /dev/null
@@ -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);
+  });
+})