Allow configuration of IP addresses to use for outgoing connections
authorJack Allnutt <jack@allnutt.eu>
Sat, 13 Jul 2013 10:49:38 +0000 (11:49 +0100)
committerJack Allnutt <jack@allnutt.eu>
Sat, 13 Jul 2013 10:49:38 +0000 (11:49 +0100)
Fixes #285

config.example.js
package.json
server/irc/connection.js
server/weblistener.js

index 2ca38eedda8d52412e84ffb74f13881ef5ed1260..d082d38ea5a308750c8e840dac02f9c40c441ce9 100644 (file)
@@ -33,6 +33,10 @@ conf.servers.push({
 //    ssl_cert: "cert.pem"
 //});
 
+conf.outgoing_addresses = {
+    IPv4: '0.0.0.0',
+    IPv6: '::'
+};
 
 
 // Do we want to enable the built in Identd server?
index 59125289fa9e37e895180e780aa2ff5bdc6b8df9..05b9e6c6079284d0fa22071f8619566b27ae1f53 100644 (file)
@@ -21,7 +21,7 @@
     "daemonize2": "0.4.0-rc.5",\r
     "eventemitter2": "0.4.11",\r
     "ipaddr.js": "0.1.1",\r
-    "socksjs": "0.3.3",\r
+    "socksjs": "0.4.1",\r
     "iconv-lite" : "0.2.10",\r
     "spdy": "1.9.1"\r
   }\r
index df9b5b9b1ddec91919554a6659293cea8c21f70f..be227405793d84c3e36a0e59fd1a394ed880193d 100644 (file)
@@ -1,6 +1,7 @@
 var net             = require('net'),
     tls             = require('tls'),
     util            = require('util'),
+    dns             = require('dns'),
     _               = require('lodash'),
     EventBinder     = require('./eventbinder.js'),
     IrcServer       = require('./server.js'),
@@ -121,8 +122,9 @@ IrcConnection.prototype.applyIrcEvents = function () {
  * Start the connection to the IRCd
  */
 IrcConnection.prototype.connect = function () {
-    var that = this;
-    var socks;
+    var that = this,
+        socks,
+        addSocketHandlers;
 
     // The socket connect event to listener for
     var socket_connect_event_name = 'connect';
@@ -133,53 +135,80 @@ IrcConnection.prototype.connect = function () {
 
     // Are we connecting through a SOCKS proxy?
     if (this.socks) {
-        this.socket = Socks.connect({
-            host: this.irc_host.hostname,
-            port: this.irc_host.port,
-            ssl: this.ssl,
-            rejectUnauthorized: global.config.reject_unauthorised_certificates
-        }, {host: this.socks.host,
-            port: this.socks.port,
-            user: this.socks.user,
-            pass: this.socks.pass
-        });
+        getConnectionFamily(this.socks.host, function (err, family, host) {
+            var outgoing;
+            if ((family === 'IPv6') && (global.config.outgoing_address.IPv6) && (global.config.outgoing_address.IPv6 !== '')) {
+                outgoing = global.config.outgoing_address.IPv6;
+            } else {
+                outgoing = global.config.outgoing_address.IPv4;
+            }
+            that.socket = Socks.connect({
+                host: that.irc_host.hostname,
+                port: that.irc_host.port,
+                ssl: that.ssl,
+                rejectUnauthorized: global.config.reject_unauthorised_certificates
+            }, {host: host,
+                port: that.socks.port,
+                user: that.socks.user,
+                pass: that.socks.pass,
+                localAddress: outgoing
+            });
 
-    } else if (this.ssl) {
-        this.socket = tls.connect({
-            host: this.irc_host.hostname,
-            port: this.irc_host.port,
-            rejectUnauthorized: global.config.reject_unauthorised_certificates
+            addSocketHandlers.call(that);
         });
+    } else {
+        getConnectionFamily(this.irc_host.hostname, function (err, family, host) {
+            var outgoing;
+            if ((family === 'IPv6') && (global.config.outgoing_addresses.IPv6) && (global.config.outgoing_addresses.IPv6 !== '')) {
+                outgoing = global.config.outgoing_addresses.IPv6;
+            } else {
+                outgoing = global.config.outgoing_addresses.IPv4;
+            }
 
-        socket_connect_event_name = 'secureConnect';
+            if (that.ssl) {
+                that.socket = tls.connect({
+                    host: host,
+                    port: that.irc_host.port,
+                    rejectUnauthorized: global.config.reject_unauthorised_certificates,
+                    localAddress: outgoing
+                });
+
+                socket_connect_event_name = 'secureConnect';
+
+            } else {
+                that.socket = net.connect({
+                    host: host,
+                    port: that.irc_host.port,
+                    localAddress: outgoing
+                });
+            }
 
-    } else {
-        this.socket = net.connect({
-            host: this.irc_host.hostname,
-            port: this.irc_host.port
+            addSocketHandlers.call(that);
         });
     }
 
-    this.socket.on(socket_connect_event_name, function () {
-        that.connected = true;
-        socketConnectHandler.call(that);
-    });
+    addSocketHandlers = function () {
+        this.socket.on(socket_connect_event_name, function () {
+            that.connected = true;
+            socketConnectHandler.call(that);
+        });
 
-    this.socket.on('error', function (event) {
-        that.emit('error', event);
-    });
+        this.socket.on('error', function (event) {
+            that.emit('error', event);
+        });
 
-    this.socket.on('data', function () {
-        parse.apply(that, arguments);
-    });
+        this.socket.on('data', function () {
+            parse.apply(that, arguments);
+        });
 
-    this.socket.on('close', function (had_error) {
-        that.connected = false;
-        that.emit('close');
+        this.socket.on('close', function (had_error) {
+            that.connected = false;
+            that.emit('close');
 
-        // Close the whole socket down
-        that.disposeSocket();
-    });
+            // Close the whole socket down
+            that.disposeSocket();
+        });
+    };
 };
 
 /**
@@ -285,6 +314,30 @@ IrcConnection.prototype.setEncoding = function (encoding) {
     }
 };
 
+function getConnectionFamily(host, callback) {
+    if (net.isIP(host)) {
+        if (net.isIPv4(host)) {
+            setImmediate(callback, null, 'IPv4', host);
+        } else {
+            setImmediate(callback, null, 'IPv6', host);
+        }
+    } else {
+        dns.resolve6(host, function (err, addresses) {
+            if (!err) {
+                callback(null, 'IPv6', addresses[0]);
+            } else {
+                dns.resolve4(host, function (err, addresses) {
+                    if (!err) {
+                        callback(null, 'IPv4',addresses[0]);
+                    } else {
+                        callback(err);
+                    }
+                });
+            }
+        });
+    }
+}
+
 
 function onChannelJoin(event) {
     var chan;
index a8b785041177ddd315ef89203f2e3d05ad1acbaf..ce75b3e247465c495e4fdf06313699f02ae8c54b 100644 (file)
@@ -31,9 +31,9 @@ var WebListener = function (web_config, transports) {
 
 
     events.EventEmitter.call(this);
-    
+
     http_handler = new HttpHandler(web_config);
-    
+
     // Standard options for the socket.io connections
     ws_opts = {
         'log level': 0,
@@ -79,8 +79,8 @@ var WebListener = function (web_config, transports) {
 
     hs.on('error', function (err) {
         that.emit('error', err);
-    })
-    
+    });
+
     this.ws.enable('browser client minification');
     this.ws.enable('browser client etag');
     this.ws.set('transports', transports);
@@ -128,7 +128,7 @@ function authoriseConnection(handshakeData, callback) {
     }
 
     handshakeData.real_address = address;
-    
+
     // If enabled, don't go over the connection limit
     if (global.config.max_client_conns && global.config.max_client_conns > 0) {
         if (global.clients.numOnAddress(address) + 1 > global.config.max_client_conns) {
@@ -144,7 +144,7 @@ function authoriseConnection(handshakeData, callback) {
             } else {
                 handshakeData.revdns = _.first(domains) || address;
             }
-            
+
             // All is well, authorise the connection
             callback(null, true);
         });