From 6decc790d6ba63bfd25ce7c103cc3df9b01b39c6 Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Thu, 8 Feb 2018 14:55:06 -0800 Subject: [PATCH] Follow CNAME records DNS records can now be a A record or a CNAME record. All we care about is them resolving to the public IP of the server somehow. The main reason for this change is that altDomain is migrated into domains table and the DNS propagation checks have to work after that. (previously, the 'altDomain' was a signal for a CNAME check which now cannot be done post-migration). In the future, we can make this more sophisticated to instead maybe do a well-known URI query. That way it will work even if there is some proxy like Cloudflare in the middle. Fixes #503 --- src/dns/waitfordns.js | 56 +++++++++++++++++++++++++++---------------- src/native-dns.js | 5 ---- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/dns/waitfordns.js b/src/dns/waitfordns.js index f9fba81be..b4b54f332 100644 --- a/src/dns/waitfordns.js +++ b/src/dns/waitfordns.js @@ -8,6 +8,28 @@ var assert = require('assert'), dns = require('../native-dns.js'), DomainError = require('../domains.js').DomainError; +function resolveIp(hostname, options, callback) { + assert.strictEqual(typeof hostname, 'string'); + assert.strictEqual(typeof options, 'object'); + assert.strictEqual(typeof callback, 'function'); + + // try A record at authoritative server + debug(`resolveIp: Checking if ${hostname} has A record at ${options.server}`); + dns.resolve(hostname, 'A', options, function (error, results) { + if (!error && results.length !== 0) return callback(null, results); + + // try CNAME record at authoritative server + debug(`resolveIp: Checking if ${hostname} has CNAME record at ${options.server}`); + dns.resolve(hostname, 'CNAME', options, function (error, results) { + if (error || results.length === 0) return callback(error, results); + + // recurse lookup the CNAME record + debug(`resolveIp: Resolving ${hostname}'s CNAME record ${results[0]}`); + dns.resolve(results[0], 'A', { server: '127.0.0.1', timeout: options.timeout }, callback); + }); + }); +} + function isChangeSynced(domain, value, nameserver, callback) { assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof value, 'string'); @@ -15,36 +37,27 @@ function isChangeSynced(domain, value, nameserver, callback) { assert.strictEqual(typeof callback, 'function'); // ns records cannot have cname - dns.resolve4(nameserver, function (error, nsIps) { + dns.resolve(nameserver, 'A', { timeout: 5000 }, function (error, nsIps) { if (error || !nsIps || nsIps.length === 0) { - debug('nameserver %s does not resolve. assuming it stays bad.', nameserver); // it's fine if one or more ns are dead + debug(`isChangeSynced: cannot resolve NS ${nameserver}`); // it's fine if one or more ns are dead return callback(null, true); } async.every(nsIps, function (nsIp, iteratorCallback) { - dns.resolve(domain, 'A', { server: nsIp, timeout: 5000 }, function (error, answer) { + resolveIp(domain, { server: nsIp, timeout: 5000 }, function (error, answer) { if (error && error.code === 'TIMEOUT') { - debug('nameserver %s (%s) timed out when trying to resolve %s', nameserver, nsIp, domain); + debug(`isChangeSynced: NS ${nameserver} (${nsIp}) timed out when resolving ${domain}`); return iteratorCallback(null, true); // should be ok if dns server is down } if (error) { - debug('nameserver %s (%s) returned error trying to resolve %s: %s', nameserver, nsIp, domain, error); + debug(`isChangeSynced: NS ${nameserver} (${nsIp}) errored when resolve ${domain}: ${error}`); return iteratorCallback(null, false); } - if (!answer || answer.length === 0) { - debug('bad answer from nameserver %s (%s) resolving %s', nameserver, nsIp, domain); - return iteratorCallback(null, false); - } + debug(`isChangeSynced: ${domain} was resolved to ${answer} at NS ${nameserver} (${nsIp}). Expecting ${value}`); - debug('isChangeSynced: ns: %s (%s), name:%s Actual:%j Expecting:%s', nameserver, nsIp, domain, answer, value); - - var match = answer.some(a => a === value); - - if (match) return iteratorCallback(null, true); // done! - - iteratorCallback(null, false); + iteratorCallback(null, answer.length === 1 && answer[0] === value); }); }, callback); @@ -59,17 +72,18 @@ function waitForDns(domain, zoneName, value, options, callback) { assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 } assert.strictEqual(typeof callback, 'function'); - debug('waitForIp: domain %s to be %s in zone %s.', domain, value, zoneName); + debug('waitForDns: domain %s to be %s in zone %s.', domain, value, zoneName); - var attempt = 1; + var attempt = 0; async.retry(options, function (retryCallback) { - debug('waitForDNS: %s (zone: %s) attempt %s.', domain, zoneName, attempt++); + ++attempt; + debug(`waitForDns (try ${attempt}): ${domain} to be ${value} in zone ${zoneName}`); dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) { if (error || !nameservers) return retryCallback(error || new DomainError(DomainError.EXTERNAL_ERROR, 'Unable to get nameservers')); async.every(nameservers, isChangeSynced.bind(null, domain, value), function (error, synced) { - debug('waitForIp: %s %s ns: %j', domain, synced ? 'done' : 'not done', nameservers); + debug('waitForDns: %s %s ns: %j', domain, synced ? 'done' : 'not done', nameservers); retryCallback(synced ? null : new DomainError(DomainError.EXTERNAL_ERROR, 'ETRYAGAIN')); }); @@ -77,7 +91,7 @@ function waitForDns(domain, zoneName, value, options, callback) { }, function retryDone(error) { if (error) return callback(error); - debug('waitForDNS: %s done.', domain); + debug(`waitForDns: ${domain} has propagated`); callback(null); }); diff --git a/src/native-dns.js b/src/native-dns.js index 91a1aea50..f37e6cf87 100644 --- a/src/native-dns.js +++ b/src/native-dns.js @@ -1,7 +1,6 @@ 'use strict'; exports = module.exports = { - resolve4: resolve4, resolve: resolve }; @@ -34,7 +33,3 @@ function resolve(hostname, rrtype, options, callback) { callback(error, result); }); } - -function resolve4(hostname, callback) { - resolve(hostname, 'A', { timeout: 5000 }, callback); -}