working better

This commit is contained in:
AJ ONeal 2015-12-31 02:21:29 +00:00
parent d11a51ff90
commit bef1fbaba4
8 changed files with 214 additions and 83 deletions

View File

@ -47,15 +47,14 @@ holepunch --plain-ports 80,65080 --tls-ports 443,65443
### API ### API
This is the current Dec 30th alpha api This is the current Dec 30th api in master
```javascript ```javascript
var punch = require('holepunch'); var punch = require('holepunch');
punch({ punch({
debug: true debug: true
, plainPorts: [{ internal: 80, external: 80 }] , mappings: [{ internal: 443, external: 443, secure: true }]
, tlsPorts: [{ internal: 443, external: 443 }]
, ipifyUrls: ['api.ipify.org'], , ipifyUrls: ['api.ipify.org'],
, protocols: ['none', 'upnp', 'pmp'] , protocols: ['none', 'upnp', 'pmp']
, rvpnConfigs: [] , rvpnConfigs: []

View File

@ -12,7 +12,9 @@ cli.parse({
//, 'plain-ports': [ false, " Port numbers to test with plaintext loopback. (default: 65080) (formats: <port>,<internal:external>,<internal:external1|external2>)", 'string' ] //, 'plain-ports': [ false, " Port numbers to test with plaintext loopback. (default: 65080) (formats: <port>,<internal:external>,<internal:external1|external2>)", 'string' ]
, 'tls-ports': [ false, " Port numbers to test with tls loopback. (default: null)", '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' ] , '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' ] , '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 // TODO allow standalone, webroot, etc
}); });
@ -22,6 +24,11 @@ cli.main(function(_, options) {
console.log(''); console.log('');
var args = {}; var args = {};
var hp = require('../'); 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) { function parsePorts(portstr) {
var parts = portstr.split(':'); var parts = portstr.split(':');
@ -45,9 +52,7 @@ cli.main(function(_, options) {
} }
args.debug = options.debug; args.debug = options.debug;
args.plainPorts = options['plain-ports']; protocols = options.protocols;
args.tlsPorts = options['tls-ports'];
args.protocols = options.protocols;
args.ipifyUrls = options['ipify-urls']; args.ipifyUrls = options['ipify-urls'];
args.rvpnConfigs = options['rvpn-configs']; args.rvpnConfigs = options['rvpn-configs'];
@ -56,21 +61,44 @@ cli.main(function(_, options) {
} else { } else {
args.ipifyUrls = (args.ipifyUrls || 'api.ipify.org').split(','); args.ipifyUrls = (args.ipifyUrls || 'api.ipify.org').split(',');
} }
if ('false' === args.protocols || false === args.protocols) { if ('false' === protocols || false === protocols) {
args.protocols = []; protocols = [];
} else { } 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. // 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); args.rvpnConfigs = (args.rvpnConfigs || "").toString().split(',').filter(exists);
if ('false' === args.plainPorts || false === args.plainPorts) { if ('false' === plainPorts || false === plainPorts) {
args.plainPorts = []; plainPorts = [];
} else { } 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('[HP] wishing wanting waiting');
console.log('complete, exiting'); console.log('complete, exiting');
process.exit(0); process.exit(0);

49
bin/service.js Normal file
View File

@ -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();

View File

@ -2,30 +2,10 @@
var PromiseA = require('bluebird'); var PromiseA = require('bluebird');
//var dns = PromiseA.promisifyAll(require('dns')); //var dns = PromiseA.promisifyAll(require('dns'));
var os = require('os');
var requestAsync = require('./request'); var requestAsync = require('./request');
module.exports = function (opts) { module.exports = function (opts) {
var promises = []; 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 // TODO how to support servername
@ -71,12 +51,9 @@ module.exports = function (opts) {
if (opts.debug) { if (opts.debug) {
console.log('[HP] external ip opts:'); console.log('[HP] external ip opts:');
console.log(opts); console.log(opts);
console.log('[HP] external ifaces:');
console.log(ifaces);
} }
ifaces.forEach(function (iface) { opts.ifaces.forEach(function (iface) {
promises.push(requestAsync({ promises.push(requestAsync({
family: iface.family family: iface.family
, method: 'GET' , method: 'GET'

View File

@ -1,23 +1,35 @@
'use strict'; 'use strict';
var PromiseA = require('bluebird'); 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) { if (args.debug) {
console.log('[HP] create holepuncher'); console.log('[HP] create holepuncher');
console.log(args); console.log(args);
} }
var servers = loopback.create(args); var interfaces = os.networkInterfaces();
//var promises = []; var ifacenames = Object.keys(interfaces).filter(function (ifacename) {
// http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/
if (args.debug) { // https://wiki.archlinux.org/index.php/Network_configuration#Device_names
console.log('[HP] create servers'); // we do not include tun and bridge devices because we're trying
//console.log(servers); // to see if any physical interface is internet-connected first
} return /^(en|sl|wl|ww|eth|net|lan|wifi|inet)/.test(ifacename);
});
function getExternalIps() { 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) { return PromiseA.any(args.ipifyUrls.map(function (ipifyUrl) {
var getIp = require('./external-ip'); var getIp = require('./external-ip');
@ -87,29 +99,39 @@ module.exports.create = function (args) {
} }
if (!opts.pretest) { if (!opts.pretest) {
return ip; return PromiseA.reject(new Error("[not an error]: skip the loopback test"));
} }
return testOpenPort(ip, opts.portInfo); return testOpenPort(ip, opts.portInfo);
})); }));
} }
return getExternalIps().then(function (ips) { args.ifaces = ifacenames.reduce(function (all, ifacename) {
var pretest = (-1 !== args.protocols.indexOf('none')); var ifs = interfaces[ifacename];
var portInfos = args.plainPorts.map(function (info) {
info.secure = false;
return info;
}).concat(args.tlsPorts.map(function (info) {
info.secure = true;
return info;
}));
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 // TODO clone-merge args
return testPort({ return testPort({
portInfo: info portInfo: mapping
, ips: ips , ips: ips
, pretest: pretest , pretest: mapping.loopback
}); });
})).then(function (portInfos) { })).then(function (portInfos) {
if (args.debug) { if (args.debug) {
@ -119,27 +141,29 @@ module.exports.create = function (args) {
return portInfos; return portInfos;
}, function () { }, function () {
// at least one port could not be mapped // 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') if (args.upnp) {
|| -1 !== args.protocols.indexOf('ssdp')
) {
if (args.debug) { if (args.debug) {
console.log('[HP] will try upnp'); console.log('[HP] will try upnp');
} }
mappers.push(require('./upnp')); upnps.push(require('./upnp'));
} }
if (-1 !== args.protocols.indexOf('pmp') if (args.pmp) {
|| -1 !== args.protocols.indexOf('nat-pmp')
) {
if (args.debug) { if (args.debug) {
console.log('[HP] will try nat-pmp'); console.log('[HP] will try nat-pmp');
} }
mappers.push(require('./pmp')); pmps.push(require('./pmp'));
} }
return PromiseA.all(portInfos.map(function (portInfo) { 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) { return PromiseA.any(mappers.map(function (fn) {
var p = fn(args, ips, portInfo); var p = fn(args, ips, portInfo);
@ -149,6 +173,41 @@ module.exports.create = function (args) {
return p; 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 () { })).then(function () {
if (args.debug) { if (args.debug) {
console.log("[HP] all ports successfully mapped"); console.log("[HP] all ports successfully mapped");

View File

@ -16,18 +16,20 @@ module.exports.create = function (opts) {
app.use('/', middleware(opts)); app.use('/', middleware(opts));
(opts.plainPorts||[]).forEach(function (plainPort) { opts.mappings.forEach(function (mapping) {
var plainServer = http.createServer(); var server;
plainServer.__plainPort = plainPort;
plainServer.on('request', app);
results.plainServers.push(plainServer);
});
(opts.tlsPorts||[]).forEach(function (tlsPort) { if (false === mapping.secure) {
var tlsServer = https.createServer(httpsOptions); server = http.createServer();
tlsServer.__tlsPort = tlsPort; server.__plainPort = mapping;
tlsServer.on('request', app); server.on('request', app);
results.tlsServers.push(tlsServer); results.plainServers.push(server);
} else {
server = https.createServer(httpsOptions);
server.__tlsPort = mapping;
server.on('request', app);
results.tlsServers.push(server);
}
}); });
function onListen() { function onListen() {

View File

@ -37,6 +37,9 @@ function pmpForwardHelper(gw, portInfo) {
return new PromiseA(function (resolve, reject) { return new PromiseA(function (resolve, reject) {
// create a "client" instance connecting to your local gateway // create a "client" instance connecting to your local gateway
var client = natpmp.connect(gw); var client = natpmp.connect(gw);
client.on('error', function (err) {
reject(err);
});
function setPortForward() { function setPortForward() {
// setup a new port mapping // setup a new port mapping
@ -60,6 +63,8 @@ function pmpForwardHelper(gw, portInfo) {
// public: 2222, // public: 2222,
// ... // ...
// } // }
client.close();
resolve(); resolve();
}); });
} }
@ -96,6 +101,7 @@ module.exports = function (args, ips, portInfo) {
module.exports.pmpForward = pmpForward; module.exports.pmpForward = pmpForward;
/*
function usage() { function usage() {
console.warn(""); console.warn("");
console.warn("node helpers/pmp-forward [public port] [private port] [ttl]"); console.warn("node helpers/pmp-forward [public port] [private port] [ttl]");
@ -121,3 +127,4 @@ function run() {
if (require.main === module) { if (require.main === module) {
run(); run();
} }
*/

View File

@ -2,6 +2,7 @@
var PromiseA = require('bluebird').Promise; var PromiseA = require('bluebird').Promise;
var natUpnp = require('holepunch-upnp'); var natUpnp = require('holepunch-upnp');
var client;
function upnpForward(opts) { function upnpForward(opts) {
if (opts.debug) { if (opts.debug) {
@ -9,7 +10,7 @@ function upnpForward(opts) {
console.log(opts); console.log(opts);
} }
return natUpnp.createClient({ timeout: 3 * 1000 }).then(function (client) { function useClient(client) {
if (opts.debug) { if (opts.debug) {
console.log('[HP] [upnp] created client'); console.log('[HP] [upnp] created client');
console.log(client); console.log(client);
@ -39,8 +40,17 @@ function upnpForward(opts) {
return promitter; 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) { module.exports = function (args, ips, portInfo) {
// TODO ips.forEach // TODO ips.forEach