diff --git a/lib/ddns/dns-ctrl.js b/lib/ddns/dns-ctrl.js index f2421c0..2c8912b 100644 --- a/lib/ddns/dns-ctrl.js +++ b/lib/ddns/dns-ctrl.js @@ -98,12 +98,20 @@ module.exports.create = function (deps, conf) { return domains.indexOf(record.host) !== -1; }); - var oldDomains = ourDns.filter(function (record) { + // Of all the DNS records referring to our device and the current list of domains determine + // which domains have records with outdated address, and which ones we can just leave be + // without updating them. + var badAddrDomains = ourDns.filter(function (record) { return record.value !== addr; - }).map(function (record) { - return record.host; + }).map(record => record.host); + var goodAddrDomains = ourDns.filter(function (record) { + return record.value === addr && badAddrDomains.indexOf(record.host) < 0; + }).map(record => record.host); + var requiredUpdates = domains.filter(function (domain) { + return goodAddrDomains.indexOf(domain) !== -1; }); - var oldDns = await splitDomains(directives.api, oldDomains); + + var oldDns = await splitDomains(directives.api, badAddrDomains); var common = { api: 'devices.detach' , session: session @@ -113,7 +121,7 @@ module.exports.create = function (deps, conf) { return deps.OAUTH3.api(directives.api, Object.assign({}, common, record)); })); - var newDns = await splitDomains(directives.api, domains); + var newDns = await splitDomains(directives.api, requiredUpdates); common = { api: 'devices.attach' , session: session diff --git a/lib/ddns/index.js b/lib/ddns/index.js index 74cdc99..60878cf 100644 --- a/lib/ddns/index.js +++ b/lib/ddns/index.js @@ -1,6 +1,7 @@ 'use strict'; module.exports.create = function (deps, conf) { + var dns = deps.PromiseA.promisifyAll(require('dns')); 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); @@ -40,7 +41,31 @@ module.exports.create = function (deps, conf) { } var tunnelActive = false; - async function connectTunnel() { + async function startTunnel(tunnelSession, mod, domainList) { + try { + var dnsSession = await getSession(mod.tokenId); + var tunnelDomain = await deps.tunnelClients.start(tunnelSession || dnsSession, domainList); + + var addrList; + try { + addrList = await dns.resolve4Async(tunnelDomain); + } catch (e) {} + if (!addrList || !addrList.length) { + try { + addrList = await dns.resolve6Async(tunnelDomain); + } catch (e) {} + } + if (!addrList || !addrList.length || !addrList[0]) { + throw new Error('failed to lookup IP for tunnel domain "' + tunnelDomain + '"'); + } + + await dnsCtrl.setDeviceAddress(dnsSession, addrList[0], domainList); + } catch (err) { + console.log('error starting tunnel for', domainList.join(', ')); + console.log(err); + } + } + async function connectAllTunnels() { var tunnelSession; if (conf.ddns.tunnel) { // In the case of a non-existant token, I'm not sure if we want to throw here and prevent @@ -48,20 +73,15 @@ module.exports.create = function (deps, conf) { tunnelSession = await deps.storage.tokens.get(conf.ddns.tunnel.tokenId); } - await iterateAllModules(function startTunnel(mod, domainList) { + await iterateAllModules(function (mod, domainList) { if (mod.type !== 'dns@oauth3.org') { return null; } - return getSession(mod.tokenId).then(function (dnsSession) { - return deps.tunnelClients.start(tunnelSession || dnsSession, domainList); - }).catch(function (err) { - console.log('error starting tunnel for', domainList.join(', ')); - console.log(err); - }); + return startTunnel(tunnelSession, mod, domainList); }); tunnelActive = true; } - async function disconnectTunnel() { + async function disconnectTunnels() { deps.tunnelClients.disconnect(); tunnelActive = false; await Promise.resolve(); @@ -94,13 +114,8 @@ module.exports.create = function (deps, conf) { tunnelSession = await deps.storage.tokens.get(conf.ddns.tunnel.tokenId); } - await Promise.all(newTokens.map(function startTunnel({mod, domainList}) { - return getSession(mod.tokenId).then(function (dnsSession) { - return deps.tunnelClients.start(tunnelSession || dnsSession, domainList); - }).catch(function (err) { - console.log('error starting tunnel for', domainList.join(', ')); - console.log(err); - }); + await Promise.all(newTokens.map(function ({mod, domainList}) { + return startTunnel(tunnelSession, mod, domainList); })); } @@ -131,11 +146,11 @@ module.exports.create = function (deps, conf) { // address. Otherwise we need to use the tunnel to accept traffic. if (!notLooped.length) { if (tunnelActive) { - await disconnectTunnel(); + await disconnectTunnels(); } } else { if (!tunnelActive) { - await connectTunnel(); + await connectAllTunnels(); } } } @@ -285,7 +300,7 @@ module.exports.create = function (deps, conf) { if (equal(curConf.ddns.tunnel, conf.ddns.tunnel)) { return checkTunnelTokens(); } else { - return disconnectTunnel().then(connectTunnel); + return disconnectTunnels().then(connectAllTunnels); } }).catch(function (err) { console.error('error transitioning DNS between configurations'); diff --git a/lib/tunnel-client-manager.js b/lib/tunnel-client-manager.js index 29105f5..8153245 100644 --- a/lib/tunnel-client-manager.js +++ b/lib/tunnel-client-manager.js @@ -92,6 +92,10 @@ module.exports.create = function (deps, config) { // to keep record of what domains we are handling and what tunnel server // those domains should go to. activeDomains[data.domains] = data; + + // This is mostly for the start, but return the host for the tunnel server + // we've connected to (after stripping the protocol and path away). + return data.tunnelUrl.replace(/^[a-z]*:\/\//i, '').replace(/\/.*/, ''); } async function acquireToken(session, domains) { @@ -118,7 +122,7 @@ module.exports.create = function (deps, config) { var directives = await OAUTH3.discover(session.token.aud); var tokenData = await OAUTH3.api(directives.api, opts); - await addToken(tokenData); + return addToken(tokenData); } function disconnectAll() {