2015-12-11 13:14:27 -08:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
exports = module.exports = waitForDns;
|
|
|
|
|
|
2025-08-14 11:17:38 +05:30
|
|
|
const assert = require('node:assert'),
|
2019-10-23 10:02:04 -07:00
|
|
|
BoxError = require('../boxerror.js'),
|
2016-12-14 12:27:11 -08:00
|
|
|
debug = require('debug')('box:dns/waitfordns'),
|
2022-02-03 16:15:14 -08:00
|
|
|
dig = require('../dig.js'),
|
2023-06-30 18:34:53 +05:30
|
|
|
dns = require('node:dns'),
|
2022-02-03 16:15:14 -08:00
|
|
|
promiseRetry = require('../promise-retry.js'),
|
2023-01-25 09:57:07 +01:00
|
|
|
safe = require('safetydance'),
|
2025-02-13 14:03:25 +01:00
|
|
|
_ = require('../underscore.js');
|
2015-12-11 13:14:27 -08:00
|
|
|
|
2022-02-03 16:15:14 -08:00
|
|
|
async function resolveIp(hostname, type, options) {
|
2018-02-08 14:55:06 -08:00
|
|
|
assert.strictEqual(typeof hostname, 'string');
|
2022-01-06 22:07:26 -08:00
|
|
|
assert(type === 'A' || type === 'AAAA');
|
2018-02-08 14:55:06 -08:00
|
|
|
assert.strictEqual(typeof options, 'object');
|
|
|
|
|
|
|
|
|
|
// try A record at authoritative server
|
2024-04-26 14:46:01 +02:00
|
|
|
debug(`resolveIp: Checking ${type} for ${hostname} at ${options.server}`);
|
2022-02-03 16:15:14 -08:00
|
|
|
const [error, results] = await safe(dig.resolve(hostname, type, options));
|
|
|
|
|
if (!error && results.length !== 0) return results;
|
|
|
|
|
|
|
|
|
|
// try CNAME record at authoritative server
|
2024-04-26 14:46:01 +02:00
|
|
|
debug(`resolveIp: No ${type}. Checking CNAME for ${hostname} at ${options.server}`);
|
2022-02-03 16:15:14 -08:00
|
|
|
const cnameResults = await dig.resolve(hostname, 'CNAME', options);
|
|
|
|
|
if (cnameResults.length === 0) return cnameResults;
|
|
|
|
|
|
|
|
|
|
// recurse lookup the CNAME record
|
2024-04-26 14:46:01 +02:00
|
|
|
debug(`resolveIp: found CNAME for ${hostname}. resolving ${cnameResults[0]}`);
|
2025-02-13 14:03:25 +01:00
|
|
|
return await dig.resolve(cnameResults[0], type, _.omit(options, ['server']));
|
2018-02-08 14:55:06 -08:00
|
|
|
}
|
|
|
|
|
|
2022-02-03 16:15:14 -08:00
|
|
|
async function isChangeSynced(hostname, type, value, nameserver) {
|
2019-01-04 18:44:54 -08:00
|
|
|
assert.strictEqual(typeof hostname, 'string');
|
2018-09-11 19:09:30 -07:00
|
|
|
assert.strictEqual(typeof type, 'string');
|
2018-02-08 14:19:14 -08:00
|
|
|
assert.strictEqual(typeof value, 'string');
|
2015-12-11 13:14:27 -08:00
|
|
|
assert.strictEqual(typeof nameserver, 'string');
|
|
|
|
|
|
|
|
|
|
// ns records cannot have cname
|
2024-04-26 17:41:51 +02:00
|
|
|
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
|
2022-02-03 16:15:14 -08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 17:41:51 +02:00
|
|
|
const nsIPs = [].concat(nsIPv4s || []).concat(nsIPv6s || []);
|
2022-02-03 16:15:14 -08:00
|
|
|
const status = [];
|
2024-04-26 17:41:51 +02:00
|
|
|
for (let i = 0; i < nsIPs.length; i++) {
|
|
|
|
|
const nsIp = nsIPs[i];
|
2024-09-18 15:25:48 +02:00
|
|
|
const resolveOptions = { server: nsIp, timeout: 5000, tries: 1 };
|
2022-02-03 16:15:14 -08:00
|
|
|
const resolver = type === 'A' || type === 'AAAA' ? resolveIp(hostname, type, resolveOptions) : dig.resolve(hostname, 'TXT', resolveOptions);
|
|
|
|
|
|
|
|
|
|
const [error, answer] = await safe(resolver);
|
2024-04-26 17:41:51 +02:00
|
|
|
// 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)) {
|
2023-09-10 06:17:43 +05:30
|
|
|
debug(`isChangeSynced: NS ${nameserver} (${nsIp}) not resolving ${hostname} (${type}): ${error}. Ignoring`);
|
2022-02-03 16:15:14 -08:00
|
|
|
status[i] = true; // should be ok if dns server is down
|
|
|
|
|
continue;
|
2016-04-25 15:58:42 -07:00
|
|
|
}
|
2015-12-11 13:14:27 -08:00
|
|
|
|
2022-02-03 16:15:14 -08:00
|
|
|
if (error) {
|
|
|
|
|
debug(`isChangeSynced: NS ${nameserver} (${nsIp}) errored when resolve ${hostname} (${type}): ${error}`);
|
|
|
|
|
status[i] = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2018-09-11 19:09:30 -07:00
|
|
|
|
2022-02-03 16:15:14 -08:00
|
|
|
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(''); });
|
|
|
|
|
}
|
2015-12-11 13:14:27 -08:00
|
|
|
|
2022-02-03 16:15:14 -08:00
|
|
|
debug(`isChangeSynced: ${hostname} (${type}) was resolved to ${answer} at NS ${nameserver} (${nsIp}). Expecting ${value}. Match ${match}`);
|
|
|
|
|
status[i] = match;
|
|
|
|
|
}
|
2017-05-26 15:15:01 -07:00
|
|
|
|
2022-02-03 16:15:14 -08:00
|
|
|
return status.every(s => s === true);
|
2018-01-09 16:09:47 -08:00
|
|
|
}
|
2015-12-11 13:14:27 -08:00
|
|
|
|
|
|
|
|
// check if IP change has propagated to every nameserver
|
2022-02-03 16:15:14 -08:00
|
|
|
async function waitForDns(hostname, zoneName, type, value, options) {
|
2019-01-04 18:44:54 -08:00
|
|
|
assert.strictEqual(typeof hostname, 'string');
|
2017-06-11 22:12:23 -07:00
|
|
|
assert.strictEqual(typeof zoneName, 'string');
|
2022-01-06 22:07:26 -08:00
|
|
|
assert(type === 'A' || type === 'AAAA' || type === 'TXT');
|
2018-02-08 14:19:14 -08:00
|
|
|
assert.strictEqual(typeof value, 'string');
|
2016-06-21 15:12:36 -05:00
|
|
|
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
|
2015-12-11 13:14:27 -08:00
|
|
|
|
2022-02-24 12:57:56 -08:00
|
|
|
debug(`waitForDns: waiting for ${hostname} to be ${value} in zone ${zoneName}`);
|
2015-12-11 13:14:27 -08:00
|
|
|
|
2022-02-03 16:15:14 -08:00
|
|
|
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');
|
2022-02-24 12:57:56 -08:00
|
|
|
debug(`waitForDns: nameservers are ${JSON.stringify(nameservers)}`);
|
2015-12-11 13:14:27 -08:00
|
|
|
|
2022-02-03 16:15:14 -08:00
|
|
|
for (const nameserver of nameservers) {
|
|
|
|
|
const synced = await isChangeSynced(hostname, type, value, nameserver);
|
2022-02-24 12:57:56 -08:00
|
|
|
debug(`waitForDns: ${hostname} at ns ${nameserver}: ${synced ? 'done' : 'not done'} `);
|
2022-02-03 16:15:14 -08:00
|
|
|
if (!synced) throw new BoxError(BoxError.EXTERNAL_ERROR, 'ETRYAGAIN');
|
|
|
|
|
}
|
2018-01-09 16:09:47 -08:00
|
|
|
});
|
2022-02-03 16:15:14 -08:00
|
|
|
|
|
|
|
|
debug(`waitForDns: ${hostname} has propagated`);
|
2015-12-11 13:14:27 -08:00
|
|
|
}
|