From bef1fbaba4aa880696a800c5450cff262e7cd0f6 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 31 Dec 2015 02:21:29 +0000 Subject: [PATCH] working better --- README.md | 5 +- bin/holepunch.js | 52 +++++++++++++---- bin/service.js | 49 ++++++++++++++++ lib/external-ip.js | 25 +------- lib/index.js | 121 +++++++++++++++++++++++++++++---------- lib/loopback-listener.js | 24 ++++---- lib/pmp.js | 7 +++ lib/upnp.js | 14 ++++- 8 files changed, 214 insertions(+), 83 deletions(-) create mode 100644 bin/service.js diff --git a/README.md b/README.md index 6d09065..daeea48 100644 --- a/README.md +++ b/README.md @@ -47,15 +47,14 @@ holepunch --plain-ports 80,65080 --tls-ports 443,65443 ### API -This is the current Dec 30th alpha api +This is the current Dec 30th api in master ```javascript var punch = require('holepunch'); punch({ debug: true -, plainPorts: [{ internal: 80, external: 80 }] -, tlsPorts: [{ internal: 443, external: 443 }] +, mappings: [{ internal: 443, external: 443, secure: true }] , ipifyUrls: ['api.ipify.org'], , protocols: ['none', 'upnp', 'pmp'] , rvpnConfigs: [] diff --git a/bin/holepunch.js b/bin/holepunch.js index aa45d5e..507d01c 100755 --- a/bin/holepunch.js +++ b/bin/holepunch.js @@ -12,7 +12,9 @@ cli.parse({ //, 'plain-ports': [ false, " Port numbers to test with plaintext loopback. (default: 65080) (formats: ,,)", 'string' ] , 'tls-ports': [ false, " Port numbers to test with tls loopback. (default: null)", 'string' ] , 'ipify-urls': [ false, " Comma separated list of URLs to test for external ip. (default: api.ipify.org)", 'string' ] -, 'protocols': [ false, " Comma separated list of ip mapping protocols. (default: none,upnp,pmp)", 'string' ] +, protocols: [ false, " Comma separated list of ip mapping protocols. (default: none,upnp,pmp)", 'string' ] +//, upnp: [ false, " Use nat-upnp. (default: true)", 'boolean' ] +//, pmp: [ false, " Use nat-pmp. (default: true)", 'boolean' ] , 'rvpn-configs': [ false, " Comma separated list of Reverse VPN config files in the order they should be tried. (default: null)", 'string' ] // TODO allow standalone, webroot, etc }); @@ -22,6 +24,11 @@ cli.main(function(_, options) { console.log(''); var args = {}; var hp = require('../'); + var loopback = require('../lib/loopback-listener'); + var plainPorts = options['plain-ports']; + var tlsPorts = options['tls-ports']; + var pretest; + var protocols; function parsePorts(portstr) { var parts = portstr.split(':'); @@ -45,9 +52,7 @@ cli.main(function(_, options) { } args.debug = options.debug; - args.plainPorts = options['plain-ports']; - args.tlsPorts = options['tls-ports']; - args.protocols = options.protocols; + protocols = options.protocols; args.ipifyUrls = options['ipify-urls']; args.rvpnConfigs = options['rvpn-configs']; @@ -56,21 +61,44 @@ cli.main(function(_, options) { } else { args.ipifyUrls = (args.ipifyUrls || 'api.ipify.org').split(','); } - if ('false' === args.protocols || false === args.protocols) { - args.protocols = []; + if ('false' === protocols || false === protocols) { + protocols = []; } else { - args.protocols = (args.protocols || 'none,upnp,pmp').split(','); + protocols = (protocols || 'none,upnp,pmp').split(','); } // Coerce to string. cli returns a number although we request a string. - args.tlsPorts = (args.tlsPorts || "").toString().split(',').filter(exists).map(parsePorts); + tlsPorts = (tlsPorts || "").toString().split(',').filter(exists).map(parsePorts); args.rvpnConfigs = (args.rvpnConfigs || "").toString().split(',').filter(exists); - if ('false' === args.plainPorts || false === args.plainPorts) { - args.plainPorts = []; + if ('false' === plainPorts || false === plainPorts) { + plainPorts = []; } else { - args.plainPorts = (args.plainPorts || "65080").toString().split(',').map(parsePorts); + plainPorts = (plainPorts || "65080").toString().split(',').map(parsePorts); + } + pretest = (-1 !== protocols.indexOf('none')); + args.upnp = options.upnp + || (-1 !== protocols.indexOf('upnp')) || (-1 !== protocols.indexOf('ssdp')); + args.pmp = options.pmp + || (-1 !== protocols.indexOf('pmp')) || (-1 !== protocols.indexOf('nat-pmp')); + + args.mappings = plainPorts.map(function (info) { + info.secure = false; + info.loopback = pretest; + return info; + }).concat(tlsPorts.map(function (info) { + info.secure = true; + info.loopback = pretest; + return info; + })); + + //var servers = loopback.create(args); + loopback.create(args); + + if (args.debug) { + console.log('[HP] create servers'); + //console.log(servers); } - return hp.create(args).then(function () { + return hp(args).then(function () { //console.log('[HP] wishing wanting waiting'); console.log('complete, exiting'); process.exit(0); diff --git a/bin/service.js b/bin/service.js new file mode 100644 index 0000000..a992e5b --- /dev/null +++ b/bin/service.js @@ -0,0 +1,49 @@ +'use strict'; + +var punch = require('../'); + +function touch() { + punch({ + mappings: [ + { internal: 80 + , external: 80 + , secure: false + , loopback: false + } + , { internal: 65080 + , external: 65080 + , secure: false + , loopback: false + } + , { internal: 65443 + , external: 65443 + , secure: false + , loopback: false + } + , { internal: 443 + , external: 443 + , secure: false + , loopback: false + } + , { internal: 65022 + , external: 65022 + , secure: false + , loopback: false + } + , { internal: 22 + , external: 22 + , secure: false + , loopback: false + } + ] + , upnp: true + , pmp: true + , debug: true + }).then(function (results) { + console.log('map results'); + console.log(results); + }); +} + +setInterval(touch, 90 * 60 * 1000); +touch(); diff --git a/lib/external-ip.js b/lib/external-ip.js index b5b1004..5ecd316 100644 --- a/lib/external-ip.js +++ b/lib/external-ip.js @@ -2,30 +2,10 @@ var PromiseA = require('bluebird'); //var dns = PromiseA.promisifyAll(require('dns')); -var os = require('os'); var requestAsync = require('./request'); 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 @@ -71,12 +51,9 @@ module.exports = function (opts) { if (opts.debug) { console.log('[HP] external ip opts:'); console.log(opts); - - console.log('[HP] external ifaces:'); - console.log(ifaces); } - ifaces.forEach(function (iface) { + opts.ifaces.forEach(function (iface) { promises.push(requestAsync({ family: iface.family , method: 'GET' diff --git a/lib/index.js b/lib/index.js index 806b97e..45fa903 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,23 +1,35 @@ 'use strict'; var PromiseA = require('bluebird'); -var loopback = require('./loopback-listener'); +var os = require('os'); -module.exports.create = function (args) { +module.exports = function (args) { if (args.debug) { console.log('[HP] create holepuncher'); console.log(args); } - var servers = loopback.create(args); - //var promises = []; - - if (args.debug) { - console.log('[HP] create servers'); - //console.log(servers); - } + 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); + }); function getExternalIps() { + if (!args.ipifyUrls || !args.ipifyUrls.length) { + return PromiseA.resolve(args.ifaces.map(function (iface) { + return { + family: iface.family + //, address: addr + , address: iface.address // TODO check where this is used + , localAddress: iface.address + }; + })); + } + return PromiseA.any(args.ipifyUrls.map(function (ipifyUrl) { var getIp = require('./external-ip'); @@ -87,29 +99,39 @@ module.exports.create = function (args) { } if (!opts.pretest) { - return ip; + return PromiseA.reject(new Error("[not an error]: skip the loopback test")); } return testOpenPort(ip, opts.portInfo); })); } - return getExternalIps().then(function (ips) { - var pretest = (-1 !== args.protocols.indexOf('none')); - var portInfos = args.plainPorts.map(function (info) { - info.secure = false; - return info; - }).concat(args.tlsPorts.map(function (info) { - info.secure = true; - return info; - })); + args.ifaces = ifacenames.reduce(function (all, ifacename) { + var ifs = interfaces[ifacename]; - return PromiseA.all(portInfos.map(function (info) { + ifs.forEach(function (iface) { + if (!iface.internal && !/^fe80/.test(iface.address)) { + all.push(iface); + } + }); + + return all; + }, []); + + if (args.debug) { + console.log('[HP] external ifaces:'); + console.log(args.ifaces); + } + + return getExternalIps().then(function (ips) { + var portInfos = args.mappings; + + return PromiseA.all(portInfos.map(function (mapping) { // TODO clone-merge args return testPort({ - portInfo: info + portInfo: mapping , ips: ips - , pretest: pretest + , pretest: mapping.loopback }); })).then(function (portInfos) { if (args.debug) { @@ -119,27 +141,29 @@ module.exports.create = function (args) { return portInfos; }, function () { // at least one port could not be mapped - var mappers = []; + var upnps = []; + var pmps = []; + var pu = PromiseA.resolve(); + var pm = PromiseA.resolve(); - if (-1 !== args.protocols.indexOf('upnp') - || -1 !== args.protocols.indexOf('ssdp') - ) { + if (args.upnp) { if (args.debug) { console.log('[HP] will try upnp'); } - mappers.push(require('./upnp')); + upnps.push(require('./upnp')); } - if (-1 !== args.protocols.indexOf('pmp') - || -1 !== args.protocols.indexOf('nat-pmp') - ) { + if (args.pmp) { if (args.debug) { console.log('[HP] will try nat-pmp'); } - mappers.push(require('./pmp')); + pmps.push(require('./pmp')); } return PromiseA.all(portInfos.map(function (portInfo) { + /* + // TODO create single dgram listeners and serialize upnp requests + // because we can't have multiple requests bound to the same port, duh return PromiseA.any(mappers.map(function (fn) { var p = fn(args, ips, portInfo); @@ -149,6 +173,41 @@ module.exports.create = function (args) { return p; })); + */ + var good; + + function nextu(fn) { + pu = pu.then(function () { + return fn(args, ips, portInfo); + }).then(function (results) { + good = results; + return null; + }, function (/*err*/) { + return null; + }); + } + + function nextm(fn) { + pm = pm.then(function () { + return fn(args, ips, portInfo); + }).then(function (results) { + good = results; + return null; + }, function (/*err*/) { + return null; + }); + } + + upnps.forEach(nextu); + pmps.forEach(nextm); + + return PromiseA.any([pu, pm]).then(function () { + if (!good) { + return PromiseA.reject(new Error("no port map success")); + } + + return null; + }); })).then(function () { if (args.debug) { console.log("[HP] all ports successfully mapped"); diff --git a/lib/loopback-listener.js b/lib/loopback-listener.js index 51adf8d..14cf063 100644 --- a/lib/loopback-listener.js +++ b/lib/loopback-listener.js @@ -16,18 +16,20 @@ module.exports.create = function (opts) { app.use('/', middleware(opts)); - (opts.plainPorts||[]).forEach(function (plainPort) { - var plainServer = http.createServer(); - plainServer.__plainPort = plainPort; - plainServer.on('request', app); - results.plainServers.push(plainServer); - }); + opts.mappings.forEach(function (mapping) { + var server; - (opts.tlsPorts||[]).forEach(function (tlsPort) { - var tlsServer = https.createServer(httpsOptions); - tlsServer.__tlsPort = tlsPort; - tlsServer.on('request', app); - results.tlsServers.push(tlsServer); + if (false === mapping.secure) { + server = http.createServer(); + server.__plainPort = mapping; + server.on('request', app); + results.plainServers.push(server); + } else { + server = https.createServer(httpsOptions); + server.__tlsPort = mapping; + server.on('request', app); + results.tlsServers.push(server); + } }); function onListen() { diff --git a/lib/pmp.js b/lib/pmp.js index 3a0cddd..aa87541 100644 --- a/lib/pmp.js +++ b/lib/pmp.js @@ -37,6 +37,9 @@ function pmpForwardHelper(gw, portInfo) { return new PromiseA(function (resolve, reject) { // create a "client" instance connecting to your local gateway var client = natpmp.connect(gw); + client.on('error', function (err) { + reject(err); + }); function setPortForward() { // setup a new port mapping @@ -60,6 +63,8 @@ function pmpForwardHelper(gw, portInfo) { // public: 2222, // ... // } + + client.close(); resolve(); }); } @@ -96,6 +101,7 @@ module.exports = function (args, ips, portInfo) { module.exports.pmpForward = pmpForward; +/* function usage() { console.warn(""); console.warn("node helpers/pmp-forward [public port] [private port] [ttl]"); @@ -121,3 +127,4 @@ function run() { if (require.main === module) { run(); } +*/ diff --git a/lib/upnp.js b/lib/upnp.js index b092ac7..ebc444d 100644 --- a/lib/upnp.js +++ b/lib/upnp.js @@ -2,6 +2,7 @@ var PromiseA = require('bluebird').Promise; var natUpnp = require('holepunch-upnp'); +var client; function upnpForward(opts) { if (opts.debug) { @@ -9,7 +10,7 @@ function upnpForward(opts) { console.log(opts); } - return natUpnp.createClient({ timeout: 3 * 1000 }).then(function (client) { + function useClient(client) { if (opts.debug) { console.log('[HP] [upnp] created client'); console.log(client); @@ -39,7 +40,16 @@ function upnpForward(opts) { return promitter; });*/ - }); + } + + if (client) { + return useClient(client); + } else { + return natUpnp.createClient({ timeout: 3 * 1000 }).then(function (_client) { + client = _client; + useClient(client); + }); + } } module.exports = function (args, ips, portInfo) {