From 691f5a4a15bff5f1d4a1860f13b769b163f0a20a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 9 Oct 2017 15:54:18 -0600 Subject: [PATCH] async-ify, handle vanity ns, bugfix some ns logic --- lib/dns-store.js | 215 +++++++++++++++++++++++++---------------------- samples/zones.js | 5 +- 2 files changed, 118 insertions(+), 102 deletions(-) diff --git a/lib/dns-store.js b/lib/dns-store.js index a869d43..a07e89b 100644 --- a/lib/dns-store.js +++ b/lib/dns-store.js @@ -9,9 +9,8 @@ module.exports.ask = function (query, cb) { var NOERROR = 0; var NXDOMAIN = 3; var REFUSED = 5; -var primaryNameservers = [ 'localhost' ]; -function getRecords(db, qname) { +function getRecords(db, qname, cb) { var myRecords = db.records.filter(function (r) { if ('string' !== typeof r.domain) { return false; @@ -24,7 +23,9 @@ function getRecords(db, qname) { } }); - return myRecords; + process.nextTick(function () { + cb(null, myRecords); + }); } function dbToResourceRecord(r) { @@ -70,52 +71,62 @@ function getNs(db, ds, results, cb) { var d = ds.shift(); if (!d) { - results.rcode = NXDOMAIN; + results.header.rcode = NXDOMAIN; cb(null, results); return; } var qn = d.id.toLowerCase(); - getRecords(db, qn).forEach(function (r) { - if ('NS' !== r.type) { - return; + + return getRecords(db, qn, function (err, records) { + if (err) { cb(err); return; } + + records.forEach(function (r) { + if ('NS' !== r.type) { + return; + } + + var ns = { + name: r.domain + , typeName: r.type // NS + , className: 'IN' + , ttl: r.ttl || 300 + , data: r.address || r.value || r.data + }; + + console.log('got NS record:'); + console.log(r); + console.log(ns); + + // TODO what if this NS is one of the NS? + // return SOA record instead + results.authority.push(ns); + }); + + if (!results.authority.length) { + return getNs(db, ds, results, cb); } - var ns = { - name: r.domain - , typeName: r.type // NS - , className: 'IN' - , ttl: r.ttl || 300 - , data: r.address || r.value || r.data - }; - - console.log('got NS record:'); - console.log(r); - console.log(ns); - - // TODO what if this NS is one of the NS? - // return SOA record instead - results.authority.push(ns); - }); - - if (results.authority.length) { + // d.vanityNs should only be vanity nameservers (pointing to this same server) + if (d.vanityNs || results.authority.some(function (ns) { + console.log('[debug] ns', ns); + return -1 !== db.primaryNameservers.indexOf(ns.data.toLowerCase()); + })) { + results.authority.length = 0; + results.authority.push(domainToSoa(db, d)); + results.header.rcode = NXDOMAIN; + } cb(null, results); return; - } - - results.header.rcode = NXDOMAIN; - cb(null, results); + }); } -function getSoa(db, domain, results, cb) { - console.log('[DEV] getSoa entered'); - - var nameservers = domain.nameservers || primaryNameservers; +function domainToSoa(db, domain) { + var nameservers = domain.vanityNs || db.primaryNameservers; var index = Math.floor(Math.random() * nameservers.length) % nameservers.length; var nameserver = nameservers[index]; - - results.authority.push({ + return { name: domain.id , typeName: 'SOA' , className: 'IN' @@ -148,7 +159,13 @@ function getSoa(db, domain, results, cb) { // minimum -- how long to cache a non-existent domain (also the default ttl for BIND) , minimum: domain.minimum || 5 , nx: domain.minimum || 5 - }); + }; +} + +function getSoa(db, domain, results, cb) { + console.log('[DEV] getSoa entered'); + + results.authority.push(domainToSoa(db, domain)); cb(null, results); return; @@ -205,84 +222,82 @@ module.exports.query = function (input, query, cb) { , question: [ query.question[0] ], answer: [], authority: [], additional: [] }; - var myRecords = getRecords(db, qname); + return getRecords(db, qname, function (err, myRecords) { + if (err) { cb(err); return; } - if (myRecords.length) { - myRecords.forEach(function (r) { - results.answer.push(dbToResourceRecord(r)); - }); - results.header.rcode = NOERROR; - console.log('[DEV] results', results); - cb(null, results); - return; - } - - if (!myRecords.length) { - // If the query is www.foo.delegated.example.com - // and we have been delegated delegated.example.com - // and delegated.example.com exists - // but foo.delegated.example.com does not exist - // what's the best strategy for returning the record? - // - // What does PowerDNS do in these situations? - // https://doc.powerdns.com/md/authoritative/backend-generic-mysql/ - - // How to optimize: - // Assume that if a record is being requested, it probably exists - // (someone has probably published it somewhere) - // If the record doesn't exist, then see if any of the domains are managed - // [ 'www.john.smithfam.net', 'john.smithfam.net', 'smithfam.net', 'net' ] - // Then if one of those exists, return the SOA record with NXDOMAIN - - var qarr = qname.split('.'); - var qnames = []; - while (qarr.length) { - qnames.push(qarr.join('.').toLowerCase()); - qarr.shift(); // first - } - - var myDomains = db.domains.filter(function (d) { - return -1 !== qnames.indexOf(d.id.toLowerCase()); - }); - - // this should result in a REFUSED status - if (!myDomains.length) { - // REFUSED will have no records, so we could still recursion, if enabled - results.header.rcode = REFUSED; + if (myRecords.length) { + myRecords.forEach(function (r) { + results.answer.push(dbToResourceRecord(r)); + }); + results.header.rcode = NOERROR; + console.log('[DEV] results', results); cb(null, results); return; } - myDomains.sort(function (d1, d2) { - if (d1.id.length > d2.id.length) { - return -1; + if (!myRecords.length) { + // If the query is www.foo.delegated.example.com + // and we have been delegated delegated.example.com + // and delegated.example.com exists + // but foo.delegated.example.com does not exist + // what's the best strategy for returning the record? + // + // What does PowerDNS do in these situations? + // https://doc.powerdns.com/md/authoritative/backend-generic-mysql/ + + // How to optimize: + // Assume that if a record is being requested, it probably exists + // (someone has probably published it somewhere) + // If the record doesn't exist, then see if any of the domains are managed + // [ 'www.john.smithfam.net', 'john.smithfam.net', 'smithfam.net', 'net' ] + // Then if one of those exists, return the SOA record with NXDOMAIN + + var qarr = qname.split('.'); + var qnames = []; + while (qarr.length) { + qnames.push(qarr.join('.').toLowerCase()); + qarr.shift(); // first } - if (d1.id.length < d2.id.length) { - return 1; + + var myDomains = db.domains.filter(function (d) { + return -1 !== qnames.indexOf(d.id.toLowerCase()); + }); + + // this should result in a REFUSED status + if (!myDomains.length) { + // REFUSED will have no records, so we could still recursion, if enabled + results.header.rcode = REFUSED; + cb(null, results); + return; } - return 0; - }); - console.log('sorted domains', myDomains); - return getNs(db, myDomains.slice(0), results, function (err, results) { - console.log('[DEV] getNs complete'); + myDomains.sort(function (d1, d2) { + if (d1.id.length > d2.id.length) { + return -1; + } + if (d1.id.length < d2.id.length) { + return 1; + } + return 0; + }); + console.log('sorted domains', myDomains); - if (err) { cb(err, results); return; } + return getNs(db, myDomains.slice(0), results, function (err, results) { + console.log('[DEV] getNs complete'); - // has NS records - if (NXDOMAIN !== results.header.rcode) { console.log(results); cb(null, results); return; } + if (err) { cb(err, results); return; } - // myDomains was sorted such that the longest was first - getSoa(db, myDomains[0], results, cb); + // has NS records (or SOA record if NS records match the server itself) + if (results.authority.length) { + console.log(results); cb(null, results); return; + } - }); - } + // myDomains was sorted such that the longest was first + getSoa(db, myDomains[0], results, cb); - /* - query.question.forEach(function (q) { - module.exports.ask(q); + }); + } }); - */ }; }()); diff --git a/samples/zones.js b/samples/zones.js index ccf4042..27b1b5e 100644 --- a/samples/zones.js +++ b/samples/zones.js @@ -1,10 +1,11 @@ 'use strict'; module.exports = { - "domains": [ + "primaryNameservers": [ 'localhost' ] // 'ns1.redirect-www.org' +, "domains": [ { "id": "daplie.me", "revokedAt": 0 } , { "id": "oneal.daplie.me", "revokedAt": 0 } - , { "id": "aj.oneal.daplie.me", "revokedAt": 0 } + , { "id": "aj.oneal.daplie.me", "revokedAt": 0, "vanityNs": [ 'ns1.daplie.me', 'ns2.daplie.me' ] } ] , "records": [ // zone daplie.me should be able to have some records on its own