From 0a3b4575833cd7adb9b3f8436c2a8b63e43e9d4a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 30 Dec 2015 02:47:38 +0000 Subject: [PATCH] get all external ips --- index.js | 16 +++-- lib/external-ip.js | 144 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 lib/external-ip.js diff --git a/index.js b/index.js index c781371..c5fc2c5 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,6 @@ 'use strict'; var PromiseA = require('bluebird'); -var requestAsync = PromiseA.promisify(require('request')); module.exports.create = function (args) { var promises = []; @@ -14,17 +13,16 @@ module.exports.create = function (args) { // TODO determine if we have a AAAA local ip or not // TODO get A and AAAA records if (-1 !== args.protocols.indexOf('none')) { - promises.push(PromiseA.some(args.ipifyUrls.map(function (ipifyUrl) { - return requestAsync('https://' + ipifyUrl).then(function (resp) { - var ip = (resp.body || '').toString('ascii'); - - if (!/\d+\.\d+\.\d+\.\d+/.test(ip) && !/\w+\:\w+/.test(ip)) { - return PromiseA.reject(new Error("bad response '" + resp.body + "'")); - } + promises.push(PromiseA.any(args.ipifyUrls.map(function (ipifyUrl) { + var getIp = require('./lib/external-ip'); + return getIp({ hostname: ipifyUrl, debug: args.debug }); + })).then(function (ips) { + return ips.map(function (ip) { + // TODO attempt loopback return ip; }); - }), 1)); + })); } return PromiseA.all(promises).then(function (results) { diff --git a/lib/external-ip.js b/lib/external-ip.js new file mode 100644 index 0000000..a1346dd --- /dev/null +++ b/lib/external-ip.js @@ -0,0 +1,144 @@ +'use strict'; + +var PromiseA = require('bluebird'); +//var dns = PromiseA.promisifyAll(require('dns')); +var https = PromiseA.promisifyAll(require('https')); +var os = require('os'); + +function requestAsync(opts) { + return new PromiseA(function (resolve, reject) { + var req = https.request(opts, function (res) { + var data = ''; + + res.on('error', function (err) { + if (opts.debug) { + console.error('[Error] HP: bad request:'); + console.error(err); + } + reject(err); + }); + res.on('data', function (chunk) { + if (opts.debug > 2) { + console.log('HP: request chunk:'); + console.log(chunk); + } + data += chunk.toString('utf8'); + }); + res.on('end', function () { + if (opts.debug > 2) { + console.log('HP: request complete:'); + console.log(data); + } + resolve(data); + }); + }); + + req.on('error', reject); + req.end(); + }); +} + +module.exports = function (opts) { + var promises = []; + var interfaces = os.networkInterfaces(); + var ifacenames = Object.keys(interfaces).filter(function (ifacename) { + // http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/ + // https://wiki.archlinux.org/index.php/Network_configuration#Device_names + // we do not include tun and bridge devices because we're trying + // to see if any physical interface is internet-connected first + return /^(en|sl|wl|ww|eth|net|lan|wifi|inet)/.test(ifacename); + }); + var ifaces = ifacenames.reduce(function (all, ifacename) { + var ifs = interfaces[ifacename]; + + ifs.forEach(function (iface) { + if (!iface.internal && !/^fe80/.test(iface.address)) { + all.push(iface); + } + }); + + return all; + }, []); + + /* + // TODO how to support servername + promises.push(dns.resolve4Async(hostname).then(function (ips) { + return ips.map(function (ip) { + return { + address: ip + , family: 'IPv4' + }; + }); + })); + + promises.push(dns.resolve6Async(hostname).then(function (ips) { + return ips.map(function (ip) { + return { + address: ip + , family: 'IPv6' + }; + }); + })); + */ + + function parseIp(ip) { + if (!/\d+\.\d+\.\d+\.\d+/.test(ip) && !/\w+\:\w+/.test(ip)) { + return PromiseA.reject(new Error("bad response '" + ip + "'")); + } + + return ip; + } + + function ignoreEinval(err) { + if ('EINVAL' === err.code) { + if (opts.debug) { + console.warn('[HP] tried to bind to invalid address:'); + console.warn(err.stack); + } + return null; + } + + return PromiseA.reject(err); + } + + if (opts.debug) { + console.log('[HP] external ip opts:'); + console.log(opts); + + console.log('[HP] external ifaces:'); + console.log(ifaces); + } + + ifaces.forEach(function (iface) { + promises.push(requestAsync({ + family: iface.family + , method: 'GET' + , headers: { + Host: opts.hostname + } + , localAddress: iface.address + , servername: opts.hostname // is this actually sent to tls.connect()? + , hostname: opts.hostname // if so we could do the DNS ourselves + // and use the ip address here + , port: opts.port || 443 + , pathname: opts.pathname || opts.path || '/' + }).then(parseIp, ignoreEinval).then(function (addr) { + return { + family: iface.family + , address: addr + }; + })); + }); + + return PromiseA.all(promises).then(function (results) { + if (opts.debug) { + console.log('[HP] got all ip address types'); + console.log(results); + } + + return results; + }, function (err) { + console.error('[HP] error'); + console.error(err); + }); +};