async-ify, handle vanity ns, bugfix some ns logic

This commit is contained in:
AJ ONeal 2017-10-09 15:54:18 -06:00
parent 717fb1fe3e
commit 691f5a4a15
2 changed files with 118 additions and 102 deletions

View File

@ -9,9 +9,8 @@ module.exports.ask = function (query, cb) {
var NOERROR = 0; var NOERROR = 0;
var NXDOMAIN = 3; var NXDOMAIN = 3;
var REFUSED = 5; var REFUSED = 5;
var primaryNameservers = [ 'localhost' ];
function getRecords(db, qname) { function getRecords(db, qname, cb) {
var myRecords = db.records.filter(function (r) { var myRecords = db.records.filter(function (r) {
if ('string' !== typeof r.domain) { if ('string' !== typeof r.domain) {
return false; return false;
@ -24,7 +23,9 @@ function getRecords(db, qname) {
} }
}); });
return myRecords; process.nextTick(function () {
cb(null, myRecords);
});
} }
function dbToResourceRecord(r) { function dbToResourceRecord(r) {
@ -70,52 +71,62 @@ function getNs(db, ds, results, cb) {
var d = ds.shift(); var d = ds.shift();
if (!d) { if (!d) {
results.rcode = NXDOMAIN; results.header.rcode = NXDOMAIN;
cb(null, results); cb(null, results);
return; return;
} }
var qn = d.id.toLowerCase(); var qn = d.id.toLowerCase();
getRecords(db, qn).forEach(function (r) {
if ('NS' !== r.type) { return getRecords(db, qn, function (err, records) {
return; 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 = { // d.vanityNs should only be vanity nameservers (pointing to this same server)
name: r.domain if (d.vanityNs || results.authority.some(function (ns) {
, typeName: r.type // NS console.log('[debug] ns', ns);
, className: 'IN' return -1 !== db.primaryNameservers.indexOf(ns.data.toLowerCase());
, ttl: r.ttl || 300 })) {
, data: r.address || r.value || r.data results.authority.length = 0;
}; results.authority.push(domainToSoa(db, d));
results.header.rcode = NXDOMAIN;
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) {
cb(null, results); cb(null, results);
return; return;
} });
results.header.rcode = NXDOMAIN;
cb(null, results);
} }
function getSoa(db, domain, results, cb) { function domainToSoa(db, domain) {
console.log('[DEV] getSoa entered'); var nameservers = domain.vanityNs || db.primaryNameservers;
var nameservers = domain.nameservers || primaryNameservers;
var index = Math.floor(Math.random() * nameservers.length) % nameservers.length; var index = Math.floor(Math.random() * nameservers.length) % nameservers.length;
var nameserver = nameservers[index]; var nameserver = nameservers[index];
return {
results.authority.push({
name: domain.id name: domain.id
, typeName: 'SOA' , typeName: 'SOA'
, className: 'IN' , 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 -- how long to cache a non-existent domain (also the default ttl for BIND)
, minimum: domain.minimum || 5 , minimum: domain.minimum || 5
, nx: 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); cb(null, results);
return; return;
@ -205,84 +222,82 @@ module.exports.query = function (input, query, cb) {
, question: [ query.question[0] ], answer: [], authority: [], additional: [] , 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) { if (myRecords.length) {
myRecords.forEach(function (r) { myRecords.forEach(function (r) {
results.answer.push(dbToResourceRecord(r)); results.answer.push(dbToResourceRecord(r));
}); });
results.header.rcode = NOERROR; results.header.rcode = NOERROR;
console.log('[DEV] results', results); 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;
cb(null, results); cb(null, results);
return; return;
} }
myDomains.sort(function (d1, d2) { if (!myRecords.length) {
if (d1.id.length > d2.id.length) { // If the query is www.foo.delegated.example.com
return -1; // 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) { myDomains.sort(function (d1, d2) {
console.log('[DEV] getNs complete'); 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 (err) { cb(err, results); return; }
if (NXDOMAIN !== results.header.rcode) { console.log(results); cb(null, results); return; }
// myDomains was sorted such that the longest was first // has NS records (or SOA record if NS records match the server itself)
getSoa(db, myDomains[0], results, cb); 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);
}); });
*/
}; };
}()); }());

View File

@ -1,10 +1,11 @@
'use strict'; 'use strict';
module.exports = { module.exports = {
"domains": [ "primaryNameservers": [ 'localhost' ] // 'ns1.redirect-www.org'
, "domains": [
{ "id": "daplie.me", "revokedAt": 0 } { "id": "daplie.me", "revokedAt": 0 }
, { "id": "oneal.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": [ , "records": [
// zone daplie.me should be able to have some records on its own // zone daplie.me should be able to have some records on its own