async-ify, handle vanity ns, bugfix some ns logic
This commit is contained in:
parent
717fb1fe3e
commit
691f5a4a15
215
lib/dns-store.js
215
lib/dns-store.js
|
@ -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);
|
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue