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 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);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}());
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user