mostly because code is being autogenerated by all the AI stuff using this prefix. it's also used in the stack trace.
108 lines
4.7 KiB
JavaScript
108 lines
4.7 KiB
JavaScript
'use strict';
|
|
|
|
exports = module.exports = waitForDns;
|
|
|
|
const assert = require('node:assert'),
|
|
BoxError = require('../boxerror.js'),
|
|
debug = require('debug')('box:dns/waitfordns'),
|
|
dig = require('../dig.js'),
|
|
dns = require('node:dns'),
|
|
promiseRetry = require('../promise-retry.js'),
|
|
safe = require('safetydance'),
|
|
_ = require('../underscore.js');
|
|
|
|
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 ${type} for ${hostname} 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: No ${type}. Checking CNAME for ${hostname} at ${options.server}`);
|
|
const cnameResults = await dig.resolve(hostname, 'CNAME', options);
|
|
if (cnameResults.length === 0) return cnameResults;
|
|
|
|
// recurse lookup the CNAME record
|
|
debug(`resolveIp: found CNAME for ${hostname}. resolving ${cnameResults[0]}`);
|
|
return await dig.resolve(cnameResults[0], type, _.omit(options, ['server']));
|
|
}
|
|
|
|
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');
|
|
|
|
// ns records cannot have cname
|
|
const [error4, nsIPv4s] = await safe(dig.resolve(nameserver, 'A', { timeout: 5000 }));
|
|
const [error6, nsIPv6s] = await safe(dig.resolve(nameserver, 'AAAA', { timeout: 5000 }));
|
|
|
|
if (error4 && error6) {
|
|
debug(`isChangeSynced: cannot resolve NS ${nameserver}`); // NS doesn't resolve at all; it's fine
|
|
return true;
|
|
}
|
|
|
|
const nsIPs = [].concat(nsIPv4s || []).concat(nsIPv6s || []);
|
|
const status = [];
|
|
for (let i = 0; i < nsIPs.length; i++) {
|
|
const nsIp = nsIPs[i];
|
|
const resolveOptions = { server: nsIp, timeout: 5000, tries: 1 };
|
|
const resolver = type === 'A' || type === 'AAAA' ? resolveIp(hostname, type, resolveOptions) : dig.resolve(hostname, 'TXT', resolveOptions);
|
|
|
|
const [error, answer] = await safe(resolver);
|
|
// CONNREFUSED - when there is no ipv4/ipv6 connectivity. REFUSED - server won't answer maybe by policy
|
|
if (error && (error.code === dns.TIMEOUT || error.code === dns.REFUSED || error.code === dns.CONNREFUSED)) {
|
|
debug(`isChangeSynced: NS ${nameserver} (${nsIp}) not resolving ${hostname} (${type}): ${error}. Ignoring`);
|
|
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: waiting for ${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');
|
|
debug(`waitForDns: nameservers are ${JSON.stringify(nameservers)}`);
|
|
|
|
for (const nameserver of nameservers) {
|
|
const synced = await isChangeSynced(hostname, type, value, nameserver);
|
|
debug(`waitForDns: ${hostname} at ns ${nameserver}: ${synced ? 'done' : 'not done'} `);
|
|
if (!synced) throw new BoxError(BoxError.EXTERNAL_ERROR, 'ETRYAGAIN');
|
|
}
|
|
});
|
|
|
|
debug(`waitForDns: ${hostname} has propagated`);
|
|
}
|