Files
cloudron-box/src/dns/waitfordns.js
T
Girish Ramakrishnan 0373fb70d5 make waitForDns async
cloudflare is partly broken
2022-02-03 17:35:45 -08:00

102 lines
4.2 KiB
JavaScript

'use strict';
exports = module.exports = waitForDns;
const assert = require('assert'),
BoxError = require('../boxerror.js'),
debug = require('debug')('box:dns/waitfordns'),
dig = require('../dig.js'),
promiseRetry = require('../promise-retry.js'),
safe = require('safetydance');
async function resolveIp(hostname, type, options) {
assert.strictEqual(typeof hostname, 'string');
assert(type === 'A' || type === 'AAAA');
assert.strictEqual(typeof options, 'object');
// try A record at authoritative server
debug(`resolveIp: Checking if ${hostname} has ${type} record at ${options.server}`);
const [error, results] = await safe(dig.resolve(hostname, type, options));
if (!error && results.length !== 0) return results;
// try CNAME record at authoritative server
debug(`resolveIp: Checking if ${hostname} has CNAME record at ${options.server}`);
const cnameResults = await dig.resolve(hostname, 'CNAME', options);
if (cnameResults.length === 0) return cnameResults;
// recurse lookup the CNAME record
debug(`resolveIp: Resolving ${hostname}'s CNAME record ${results[0]}`);
await dig.resolve(results[0], type, options);
}
async function isChangeSynced(hostname, type, value, nameserver) {
assert.strictEqual(typeof hostname, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof value, 'string');
assert.strictEqual(typeof nameserver, 'string');
assert.strictEqual(typeof callback, 'function');
// ns records cannot have cname
const [error, nsIps] = await safe(dig.resolve(nameserver, 'A', { timeout: 5000 }));
if (error || !nsIps || nsIps.length === 0) {
debug(`isChangeSynced: cannot resolve NS ${nameserver}`); // it's fine if one or more ns are dead
return true;
}
const status = [];
for (let i = 0; i < nsIps.length; i++) {
const nsIp = nsIps[i];
const resolveOptions = { server: nsIp, timeout: 5000 };
const resolver = type === 'A' || type === 'AAAA' ? resolveIp(hostname, type, resolveOptions) : dig.resolve(hostname, 'TXT', resolveOptions);
const [error, answer] = await safe(resolver);
if (error && error.code === 'TIMEOUT') {
debug(`isChangeSynced: NS ${nameserver} (${nsIp}) timed out when resolving ${hostname} (${type})`);
status[i] = true; // should be ok if dns server is down
continue;
}
if (error) {
debug(`isChangeSynced: NS ${nameserver} (${nsIp}) errored when resolve ${hostname} (${type}): ${error}`);
status[i] = false;
continue;
}
let match;
if (type === 'A' || type === 'AAAA') {
match = answer.length === 1 && answer[0] === value;
} else if (type === 'TXT') { // answer is a 2d array of strings
match = answer.some(function (a) { return value === a.join(''); });
}
debug(`isChangeSynced: ${hostname} (${type}) was resolved to ${answer} at NS ${nameserver} (${nsIp}). Expecting ${value}. Match ${match}`);
status[i] = match;
}
return status.every(s => s === true);
}
// check if IP change has propagated to every nameserver
async function waitForDns(hostname, zoneName, type, value, options) {
assert.strictEqual(typeof hostname, 'string');
assert.strictEqual(typeof zoneName, 'string');
assert(type === 'A' || type === 'AAAA' || type === 'TXT');
assert.strictEqual(typeof value, 'string');
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
debug(`waitForDns: ${hostname} to be ${value} in zone ${zoneName}`);
await promiseRetry(Object.assign({ debug }, options), async function () {
const nameservers = await dig.resolve(zoneName, 'NS', { timeout: 5000 });
if (!nameservers) throw new BoxError(BoxError.EXTERNAL_ERROR, 'Unable to get nameservers');
for (const nameserver of nameservers) {
const synced = await isChangeSynced(hostname, type, value, nameserver);
debug('waitForDns: %s %s ns: %j', hostname, synced ? 'done' : 'not done', nameservers);
if (!synced) throw new BoxError(BoxError.EXTERNAL_ERROR, 'ETRYAGAIN');
}
});
debug(`waitForDns: ${hostname} has propagated`);
}