working better
This commit is contained in:
parent
d11a51ff90
commit
bef1fbaba4
|
@ -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: []
|
||||
|
|
|
@ -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' ]
|
||||
, '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);
|
||||
|
|
|
@ -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();
|
|
@ -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'
|
||||
|
|
121
lib/index.js
121
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");
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
*/
|
||||
|
|
14
lib/upnp.js
14
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) {
|
||||
|
|
Loading…
Reference in New Issue