demos, stubs, and scraps
This commit is contained in:
parent
82245c2d45
commit
04e788ddb4
|
@ -0,0 +1,83 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var PromiseA = require('bluebird').Promise;
|
||||||
|
var updateIp = require('./helpers/update-ip.js').update;
|
||||||
|
var request = PromiseA.promisifyAll(require('request'));
|
||||||
|
var requestAsync = PromiseA.promisify(require('request'));
|
||||||
|
var upnpForward = require('./helpers/upnp-forward').upnpForward;
|
||||||
|
var pmpForward = require('./helpers/pmp-forward').pmpForward;
|
||||||
|
var loopbackHttps = require('./loopback-https');
|
||||||
|
//var checkip = require('check-ip-address');
|
||||||
|
|
||||||
|
function openPort(ip, port) {
|
||||||
|
if (!/tcp|https|http/.test(port.protocol || 'tcp')) {
|
||||||
|
throw new Error('not yet supported \'' + port.protocol + '\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === port.testable) {
|
||||||
|
return PromiseA.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return loopbackHttps.create(ip, port.private, port.public).then(function () {
|
||||||
|
console.log('success');
|
||||||
|
}).catch(function (err) {
|
||||||
|
// TODO test err
|
||||||
|
return upnpForward(port).catch(function (err) {
|
||||||
|
console.error('[ERROR] UPnP Port Forward');
|
||||||
|
console.error(err);
|
||||||
|
// TODO test err
|
||||||
|
return pmpForward(port);
|
||||||
|
}).then(function () {
|
||||||
|
return loopbackHttps.create(ip, port.private, port.public);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. update dyndns
|
||||||
|
// 1.5. check ip every 5 min
|
||||||
|
// 2. loopback test on ip for http / https / ssh
|
||||||
|
// 3. if needed: discover gateway, map ports
|
||||||
|
function beacon(hostnames, ports) {
|
||||||
|
// test with
|
||||||
|
// dig -p 53 @redirect-www.org pi.nadal.daplie.com A
|
||||||
|
return updateIp({
|
||||||
|
updater: 'redirect-www.org'
|
||||||
|
, port: 65443
|
||||||
|
, ddns: hostnames.map(function (hostname) {
|
||||||
|
return { "name": hostname /*, "value": ipaddress, "type": "A"*/ };
|
||||||
|
})
|
||||||
|
}).then(function (data) {
|
||||||
|
var promises = [];
|
||||||
|
|
||||||
|
console.log("Updated DynDNS");
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
ports.forEach(function (port) {
|
||||||
|
promises.push(openPort(JSON.parse(data)[0].answers[0] || hostname, port));
|
||||||
|
});
|
||||||
|
|
||||||
|
return PromiseA.all(promises);
|
||||||
|
}).then(function () {
|
||||||
|
console.log('opened ports');
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
request.getAsync('http://checkip.hellabit.com').spread(function (resp, data) {
|
||||||
|
console.log("External IP is", data);
|
||||||
|
}).then(function () {
|
||||||
|
return upnpForward().catch(function (err) {
|
||||||
|
console.error('ERROR: UPnP failure:');
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}).then(function () {
|
||||||
|
return pmpForward().catch(function (err) {
|
||||||
|
console.error('TODO: Notify user that their router is not compatible');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO test roundtrip
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
//setInterval(beacon, 5 * 60 * 1000);
|
||||||
|
exports.run = beacon;
|
|
@ -0,0 +1,81 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var PromiseA = require('bluebird').Promise;
|
||||||
|
var natpmp = require('nat-pmp');
|
||||||
|
var exec = require('child_process').exec;
|
||||||
|
|
||||||
|
exports.pmpForward = function (port) {
|
||||||
|
return new PromiseA(function (resolve, reject) {
|
||||||
|
exec('ip route show default', function (err, stdout, stderr) {
|
||||||
|
var gw;
|
||||||
|
|
||||||
|
if (err || stderr) { reject(err || stderr); return; }
|
||||||
|
|
||||||
|
// default via 192.168.1.1 dev eth0
|
||||||
|
gw = stdout.replace(/^default via (\d+\.\d+\.\d+\.\d+) dev[\s\S]+/m, '$1');
|
||||||
|
console.log('Possible PMP gateway is', gw);
|
||||||
|
|
||||||
|
// create a "client" instance connecting to your local gateway
|
||||||
|
var client = natpmp.connect(gw);
|
||||||
|
|
||||||
|
function setPortForward() {
|
||||||
|
// setup a new port mapping
|
||||||
|
client.portMapping({
|
||||||
|
private: port.private || port.public
|
||||||
|
, public: port.public || port.private
|
||||||
|
, ttl: port.ttl || 0 // 600
|
||||||
|
}, function (err, info) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(info);
|
||||||
|
// {
|
||||||
|
// type: 'tcp',
|
||||||
|
// epoch: 8922109,
|
||||||
|
// private: 22,
|
||||||
|
// public: 2222,
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// explicitly ask for the current external IP address
|
||||||
|
setTimeout(function () {
|
||||||
|
client.externalIp(function (err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
console.log('Current external IP address: %s', info.ip.join('.'));
|
||||||
|
setPortForward();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
console.warn("");
|
||||||
|
console.warn("node helpers/pmp-forward [public port] [private port] [ttl]");
|
||||||
|
console.warn("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
var pubPort = parseInt(process.argv[2], 10) || 0;
|
||||||
|
var privPort = parseInt(process.argv[3], 10) || pubPort;
|
||||||
|
var ttl = parseInt(process.argv[4], 10) || 0;
|
||||||
|
var options = { public: pubPort, private: privPort, ttl: ttl };
|
||||||
|
|
||||||
|
if (!pubPort) {
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.pmpForward(options).then(function () {
|
||||||
|
console.log('done');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
run();
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var PromiseA = require('bluebird').Promise;
|
||||||
|
var natUpnp = require('nat-upnp');
|
||||||
|
|
||||||
|
exports.upnpForward = function (port) {
|
||||||
|
return natUpnp.createClient({ timeout: 1800 }).then(function (client) {
|
||||||
|
return client.portMapping({
|
||||||
|
public: port.public,
|
||||||
|
private: port.private || port.public,
|
||||||
|
ttl: port.ttl || 0
|
||||||
|
})/*.then(function () {
|
||||||
|
var promitter = client.getMappings();
|
||||||
|
|
||||||
|
promitter.on('entry', function (entry, i) {
|
||||||
|
console.log('entry', i);
|
||||||
|
console.log(entry);
|
||||||
|
}).then(function (mappings) {
|
||||||
|
console.log('mappings');
|
||||||
|
console.log(mappings);
|
||||||
|
});
|
||||||
|
|
||||||
|
return promitter;
|
||||||
|
})*/;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
client.portUnmapping({
|
||||||
|
public: 80
|
||||||
|
});
|
||||||
|
|
||||||
|
.findGateway().then(function (stuff) {
|
||||||
|
console.log('[a] gateway');
|
||||||
|
console.log(stuff.gateway);
|
||||||
|
console.log('[a] address');
|
||||||
|
console.log(stuff.address);
|
||||||
|
}).then(function () {
|
||||||
|
return client
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
client.getMappings({ local: true }, function(err, results) {
|
||||||
|
console.log('local mappings', results);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.externalIp(function(err, ip) {
|
||||||
|
console.log('ext-ip', ip);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
console.warn("");
|
||||||
|
console.warn("node helpers/upnp-forward [public port] [private port] [ttl]");
|
||||||
|
console.warn("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
var pubPort = parseInt(process.argv[2], 10) || 0;
|
||||||
|
var privPort = parseInt(process.argv[3], 10) || pubPort;
|
||||||
|
var ttl = parseInt(process.argv[4], 10) || 0;
|
||||||
|
var options = { public: pubPort, private: privPort, ttl: ttl };
|
||||||
|
|
||||||
|
if (!pubPort) {
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.upnpForward(options).then(function () {
|
||||||
|
console.log('done');
|
||||||
|
}).catch(function (err) {
|
||||||
|
console.error('ERROR');
|
||||||
|
console.error(err);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
run();
|
||||||
|
return;
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var https = require('https');
|
||||||
|
var path = require('path');
|
||||||
|
var fs = require('fs');
|
||||||
|
var PromiseA = global.Promise || require('bluebird').Promise;
|
||||||
|
|
||||||
|
exports.create = function (ip, localPort, externalPort) {
|
||||||
|
return new PromiseA(function (resolve, reject) {
|
||||||
|
var token = Math.random().toString(16).split('.')[1];
|
||||||
|
var tokenPath = Math.random().toString(16).split('.')[1];
|
||||||
|
var options;
|
||||||
|
var server;
|
||||||
|
var options;
|
||||||
|
var certsPath = path.join(__dirname, 'certs', 'server');
|
||||||
|
var caCertsPath = path.join(__dirname, 'certs', 'ca');
|
||||||
|
|
||||||
|
|
||||||
|
function testConnection() {
|
||||||
|
var awesome = false;
|
||||||
|
var timetok;
|
||||||
|
var webreq;
|
||||||
|
var options = {
|
||||||
|
// not hostname because we set headers.host on our own
|
||||||
|
host: ip
|
||||||
|
, headers: {
|
||||||
|
// whatever's on the fake cert
|
||||||
|
'Host': 'redirect-www.org'
|
||||||
|
}
|
||||||
|
, port: externalPort
|
||||||
|
, path: '/' + tokenPath
|
||||||
|
, ca: fs.readFileSync(path.join(caCertsPath, 'my-root-ca.crt.pem'))
|
||||||
|
};
|
||||||
|
options.agent = new https.Agent(options);
|
||||||
|
|
||||||
|
timetok = setTimeout(function () {
|
||||||
|
reject(new Error("timed out while testing NAT loopback for port " + externalPort));
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
function finishHim(err) {
|
||||||
|
clearTimeout(timetok);
|
||||||
|
server.close(function () {
|
||||||
|
if (!err && awesome) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (err || !awesome) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
else if (!awesome) {
|
||||||
|
reject(new Error("loopback failed. Why? here's my best guess: "
|
||||||
|
+ "the ssl cert matched, so you've probably got two boxes and this isn't the right one"));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webreq = https.request(options, function(res) {
|
||||||
|
res.on('data', function (resToken) {
|
||||||
|
if (resToken.toString() === token) {
|
||||||
|
awesome = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.on('error', function (err) {
|
||||||
|
console.error('[ERROR] https.request.response');
|
||||||
|
console.error(err);
|
||||||
|
finishHim(new Error("loopback failed. Why? here's my best guess: "
|
||||||
|
+ "the connection was interrupted"));
|
||||||
|
});
|
||||||
|
res.on('end', function () {
|
||||||
|
finishHim();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
webreq.on('error', function (err) {
|
||||||
|
console.error('[ERROR] https.request');
|
||||||
|
console.error(err);
|
||||||
|
if (/ssl|cert|chain/i.test(err.message || err.toString())) {
|
||||||
|
finishHim(new Error("loopback failed. Why? here's my best guess: "
|
||||||
|
+ "the ssl cert validation may have failed (might port-forward to the wrong box)"));
|
||||||
|
} else {
|
||||||
|
finishHim(new Error("loopback failed. Why? here's my best guess: "
|
||||||
|
+ "port forwarding isn't configured for " + ip + ":" + externalPort + " to " + localPort));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
webreq.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// SSL Certificates
|
||||||
|
//
|
||||||
|
options = {
|
||||||
|
key: fs.readFileSync(path.join(certsPath, 'my-server.key.pem'))
|
||||||
|
, ca: [ fs.readFileSync(path.join(caCertsPath, 'my-root-ca.crt.pem')) ]
|
||||||
|
, cert: fs.readFileSync(path.join(certsPath, 'my-server.crt.pem'))
|
||||||
|
, requestCert: false
|
||||||
|
, rejectUnauthorized: false
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Serve an Express App securely with HTTPS
|
||||||
|
//
|
||||||
|
server = https.createServer(options);
|
||||||
|
function listen(app) {
|
||||||
|
server.on('request', app);
|
||||||
|
server.listen(localPort, function () {
|
||||||
|
localPort = server.address().port;
|
||||||
|
setTimeout(testConnection, 2000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
listen(function (req, res) {
|
||||||
|
if (('/' + tokenPath) === req.url) {
|
||||||
|
res.end(token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end('loopback failure');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,49 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
//var config = require('./device.json');
|
||||||
|
|
||||||
|
// require('ssl-root-cas').inject();
|
||||||
|
// TODO try SNI loopback.example.com as result of api.ipify.com with loopback token
|
||||||
|
|
||||||
|
function phoneHome() {
|
||||||
|
var holepunch = require('./holepunch/beacon');
|
||||||
|
var ports;
|
||||||
|
|
||||||
|
ports = [
|
||||||
|
{ private: 65022
|
||||||
|
, public: 65022
|
||||||
|
, protocol: 'tcp'
|
||||||
|
, ttl: 0
|
||||||
|
, test: { service: 'ssh' }
|
||||||
|
, testable: false
|
||||||
|
}
|
||||||
|
, { private: 650443
|
||||||
|
, public: 650443
|
||||||
|
, protocol: 'tcp'
|
||||||
|
, ttl: 0
|
||||||
|
, test: { service: 'https' }
|
||||||
|
}
|
||||||
|
, { private: 65080
|
||||||
|
, public: 65080
|
||||||
|
, protocol: 'tcp'
|
||||||
|
, ttl: 0
|
||||||
|
, test: { service: 'http' }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// TODO return a middleware
|
||||||
|
holepunch.run(require('./redirects.json').reduce(function (all, redirect) {
|
||||||
|
if (!all[redirect.from.hostname]) {
|
||||||
|
all[redirect.from.hostname] = true;
|
||||||
|
all.push(redirect.from.hostname);
|
||||||
|
}
|
||||||
|
if (!all[redirect.to.hostname]) {
|
||||||
|
all[redirect.to.hostname] = true;
|
||||||
|
all.push(redirect.to.hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
return all;
|
||||||
|
}, []), ports).catch(function () {
|
||||||
|
console.error("Couldn't phone home. Oh well");
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue