From 5d3a6d30d7bb5f7b7d82440a35ce44a2c80eb51e Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 9 Oct 2017 19:17:34 -0600 Subject: [PATCH] more consistent naming, example db file, ANY matching --- README.md | 121 +++++++++++++++++++++++++++++++++++++++++++++-- TESTS.md | 3 ++ lib/dns-store.js | 23 ++++++--- samples/db.js | 53 +++++++++++---------- 4 files changed, 164 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 06ef0d3..37023cc 100644 --- a/README.md +++ b/README.md @@ -37,13 +37,13 @@ Usage ----- ```bash -digd.js --input +digd.js --input ``` **Example**: ```bash -digd.js --input ./examples/example.com.json +digd.js --input ./samples/db.json ``` ### Testing @@ -64,7 +64,7 @@ Options ``` --output write query and response(s) to disk with this path prefix (ex: ./samples/dns) ---input input file to use for authoritative responses (ex: ./samples/zones.json) +--input input file to use for authoritative responses (ex: ./samples/db.json) --mdns Use mDNS port (5353) and nameserver address (224.0.0.251) @@ -80,6 +80,121 @@ Options --debug verbose output ``` +JSON Database File +------------------ + +This DNS server is being created for use in the wild. +Although there will be a true database adapter later, +this JSON representation gives us an easy way to experiment with serving DNS and various record types. + +There are 4 types of information in the file: + +* Primary Nameservers `primaryNameservers` +* SOA Records `domains` +* devices +* All other records (A, AAAA, CAA, CNAME, MX, NS, PTR, SPF, SRV, TXT) + +```js +module.exports = { + primaryNameservers: [ 'ns1.example.com', 'ns2.example.com' ] + + // SOA records +, domains: [ + // `primary` is chosen at random from `primaryNameservers` or `vanityNs` + // `serial` is generated from `updatedAt` + + { id: "publicsuffix.net", updatedAt: 1507594095118, ttl: 60 + , admin: 'admin.publicsuffix.net', refresh: 1800, retry: 600 + , expiration: 2419200, minimum: 5 } + + , { id: "doe.publicsuffix.net", updatedAt: 1507594095118, ttl: 60 + , admin: 'admin.doe.publicsuffix.net', refresh: 1800, retry: 600 + , expiration: 2419200, minimum: 5 } + + + // default values will be used when left undefined + , { id: "doefam.net", updatedAt: 1507594095118 + , vanityNs: [ 'ns1.awesome.com', 'ns2.awesome.com' ] } + ] +, records: [ + // + // Plain old boring A Records + // + { name: "publicsuffix.net", zone: "publicsuffix.net" + , tld: "net", sld: "publicsuffix", sub: "" + , type: 'A', ttl: 300, address: '127.0.0.1' } + + { name: "www.publicsuffix.net", zone: "publicsuffix.net" + , tld: "net", sld: "publicsuffix", sub: "www" + , type: 'A', ttl: 300, address: '127.0.0.1' } + + // + // Subdomain Delegation of a public suffix (treated as TLD) + // + { name: "jane.doe.publicsuffix.net", zone: "doe.publicsuffix.net" + , tld: "publicsuffix.net", sld: "doe", sub: "john" + , type: 'NS', ttl: 300, data: 'ns1.other-dns.net' + } + + // + // Example of all other record types + // + { name: "john.doe.publicsuffix.net" + + // The zone / SOA it belongs to (keep in mind that subdomains can be delegated to other users and/or nameservers) + , zone: "doe.publicsuffix.net" + + // For indexing (note that we can treat delegated subdomains as if they were TLDs for delegation and resale) + , tld: "publicsuffix.net" + , sld: "doe" + , sub: "john" + + , type: 'A' // for this example we specify a type even though we show all of the record data + , class: 'IN' // (default) + , ttl: 300 + + // A, AAAA + , address: '127.0.0.1' + + // CAA + , flag: 0 + , tag: 'issue' + , value: 'letsencrypt.org' + + // CNAME, NS, PTR put 'name' here + // TXT puts an array here + , data: 'a.example.com' + + // MX, SRV + , priority: 10 + + // MX + , exchange: 'mxa.example.org' + + // SRV + , weight: 20 + , port: 65065 + , target: 'laptop1.devices.example.com' + } + ] +}; +``` + +The **Primary Nameservers** should be all of the nameservers that are in sync for these collections of records. + +The **SOA** records represent that a domain or subdomain has be registered to or delegated to these nameservers. +The SOA records are separate from other record types because they are automatically generated as part of registering +a domain or updating its records. + +The **other records** are in their own table for easy and fast lookup. + +The **devices** are an abstraction that will be used in the future for ANAMEs and Dynamic DNS. + +Note: Because it's possible to that delegated subdomains could have delegated subdomains that go right back to the +original nameserver, **NS** records will be replaced with an SOA record if any of the NS records match any of +the server's primary nameservers or if vanity nameservers are used. + + Other Resources --------------- diff --git a/TESTS.md b/TESTS.md index bf608c5..bc9e373 100644 --- a/TESTS.md +++ b/TESTS.md @@ -21,3 +21,6 @@ Send malformed packets (both as queries and as answers): - randomly twiddled bits - forward compression pointers - compression pointers to wrong bits (throw error on non-ascii / unsafe chars) + +Test that ANY queries return records of all types matching the domain +Test that A queries only return A records, not others matching the domain diff --git a/lib/dns-store.js b/lib/dns-store.js index a07e89b..738ab38 100644 --- a/lib/dns-store.js +++ b/lib/dns-store.js @@ -12,13 +12,13 @@ var REFUSED = 5; function getRecords(db, qname, cb) { var myRecords = db.records.filter(function (r) { - if ('string' !== typeof r.domain) { + if ('string' !== typeof r.name) { return false; } // TODO use IN in masterquest (or implement OR) // Only return single-level wildcard? - if (qname === r.domain || ('*.' + qname.split('.').slice(1).join('.')) === r.domain) { + if (qname === r.name || ('*.' + qname.split('.').slice(1).join('.')) === r.name) { return true; } }); @@ -30,7 +30,7 @@ function getRecords(db, qname, cb) { function dbToResourceRecord(r) { return { - name: r.domain + name: r.name , typeName: r.type // NS , className: 'IN' , ttl: r.ttl || 300 @@ -50,7 +50,7 @@ function dbToResourceRecord(r) { , address: -1 !== [ 'A', 'AAAA' ].indexOf(r.type) ? (r.address || r.value) : undefined // CNAME, NS, PTR || TXT - , data: -1 !== [ 'CNAME', 'NS', 'PTR', 'TXT' ].indexOf(r.type) ? (r.value || r.values) : undefined + , data: -1 !== [ 'CNAME', 'NS', 'PTR', 'TXT' ].indexOf(r.type) ? (r.data || r.value || r.values) : undefined // MX, SRV , priority: r.priority @@ -87,11 +87,11 @@ function getNs(db, ds, results, cb) { } var ns = { - name: r.domain + name: r.name , typeName: r.type // NS - , className: 'IN' + , className: r.class || 'IN' , ttl: r.ttl || 300 - , data: r.address || r.value || r.data + , data: r.data || r.value || r.address }; console.log('got NS record:'); @@ -225,6 +225,15 @@ module.exports.query = function (input, query, cb) { return getRecords(db, qname, function (err, myRecords) { if (err) { cb(err); return; } + if (255 !== query.question[0].type && 'ANY' !== query.question[0].typeName) { + myRecords = myRecords.filter(function (r) { + return ((r.type && r.type === query.question[0].type) + || (r.type && r.type === query.question[0].typeName) + || (r.typeName && r.typeName === query.question[0].typeName) + ); + }); + } + if (myRecords.length) { myRecords.forEach(function (r) { results.answer.push(dbToResourceRecord(r)); diff --git a/samples/db.js b/samples/db.js index 27b1b5e..46d3db9 100644 --- a/samples/db.js +++ b/samples/db.js @@ -9,57 +9,58 @@ module.exports = { ] , "records": [ // zone daplie.me should be able to have some records on its own - { "zone": "daplie.me", "type": "A", "domain": "daplie.me" - , "tld": "me", "sld": "daplie", "sub": "", "value": "23.228.168.108", "aname": "tardigrade.devices.daplie.me" } + { "zone": "daplie.me", "name": "daplie.me", "tld": "me", "sld": "daplie", "sub": "" + , "type": "A", "address": "23.228.168.108", "aname": "tardigrade.devices.daplie.me" } - , { "zone": "daplie.me", "type": "A", "domain": "www.daplie.me" - , "tld": "me", "sld": "daplie", "sub": "www", "value": "23.228.168.108", "aname": "tardigrade.devices.daplie.me" } + , { "zone": "daplie.me", "name": "www.daplie.me", "tld": "me", "sld": "daplie", "sub": "www" + , "type": "A", "address": "23.228.168.108", "aname": "tardigrade.devices.daplie.me" } - , { "zone": "daplie.me", "type": "CNAME", "domain": "email.daplie.me" - , "tld": "me", "sld": "daplie", "sub": "email", "value": "mailgun.org" } + , { "zone": "daplie.me", "name": "email.daplie.me", "tld": "me", "sld": "daplie", "sub": "email" + , "type": "CNAME", "data": "mailgun.org" } - , { "zone": "daplie.me", "type": "ANAME", "domain": "tardigrade.devices.daplie.me", "device": "abcdef123" - , "tld": "me", "sld": "daplie", "sub": "tardigrade.devices", "value": "23.228.168.108" } + , { "zone": "daplie.me", "name": "tardigrade.devices.daplie.me", "tld": "me", "sld": "daplie", "sub": "tardigrade.devices" + , "device": "abcdef123" + , "type": "ANAME", "address": "23.228.168.108" } // zone daplie.me can delegate oneal.daplie.me to the same nameserver // (it's probably programmatically and politically simplest to always delegate from a parent zone) // Thought Experiment: could we delegate the root to a child? i.e. daplie.me -> www.daplie.me // to let someone exclusively "own" the root domain, but none of the children? - , { "zone": "daplie.me", "type": "NS", "domain": "oneal.daplie.me" - , "tld": "me", "sld": "daplie", "sub": "oneal", "value": "ns1.redirect-www.org" } + , { "zone": "daplie.me", "type": "NS", "name": "oneal.daplie.me" + , "tld": "me", "sld": "daplie", "sub": "oneal", "data": "ns1.redirect-www.org" } - , { "zone": "daplie.me", "type": "NS", "domain": "oneal.daplie.me" - , "tld": "me", "sld": "daplie", "sub": "oneal", "value": "ns2.redirect-www.org" } + , { "zone": "daplie.me", "name": "oneal.daplie.me", "tld": "me", "sld": "daplie", "sub": "oneal" + , "type": "NS", "data": "ns2.redirect-www.org" } // // now the zone "oneal.daplie.me" can be independently owned (and delegated) // ... but what about email for aj@daplie.me with aj@daplie.me? - , { "zone": "oneal.daplie.me", "type": "A", "domain": "oneal.daplie.me" - , "tld": "daplie.me", "sld": "oneal", "sub": "", "value": "45.56.59.142", "aname": "leo.devices.oneal.daplie.me" } + , { "zone": "oneal.daplie.me", "name": "oneal.daplie.me", "tld": "daplie.me", "sld": "oneal", "sub": "" + , "type": "A", "address": "45.56.59.142", "aname": "leo.devices.oneal.daplie.me" } - , { "zone": "oneal.daplie.me", "type": "CNAME", "domain": "www.oneal.daplie.me" - , "tld": "daplie.me", "sld": "oneal", "sub": "www", "value": "oneal.daplie.me" } + , { "zone": "oneal.daplie.me", "name": "www.oneal.daplie.me", "tld": "daplie.me", "sld": "oneal", "sub": "www" + , "type": "CNAME", "data": "oneal.daplie.me" } - , { "zone": "oneal.daplie.me", "type": "NS", "domain": "aj.oneal.daplie.me" - , "tld": "daplie.me", "sld": "oneal", "sub": "aj", "value": "ns1.redirect-www.org" } + , { "zone": "oneal.daplie.me", "name": "aj.oneal.daplie.me", "tld": "daplie.me", "sld": "oneal", "sub": "aj" + , "type": "NS", "data": "ns1.redirect-www.org" } - , { "zone": "oneal.daplie.me", "type": "NS", "domain": "aj.oneal.daplie.me" - , "tld": "daplie.me", "sld": "oneal", "sub": "aj", "value": "ns2.redirect-www.org" } + , { "zone": "oneal.daplie.me", "name": "aj.oneal.daplie.me", "tld": "daplie.me", "sld": "oneal", "sub": "aj" + , "type": "NS", "data": "ns2.redirect-www.org" } // there can be a wildcard, to which a delegation is the exception - , { "zone": "oneal.daplie.me", "type": "A", "domain": "*.oneal.daplie.me" - , "tld": "daplie.me", "sld": "oneal", "sub": "*", "value": "45.56.59.142", "aname": "leo.devices.oneal.daplie.me" } + , { "zone": "oneal.daplie.me", "name": "*.oneal.daplie.me", "tld": "daplie.me", "sld": "oneal", "sub": "*" + , "type": "A", "address": "45.56.59.142", "aname": "leo.devices.oneal.daplie.me" } // there can be an exception to the delegation - , { "zone": "oneal.daplie.me", "type": "A", "domain": "exception.aj.oneal.daplie.me" - , "tld": "daplie.me", "sld": "oneal", "sub": "exception.aj", "value": "45.56.59.142", "aname": "leo.devices.oneal.daplie.me" } + , { "zone": "oneal.daplie.me", "name": "exception.aj.oneal.daplie.me", "tld": "daplie.me", "sld": "oneal", "sub": "exception.aj" + , "type": "A", "address": "45.56.59.142", "aname": "leo.devices.oneal.daplie.me" } // // aj.oneal.daplie.me // - , { "zone": "aj.oneal.daplie.me", "type": "A", "domain": "aj.oneal.daplie.me" - , "tld": "oneal.daplie.me", "sld": "aj", "sub": "", "value": "45.56.59.142", "aname": "leo.devices.oneal.daplie.me" } + , { "zone": "aj.oneal.daplie.me", "name": "aj.oneal.daplie.me", "tld": "oneal.daplie.me", "sld": "aj", "sub": "" + , "type": "A", "address": "45.56.59.142", "aname": "leo.devices.oneal.daplie.me" } ] } ;