168 lines
5.3 KiB
JavaScript
168 lines
5.3 KiB
JavaScript
'use strict';
|
|
|
|
module.exports.create = function (deps, conf) {
|
|
var network = deps.PromiseA.promisifyAll(deps.recase.camelCopy(require('network')));
|
|
var loopback = require('./loopback').create(deps, conf);
|
|
var dnsCtrl = require('./dns-ctrl').create(deps, conf);
|
|
|
|
var loopbackDomain;
|
|
function updateConf() {
|
|
loopbackDomain = 'oauth3.org';
|
|
if (conf.ddns && conf.ddns.loopback) {
|
|
if (conf.ddns.loopback.type === 'tunnel@oauth3.org' && conf.ddns.loopback.domain) {
|
|
loopbackDomain = conf.ddns.loopback.domain;
|
|
} else {
|
|
console.error('invalid loopback configuration: bad type or missing domain');
|
|
}
|
|
}
|
|
}
|
|
updateConf();
|
|
|
|
function iterateAllModules(action) {
|
|
var promises = conf.ddns.modules.map(function (mod) {
|
|
return action(mod, mod.domains);
|
|
});
|
|
|
|
conf.domains.forEach(function (dom) {
|
|
if (!dom.modules || !Array.isArray(dom.modules.ddns) || !dom.modules.ddns.length) {
|
|
return null;
|
|
}
|
|
|
|
// For the time being all of our things should only be tried once (regardless if it succeeded)
|
|
// TODO: revisit this behavior when we support multiple ways of setting records, and/or
|
|
// if we want to allow later modules to run if early modules fail.
|
|
promises.push(dom.modules.ddns.reduce(function (prom, mod) {
|
|
if (prom) { return prom; }
|
|
return action(mod, dom.names);
|
|
}, null));
|
|
});
|
|
|
|
return deps.PromiseA.all(promises.filter(Boolean));
|
|
}
|
|
|
|
var tunnelActive = false;
|
|
async function connectTunnel() {
|
|
var sessionCache = {};
|
|
var sessionOverride;
|
|
if (conf.ddns.tunnel) {
|
|
sessionOverride = await deps.storage.tokens.get(conf.ddns.tunnel.tokenId);
|
|
}
|
|
async function getSession(id) {
|
|
if (sessionOverride) {
|
|
return sessionOverride;
|
|
}
|
|
if (!sessionCache.hasOwnProperty(id)) {
|
|
sessionCache[id] = await deps.storage.tokens.get(conf.ddns.tunnel.tokenId);
|
|
}
|
|
if (!sessionCache[id]) {
|
|
throw new Error('no user token with ID "'+id+'"');
|
|
}
|
|
return sessionCache[id];
|
|
}
|
|
|
|
await iterateAllModules(function startTunnel(mod, domainsList) {
|
|
if (mod.type !== 'dns@oauth3.org') { return null; }
|
|
|
|
return getSession(mod.token_id).then(function (session) {
|
|
return deps.tunnelClients.start(session, domainsList);
|
|
}).catch(function (err) {
|
|
console.log('error starting tunnel for', domainsList.join(', '));
|
|
console.log(err);
|
|
});
|
|
});
|
|
|
|
tunnelActive = true;
|
|
}
|
|
async function disconnectTunnel() {
|
|
deps.tunnelClients.disconnect();
|
|
tunnelActive = false;
|
|
}
|
|
|
|
var localAddr, gateway;
|
|
async function checkNetworkEnv() {
|
|
// Since we can't detect the OS level events when a user plugs in an ethernet cable to recheck
|
|
// what network environment we are in we check our local network address and the gateway to
|
|
// determine if we need to run the loopback check and router configuration again.
|
|
var gw = await network.getGatewayIpAsync();
|
|
var addr = await network.getPrivateIpAsync();
|
|
if (localAddr === addr && gateway === gw) {
|
|
return;
|
|
}
|
|
|
|
localAddr = addr;
|
|
gateway = gw;
|
|
var loopResult = await loopback(loopbackDomain);
|
|
var notLooped = Object.keys(loopResult.ports).filter(function (port) {
|
|
return !loopResult.ports[port];
|
|
});
|
|
|
|
// if (notLooped.length) {
|
|
// // TODO: try to automatically configure router to forward ports to us.
|
|
// }
|
|
|
|
// If we are on a public address or all ports we are listening on are forwarded to us then
|
|
// we don't need the tunnel and we can set the DNS records for all our domains to our public
|
|
// address. Otherwise we need to use the tunnel to accept traffic.
|
|
if (!notLooped.length) {
|
|
if (tunnelActive) {
|
|
await disconnectTunnel();
|
|
}
|
|
} else {
|
|
if (!tunnelActive) {
|
|
await connectTunnel();
|
|
}
|
|
}
|
|
}
|
|
|
|
var publicAddress;
|
|
async function recheckPubAddr() {
|
|
await checkNetworkEnv();
|
|
if (tunnelActive) {
|
|
return;
|
|
}
|
|
|
|
var addr = await loopback.checkPublicAddr(loopbackDomain);
|
|
if (publicAddress === addr) {
|
|
return;
|
|
}
|
|
|
|
if (conf.debug) {
|
|
console.log('previous public address',publicAddress, 'does not match current public address', addr);
|
|
}
|
|
publicAddress = addr;
|
|
|
|
var sessionCache = {};
|
|
async function getSession(id) {
|
|
if (!sessionCache.hasOwnProperty(id)) {
|
|
sessionCache[id] = await deps.storage.tokens.get(conf.ddns.tunnel.tokenId);
|
|
}
|
|
if (!sessionCache[id]) {
|
|
throw new Error('no user token with ID "'+id+'"');
|
|
}
|
|
return sessionCache[id];
|
|
}
|
|
|
|
await iterateAllModules(function setModuleDNS(mod, domainsList) {
|
|
if (mod.type !== 'dns@oauth3.org' || mod.disabled) { return null; }
|
|
|
|
return getSession(mod.token_id).then(function (session) {
|
|
return dnsCtrl.setDeviceAddress(session, addr, domainsList);
|
|
}).catch(function (err) {
|
|
console.log('error setting DNS records for', domainsList.join(', '));
|
|
console.log(err);
|
|
});
|
|
});
|
|
}
|
|
|
|
recheckPubAddr();
|
|
setInterval(recheckPubAddr, 5*60*1000);
|
|
|
|
return {
|
|
loopbackServer: loopback.server
|
|
, setDeviceAddress: dnsCtrl.setDeviceAddress
|
|
, getDeviceAddresses: dnsCtrl.getDeviceAddresses
|
|
, recheckPubAddr: recheckPubAddr
|
|
, updateConf: updateConf
|
|
};
|
|
};
|