From 82f0b45c56dc36b26602bb065335226a8f6a5225 Mon Sep 17 00:00:00 2001 From: tigerbot Date: Fri, 20 Oct 2017 15:38:10 -0600 Subject: [PATCH] implemented cleanup/update of DNS records on config change --- lib/ddns/dns-ctrl.js | 25 +++++++-- lib/ddns/index.js | 119 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 127 insertions(+), 17 deletions(-) diff --git a/lib/ddns/dns-ctrl.js b/lib/ddns/dns-ctrl.js index ed290ac..f2421c0 100644 --- a/lib/ddns/dns-ctrl.js +++ b/lib/ddns/dns-ctrl.js @@ -23,7 +23,7 @@ module.exports.create = function (deps, conf) { var tldObj = {}; resp.data.forEach(function (tldInfo) { if (tldInfo.enabled) { - tldObj[tldInfo.com] = true; + tldObj[tldInfo.tld] = true; } }); @@ -60,8 +60,8 @@ module.exports.create = function (deps, conf) { // rare even if the assumption isn't valid. return { tld: split.slice(-tldSegCnt).join('.') - , sld: split.slice(-tldSegCnt-1, 1) - , sub: split.slice(0, -tldSegCnt-1) + , sld: split.slice(-tldSegCnt-1, -tldSegCnt).join('.') + , sub: split.slice(0, -tldSegCnt-1).join('.') }; }); } @@ -152,8 +152,23 @@ module.exports.create = function (deps, conf) { return (dev || {}).addresses || []; } + async function removeDomains(session, domains) { + var directives = await deps.OAUTH3.discover(session.token.aud); + + var oldDns = await splitDomains(directives.api, domains); + var common = { + api: 'devices.detach' + , session: session + , device: conf.device.hostname + }; + await deps.PromiseA.all(oldDns.map(function (record) { + return deps.OAUTH3.api(directives.api, Object.assign({}, common, record)); + })); + } + return { - getDeviceAddresses: getDeviceAddresses - , setDeviceAddress: setDeviceAddress + getDeviceAddresses + , setDeviceAddress + , removeDomains }; }; diff --git a/lib/ddns/index.js b/lib/ddns/index.js index 036bb02..74cdc99 100644 --- a/lib/ddns/index.js +++ b/lib/ddns/index.js @@ -8,12 +8,13 @@ module.exports.create = function (deps, conf) { var loopbackDomain; - function iterateAllModules(action) { - var promises = conf.ddns.modules.map(function (mod) { + function iterateAllModules(action, curConf) { + curConf = curConf || conf; + var promises = curConf.ddns.modules.map(function (mod) { return action(mod, mod.domains); }); - conf.domains.forEach(function (dom) { + curConf.domains.forEach(function (dom) { if (!dom.modules || !Array.isArray(dom.modules.ddns) || !dom.modules.ddns.length) { return null; } @@ -168,6 +169,87 @@ module.exports.create = function (deps, conf) { }); } + function getModuleDiffs(prevConf) { + var prevMods = {}; + var curMods = {}; + + // this returns a Promise, but since the functions we use are synchronous + // and change our enclosed variables we don't need to wait for the return. + iterateAllModules(function (mod, domainList) { + if (mod.type !== 'dns@oauth3.org') { return; } + + prevMods[mod.id] = { mod, domainList }; + return true; + }, prevConf); + iterateAllModules(function (mod, domainList) { + if (mod.type !== 'dns@oauth3.org') { return; } + + curMods[mod.id] = { mod, domainList }; + return true; + }); + + // Filter out all of the modules that are exactly the same including domainList + // since there is no required action to transition. + Object.keys(prevMods).map(function (id) { + if (equal(prevMods[id], curMods[id])) { + delete prevMods[id]; + delete curMods[id]; + } + }); + + return {prevMods, curMods}; + } + async function cleanOldDns(prevConf) { + var {prevMods, curMods} = getModuleDiffs(prevConf); + + // Then remove DNS records for the domains that we are no longer responsible for. + await Promise.all(Object.values(prevMods).map(function ({mod, domainList}) { + var oldDomains; + if (!curMods[mod.id] || mod.tokenId !== curMods[mod.id].mod.tokenId) { + oldDomains = domainList.slice(); + } else { + oldDomains = domainList.filter(function (domain) { + return curMods[mod.id].domainList.indexOf(domain) < 0; + }); + } + if (conf.debug) { + console.log('removing old domains for module', mod.id, oldDomains.join(', ')); + } + if (!oldDomains.length) { + return; + } + + return getSession(mod.tokenId).then(function (session) { + return dnsCtrl.removeDomains(session, oldDomains); + }); + }).filter(Boolean)); + } + async function setNewDns(prevConf) { + var {prevMods, curMods} = getModuleDiffs(prevConf); + + // And add DNS records for any newly added domains. + await Promise.all(Object.values(curMods).map(function ({mod, domainList}) { + var newDomains; + if (!prevMods[mod.id] || mod.tokenId !== prevMods[mod.id].mod.tokenId) { + newDomains = domainList.slice(); + } else { + newDomains = domainList.filter(function (domain) { + return prevMods[mod.id].domainList.indexOf(domain) < 0; + }); + } + if (conf.debug) { + console.log('adding new domains for module', mod.id, newDomains.join(', ')); + } + if (!newDomains.length) { + return; + } + + return getSession(mod.tokenId).then(function (session) { + return dnsCtrl.setDeviceAddress(session, publicAddress, newDomains); + }); + }).filter(Boolean)); + } + recheckPubAddr(); setInterval(recheckPubAddr, 5*60*1000); @@ -189,17 +271,30 @@ module.exports.create = function (deps, conf) { } } - if (tunnelActive) { - if (!curConf || !equal(curConf.ddns.tunnel, conf.ddns.tunnel)) { - disconnectTunnel().then(connectTunnel); - } else { - checkTunnelTokens(); - } + if (!curConf) { + // We need to make a deep copy of the config so we can use it next time to + // compare and see what setup/cleanup is needed to adapt to the changes. + curConf = JSON.parse(JSON.stringify(conf)); + return; } - // We need to make a deep copy of the config so we can use it next time to - // compare and see what setup/cleanup is needed to adapt to the changes. - curConf = JSON.parse(JSON.stringify(conf)); + cleanOldDns(curConf).then(function () { + if (!tunnelActive) { + return setNewDns(curConf); + } + if (equal(curConf.ddns.tunnel, conf.ddns.tunnel)) { + return checkTunnelTokens(); + } else { + return disconnectTunnel().then(connectTunnel); + } + }).catch(function (err) { + console.error('error transitioning DNS between configurations'); + console.error(err); + }).then(function () { + // We need to make a deep copy of the config so we can use it next time to + // compare and see what setup/cleanup is needed to adapt to the changes. + curConf = JSON.parse(JSON.stringify(conf)); + }); } updateConf();