Compare commits

...

88 Commits

Author SHA1 Message Date
c0eb4225dc Update 'README.md' 2019-03-05 23:43:14 +00:00
6ef61a8674 don't let it die, duh!! 2018-05-04 16:05:59 +00:00
AJ ONeal
d8ead3181d update installer with v1.2 2017-12-14 20:33:32 -07:00
AJ ONeal
bc93b942ee v1.2.1 2017-12-14 20:29:02 -07:00
AJ ONeal
c3934acb30 add tcp support 2017-12-14 20:26:28 -07:00
AJ ONeal
bd8b57efd1 bump version 2017-11-05 09:17:55 -07:00
AJ ONeal
59980dfa60 making room for API 2017-11-05 09:16:27 -07:00
AJ ONeal
b5ec1f7982 Merge branch 'master' of ssh://git.coolaj86.com:22042/coolaj86/digd.js 2017-11-04 21:13:16 -06:00
AJ ONeal
3fba17eb97 add standard files 2017-11-04 21:06:58 -06:00
AJ ONeal
028d5a4542 lengthen NS ttl, remove cruft, add missing records 2017-11-04 16:37:38 -06:00
AJ ONeal
2705f5ef65 note delay in updates / expected short-term failure 2017-11-03 16:40:17 -06:00
AJ ONeal
6cbb2d1741 Merge branch 'v1.1' of ssh://git.coolaj86.com:22042/coolaj86/digd.js into v1.1 2017-11-03 16:23:34 -06:00
AJ ONeal
d5ab2d5a26 add --tld option (even though it's not usually necessary) 2017-11-03 16:23:30 -06:00
AJ ONeal
c77053c39c show example of dns tools for self-hosted dns 2017-11-03 16:15:04 -06:00
AJ ONeal
42d3e8a072 Merge branch 'v1' 2017-11-03 15:42:17 -06:00
AJ ONeal
50b335aa61 update urls 2017-11-03 15:39:59 -06:00
AJ ONeal
a1eca4f1a5 clarify (kinda) vanity nameserver comment 2017-11-03 12:23:56 -06:00
AJ ONeal
4ffdf5b59d Merge branch 'v1' 2017-11-03 12:19:51 -06:00
AJ ONeal
148bda8afc WIP changes for CNAME fix 2017-11-03 12:17:06 -06:00
AJ ONeal
ad61360a22 update with latest sample db 2017-11-03 12:14:45 -06:00
AJ ONeal
93e5c80ab4 add tests for CNAME and CNAME return on A/AAAA 2017-11-03 11:53:50 -06:00
AJ ONeal
b3ef27791e update description 2017-11-02 23:59:53 -06:00
AJ ONeal
cfe5f4e818 add mdig.js to bar 2017-11-02 23:47:20 -06:00
AJ ONeal
81928adf3e file organization 2017-11-02 23:19:15 -06:00
AJ ONeal
f3105971a4 fix vanity NS check 2017-11-02 13:08:23 -06:00
AJ ONeal
7a201e4d8a add real sample db file 2017-11-02 12:38:16 -06:00
AJ ONeal
be91254190 Merge branch 'v1.1' of ssh://git.coolaj86.com:22042/coolaj86/digd.js into v1.1 2017-11-02 12:16:38 -06:00
AJ ONeal
49811bd321 fix and/or logic 2017-11-02 12:14:44 -06:00
AJ ONeal
5839451f20 add exchange 2017-11-02 11:56:57 -06:00
AJ ONeal
f67788f9db Merge branch 'v1' of git.daplie.com:Daplie/digd.js into v1 2017-11-02 11:32:51 -06:00
AJ ONeal
5cf3a3b20f add simple parser/reformatter 2017-11-02 11:32:46 -06:00
AJ ONeal
d4646dba06 fix path name 2017-11-01 23:32:57 -06:00
AJ ONeal
58a48a2877 fix npm paths 2017-11-01 23:31:21 -06:00
AJ ONeal
a617eb176c fix chown 2017-11-01 23:26:38 -06:00
AJ ONeal
9d4489f36a remove node installer tmp file 2017-11-01 23:20:02 -06:00
AJ ONeal
b1b43a6257 fix adduser options 2017-11-01 23:19:12 -06:00
AJ ONeal
718e41b71a add installer, bump to v1.1.9 2017-11-01 23:14:44 -06:00
AJ ONeal
8f34294c88 create installer with systemd service and example db 2017-11-01 22:56:44 -06:00
AJ ONeal
861ffa1b80 add systemd launch file 2017-11-01 22:15:17 -06:00
AJ ONeal
c49c398c39 some examples 2017-11-01 00:09:30 -06:00
AJ ONeal
5af2cb51d6 print number of records 2017-10-29 02:26:38 -06:00
AJ ONeal
d625115689 Note setting glue and ns records 2017-10-29 01:43:28 -06:00
AJ ONeal
e9db7fc12e v1.1.8 2017-10-28 22:13:52 -06:00
AJ ONeal
901c31e7bf update urls 2017-10-28 22:13:32 -06:00
AJ ONeal
91154c3d2a v1.1.7 2017-10-28 22:04:41 -06:00
AJ ONeal
046e9ed575 Merge branch 'master' of ssh://git.coolaj86.com:22042/coolaj86/digd.js 2017-10-28 22:04:35 -06:00
AJ ONeal
da2d503226 v1.1.6 2017-10-28 22:04:23 -06:00
AJ ONeal
5f3aa51aec update urls 2017-10-28 22:03:58 -06:00
c09d5f8a10 Update 'README.md' 2017-10-29 03:40:26 +00:00
AJ ONeal
3f8fc8799c v1.1.5 2017-10-28 02:15:32 -06:00
AJ ONeal
ade2e97270 whitespace 2017-10-27 23:53:43 -06:00
AJ ONeal
a8061dbc6c update urls 2017-10-27 23:47:21 -06:00
AJ ONeal
f6fb74f625 update urls 2017-10-27 23:45:52 -06:00
AJ ONeal
cf8cab5596 v1.1.4 2017-10-23 22:02:12 -06:00
AJ ONeal
62733099ec pass wildcard tests 2017-10-23 22:02:03 -06:00
AJ ONeal
bcb9f3cf86 add wildcard test 2017-10-23 21:14:52 -06:00
AJ ONeal
4e65f6be3f v1.1.3 2017-10-23 20:56:43 -06:00
AJ ONeal
6680719c31 merge with v1 2017-10-23 20:56:29 -06:00
AJ ONeal
031aa24c1b v1.1.2 2017-10-23 20:55:00 -06:00
AJ ONeal
acfb2e7a01 update git deps 2017-10-23 20:54:49 -06:00
AJ ONeal
251afcf439 v1.1.1 2017-10-23 20:41:23 -06:00
AJ ONeal
a8efa1f68d v1.1.0 2017-10-23 20:41:14 -06:00
AJ ONeal
d4e036fa97 v1.0.1 2017-10-23 20:40:40 -06:00
AJ ONeal
32bc4e6fea fix bad test AAAA value 2017-10-23 20:39:32 -06:00
AJ ONeal
48fe5ba786 v1.1.0 2017-10-23 10:56:49 -06:00
AJ ONeal
df7dac0f15 v1.0.1 2017-10-23 10:56:23 -06:00
AJ ONeal
4d8c88e882 note some bugs 2017-10-20 17:28:12 -06:00
AJ ONeal
2ab424feb6 NS records returned for sub and sub sub domains 2017-10-20 11:37:04 -06:00
AJ ONeal
44f33c999d more tests and test data 2017-10-19 12:55:22 -06:00
AJ ONeal
6ac53c1b05 testing proper SOA records 2017-10-18 18:24:41 -06:00
AJ ONeal
a089bfc8e5 add ANAME resolution 2017-10-12 12:48:09 -06:00
AJ ONeal
d32dba2a75 add some test ideas 2017-10-09 19:29:31 -06:00
AJ ONeal
bc5c003901 add LICENSE 2017-10-09 19:19:18 -06:00
AJ ONeal
5d3a6d30d7 more consistent naming, example db file, ANY matching 2017-10-09 19:17:34 -06:00
AJ ONeal
0303ffee30 rename db file 2017-10-09 19:13:24 -06:00
AJ ONeal
7437de1e37 note tests that need to be tested 2017-10-09 16:03:37 -06:00
AJ ONeal
30c1fd13af note tests that are needed 2017-10-09 15:59:36 -06:00
AJ ONeal
691f5a4a15 async-ify, handle vanity ns, bugfix some ns logic 2017-10-09 15:54:18 -06:00
AJ ONeal
717fb1fe3e minor fixes 2017-10-09 14:07:45 -06:00
AJ ONeal
d26b671176 WIP some local responses work 2017-10-06 18:31:59 -06:00
AJ ONeal
d7e208d3f7 default to SERVFAIL for recurse 2017-10-06 17:38:55 -06:00
AJ ONeal
0151500ac8 use proper rcodes 2017-10-06 17:27:17 -06:00
AJ ONeal
f945da161b respond without ra on timeout 2017-10-06 16:39:57 -06:00
AJ ONeal
df78e13ef2 output udp type, show client examples with dig and netcat 2017-10-06 15:54:00 -06:00
AJ ONeal
ff963a76ec whitespace fix 2017-10-06 15:40:01 -06:00
AJ ONeal
4734b5ac57 WIP dns store 2017-10-06 15:34:36 -06:00
AJ ONeal
8131de4a08 add note on delegation 2017-10-03 16:21:51 -06:00
AJ ONeal
e52e45641e note unbound 2017-10-02 17:13:57 -06:00
22 changed files with 1917 additions and 113 deletions

4
CHANGELOG Normal file
View File

@ -0,0 +1,4 @@
v1.1.13 - Tested and working. Deployed to production with known bugs:
* vanity nameserver handling needs more testing
* delegated nameserver handling needs more testing
* malformed records in JSON may result in failure to respond

41
LICENSE Normal file
View File

@ -0,0 +1,41 @@
Copyright 2017 AJ ONeal
This is open source software; you can redistribute it and/or modify it under the
terms of either:
a) the "MIT License"
b) the "Apache-2.0 License"
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Apache-2.0 License Summary
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

186
README.md
View File

@ -1,33 +1,45 @@
digd.js
=======
| [dns-suite](https://git.daplie.com/Daplie/dns-suite)
| [dig.js](https://git.daplie.com/Daplie/dig.js)
| [dns-suite.js](https://git.coolaj86.com/coolaj86/dns-suite.js)
| [dig.js](https://git.coolaj86.com/coolaj86/dig.js)
| [mdig.js](https://git.coolaj86.com/coolaj86/mdig.js)
| **digd.js**
| A [Root project](https://rootprojects.org).
A lightweight DNS / mDNS daemon (server) in node.js.
Although originally created for testing dns-suite.js
by creating and capturing to disk DNS and mDNS query
and response packets (as binary and/or JSON), digd.js
has grown into a full-blown nameserver.
A lightweight DNS / mDNS daemon (server) for creating and capturing DNS and mDNS
query and response packets to disk as binary and/or JSON.
Options are similar to the Unix dig command.
Install
-------
### systemd service
```bash
curl -L https://git.coolaj86.com/coolaj86/digd.js/raw/v1.2/install.sh | bash
```
### with git
```bash
# Install the latest of v1.x
npm install -g 'git+https://git@git.daplie.com/Daplie/digd.js.git#v1'
npm install -g 'git+https://git.coolaj86.com/coolaj86/digd.js.git#v1'
```
```bash
# Install exactly v1.0.0
npm install -g 'git+https://git@git.daplie.com/Daplie/digd.js.git#v1.0.0'
# Install exactly v1.2.0
npm install -g 'git+https://git.coolaj86.com/coolaj86/digd.js.git#v1.2.0'
```
### without git
Don't have git? Well, you can also bow down to the gods of the centralized, monopolized, concentrated, *dictator*net
(as we like to call it here at Daplie Labs), if that's how you roll:
Don't have git? You can use npm's centralized repository:
```bash
npm install -g digd.js
@ -37,13 +49,26 @@ Usage
-----
```bash
digd.js --input <path/to/file.json>
digd.js --input <path/to/dns.json>
```
**Example**:
```bash
digd.js --input ./examples/example.com.json
digd.js --input ./samples/db.json
```
### Testing
```bash
# unix dig
dig @localhost example.com
# dns-suite's dig.js
dig.js @localhost example.com
# unix netcat
netcat -u 127.0.0.1 53 < ./samples/example.com.a.query.bin
```
Options
@ -51,7 +76,7 @@ Options
```
--output <path/to/file> write query and response(s) to disk with this path prefix (ex: ./samples/dns)
--input <path/to/file> input file to use for authoritative responses (ex: ./samples/zones.json)
--input <path/to/file> input file to use for authoritative responses (ex: ./samples/db.json)
--mdns Use mDNS port (5353) and nameserver address (224.0.0.251)
@ -63,6 +88,143 @@ Options
+time=<seconds> Sets the timeout for a query in seconds.
+norecurse Set `ra` flag to 0. Do not perform recursion.
+aaonly Set `aa` flag to 1. Do not respond with non-authoritative responses.
+notcp Disable TCP server (default in v1.2)
+tcp Enable TCP server (default in v1.3)
--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'
, aname: 'some-device.example.com' // See "A Note on ANAMEs" below
// 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.
### A Note on ANAMES
ANAMEs serve two purposes in this system:
1. Traditional ANAME. Just a CNAME that is automatically resolved to an A record for the "bare domain" problem, and efficiency.
2. Dynamic DNS. When a record on the system is updated, any records that match it by ANAME are also updated
TODO: use dns0x20 for ANAME resolutions
Other Resources
---------------
You may also be interested in Unbound (https://unboundtest.com), which is an entirely different project by someone else
which is much more complete, written in go, and may be very useful for debugging and linting.
LICENSE
=======
You may, at your option, use this software under the MIT and/or Apache-2.0 licenses.

View File

@ -5,13 +5,16 @@
var cli = require('cli');
var pkg = require('../package.json');
var dig = require('dig.js/dns-request');
var dgram = require('dgram');
var dnsjs = require('dns-suite');
var crypto = require('crypto');
var common = require('dig.js/common');
var defaultNameservers = require('dns').getServers();
var hexdump;
var NOERROR = 0;
var SERVFAIL = 2;
//var NXDOMAIN = 3;
var REFUSED = 5;
cli.parse({
// 'b': [ false, 'set source IP address (defaults to 0.0.0.0)', 'string' ]
'class': [ 'c', 'class (defaults to IN)', 'string', 'IN' ]
@ -44,8 +47,32 @@ cli.main(function (args, cli) {
cli.norecurse = true;
return;
}
if (arg === '+notcp') {
if (cli.notcp) {
console.error("'+notcp' was specified more than once");
process.exit(1);
return;
}
cli.notcp = true;
return;
}
if (arg === '+tcp') {
if (cli.tcp) {
console.error("'+tcp' was specified more than once");
process.exit(1);
return;
}
cli.tcp = true;
return;
}
});
if (!cli.tcp) {
if (!cli.notcp) {
console.info("[WARNING] Set '+notcp' to disable tcp connections. The default behavior changes to +tcp in v1.3");
}
}
if (cli.mdns) {
if (!cli.type) {
cli.type = cli.t = 'PTR';
@ -68,30 +95,10 @@ cli.main(function (args, cli) {
}
}
var handlers = {};
var server = dgram.createSocket({
type: cli.udp6 ? 'udp6' : 'udp4'
, reuseAddr: true
});
server.bind({
port: cli.port
, address: cli.address
});
handlers.onError = function (err) {
if ('EACCES' === err.code) {
console.error("");
console.error("EACCES: Couldn't bind to port. You probably need to use sudo, authbind, or setcap.");
console.error("");
process.exit(123);
return;
}
console.error("error:", err.stack);
server.close();
};
handlers.onMessage = function (nb, rinfo) {
var queryAb = nb.buffer.slice(nb.byteOffset, nb.byteOffset + nb.byteLength);
var dnsd = {};
dnsd.onMessage = function (nb, cb) {
var byteOffset = nb._dnsByteOffset || nb.byteOffset;
var queryAb = nb.buffer.slice(byteOffset, byteOffset + nb.byteLength);
var query;
var count;
@ -100,6 +107,7 @@ cli.main(function (args, cli) {
} catch(e) {
// TODO log bad queries (?)
console.error("Could not parse DNS query, ignoring.");
console.error(e);
try {
hexdump = require('hexdump.js').hexdump;
console.error(hexdump(queryAb));
@ -168,7 +176,12 @@ cli.main(function (args, cli) {
//common.writeResponse(opts, query, nb, packet);
}
function sendEmptyResponse(query, nx) {
function sendEmptyResponse(query, rcode) {
// rcode
// 0 SUCCESS // manages this domain and found a record
// 2 SERVFAIL // could not contact authoritatve nameserver (no recursion, etc)
// 3 NXDOMAIN // manages this domain, but doesn't have a record
// 5 REFUSED // doesn't manage this domain
var newAb;
var emptyResp = {
header: {
@ -179,7 +192,7 @@ cli.main(function (args, cli) {
, tc: 0
, rd: query.header.rd
, ra: cli.norecurse ? 0 : 1 // TODO is this bit dependent on the rd bit?
, rcode: nx ? 3 : 0 // no error
, rcode: rcode ? rcode : 0 // no error
}
, question: []
, answer: []
@ -199,14 +212,14 @@ cli.main(function (args, cli) {
try {
newAb = dnsjs.DNSPacket.write(emptyResp);
} catch(e) {
console.error("Could not write DNS response");
console.error("Could not write empty DNS response");
console.error(e);
console.error(emptyResp);
cb(e, null, '[DEV] response sent (empty)');
return;
}
server.send(newAb, rinfo.port, rinfo.address, function () {
console.log('[DEV] response sent (empty)');
});
cb(null, newAb, '[DEV] response sent (empty)');
}
function sendResponse(newPacket) {
@ -215,26 +228,26 @@ cli.main(function (args, cli) {
try {
newAb = dnsjs.DNSPacket.write(newPacket);
} catch(e) {
console.error("Could not write DNS response");
console.error("Could not write DNS response from local");
console.error(e);
console.error(newPacket);
cb(e, null, '[DEV] response sent (local query)');
return;
}
server.send(newAb, rinfo.port, rinfo.address, function () {
console.log('[DEV] response sent (local query)');
});
cb(null, newAb, '[DEV] response sent (local query)');
}
function recurse() {
if (!query.header.rd) {
console.log("[Could not answer. Sent empty response.]");
sendEmptyResponse(query, true);
console.log("[DEV] no recursion desired. Sending empty response.]");
sendEmptyResponse(query, SERVFAIL);
return;
}
if (cli.norecurse) {
console.log("[Could not answer. Sent empty response.]");
sendEmptyResponse(query, true);
console.log("[DEV] recursion forbidden. Sending empty response.]");
sendEmptyResponse(query, REFUSED);
return;
}
@ -245,11 +258,11 @@ cli.main(function (args, cli) {
id: query.header.id // require('crypto').randomBytes(2).readUInt16BE(0)
, qr: 0
, opcode: 0
, aa: query.header.aa ? 1 : 0 // NA? not sure what this would do
, aa: 0 // query.header.aa ? 1 : 0 // NA? not sure what this would do
, tc: 0 // NA
, rd: 1
, ra: 0 // NA
, rcode: 0 // NA
, rcode: SERVFAIL // NA
}
, question: []
, answer: []
@ -269,18 +282,17 @@ cli.main(function (args, cli) {
var newAb;
count -= 1;
if (!count) {
if (count <= 0) {
try {
newAb = dnsjs.DNSPacket.write(newResponse);
} catch(e) {
console.error("Could not write DNS response");
console.error(newResponse);
cb(e, null, '[DEV] response sent');
return;
}
server.send(newAb, rinfo.port, rinfo.address, function () {
console.log('[DEV] response sent');
});
cb(null, newAb, '[DEV] response sent');
}
}
@ -289,6 +301,9 @@ cli.main(function (args, cli) {
updateCount();
}
, onMessage: function (packet) {
// yay! recursion was available after all!
newResponse.header.ra = 1;
newResponse.header.rcode = NOERROR;
(packet.answer||[]).forEach(function (a) {
// TODO copy each relevant property
@ -317,18 +332,21 @@ cli.main(function (args, cli) {
console.log('request sent to', res.nameserver);
}
*/
console.log('[DEV] response sent (recurse)');
//console.log('[DEV] query sent (recurse)', rinfo.port, rinfo.address);
//dnsd.onSent('[DEV] query sent (recurse)');
}
, onTimeout: function (res) {
console.log(";; [" + q.name + "] connection timed out; no servers could be reached");
console.log(";; [timed out after " + res.timeout + "ms and 1 tries]");
updateCount();
}
, onClose: function () {
console.log('');
}
, mdns: cli.mdns
, nameserver: cli.nameserver
, port: cli.port
, nameserver: cli.chosenNameserver
, port: cli.resolverPort || 53 // TOODO accept resolverPort
, timeout: cli.timeout
};
@ -340,51 +358,62 @@ cli.main(function (args, cli) {
}
count = query.question.length;
if (!count) {
if (count <= 0) {
sendEmptyResponse(query);
return;
}
// TODO get local answer first, if available
require('../lib/dns-store').query(cli.input, query, function (err, resp) {
if (err) { recurse(); return; }
var path = require('path');
if (!cli.input) {
console.warn('[WARN] no db path given, must recurse if enabled');
recurse();
return;
}
function respondWithResults(err, resp) {
if (err) { console.log('[DEV] answer not found in local db, recursing'); console.error(err); recurse(); return; }
if (SERVFAIL === resp.header.rcode) { console.log('[DEV] local cache miss, recursing'); recurse(); return; }
// TODO double check does it matter whether rd is set when responding with ra?
if (!cli.norecurse && query.header.rd) { resp.header.ra = 1; }
sendResponse(resp);
});
}
var engine;
try {
engine = require('../lib/store.json.js').create({ filepath: path.resolve(cli.input) });
} catch(e) {
respondWithResults(e);
return;
}
require('../lib/digd.js').query(engine, query, respondWithResults);
};
handlers.onListening = function () {
/*jshint validthis:true*/
var server = this;
var nameserver = cli.nameserver;
cli.defaultNameservers = defaultNameservers;
require('../lib/udpd.js').create(cli, dnsd).on('listening', function () {
cli.chosenNameserver = cli.nameserver;
var index;
if (!nameserver) {
index = crypto.randomBytes(2).readUInt16BE(0) % defaultNameservers.length;
nameserver = defaultNameservers[index];
if (!cli.chosenNameserver) {
index = require('crypto').randomBytes(2).readUInt16BE(0) % cli.defaultNameservers.length;
cli.chosenNameserver = cli.defaultNameservers[index];
if (cli.debug) {
console.log(index, defaultNameservers);
console.log('index, defaultNameservers', index, cli.defaultNameservers);
}
}
if (cli.mdns || '224.0.0.251' === cli.nameserver) {
server.setBroadcast(true);
server.addMembership(cli.nameserver);
}
console.log('');
console.log('Bound and Listening:');
console.log(server.address().address + '#' + server.address().port);
};
});
if (cli.tcp /* TODO v1.3 !cli.notcp */) {
require('../lib/tcpd.js').create(cli, dnsd);
}
console.log('');
if (!cli.nocmd) {
console.log('; <<>> digd.js v' + pkg.version + ' <<>> ' + process.argv.slice(2));
console.log('; <<>> digd.js v' + pkg.version + ' <<>> ' + process.argv.slice(2).join(' '));
console.log(';; global options: +cmd');
}
server.on('error', handlers.onError);
server.on('message', handlers.onMessage);
server.on('listening', handlers.onListening);
});

61
dist/etc/systemd/system/digd.js.service vendored Normal file
View File

@ -0,0 +1,61 @@
[Unit]
Description=digd.js - A lightweight DNS server in node.js.
Documentation=https://git.coolaj86.com/coolaj86/digd.js
After=network-online.target
Wants=network-online.target systemd-networkd-wait-online.service
[Service]
# Restart on crash (bad signal), but not on 'clean' failure (error exit code)
# Allow up to 3 restarts within 10 seconds
# (it's unlikely that a user or properly-running script will do this)
Restart=always
StartLimitInterval=10
StartLimitBurst=3
# User and group the process will run as
# (git is the de facto standard on most systems)
User=digd
Group=digd
WorkingDirectory=/opt/digd.js
# TODO use --config instead of commandline params
ExecStart=/opt/digd.js/bin/node /opt/digd.js/bin/digd.js --port 53 --input /srv/digd.js/db.json +norecurse
ExecReload=/bin/kill -USR1 $MAINPID
# Limit the number of file descriptors and processes; see `man systemd.exec` for more limit settings.
# Unmodified digd.js is not expected to use more than this.
LimitNOFILE=1048576
LimitNPROC=64
# Use private /tmp and /var/tmp, which are discarded after digd.js stops.
PrivateTmp=true
# Use a minimal /dev
PrivateDevices=true
# Hide /home, /root, and /run/user. Nobody will steal your SSH-keys.
ProtectHome=true
# Make /usr, /boot, /etc and possibly some more folders read-only.
ProtectSystem=full
# ... except /srv/digd.js because we want a place for the database
# and /var/log/digd.js because we want a place where logs can go.
# This merely retains r/w access rights, it does not add any new.
# Must still be writable on the host!
ReadWriteDirectories=/opt/digd.js /srv/digd.js /var/log/digd.js
# Note: in v231 and above ReadWritePaths has been renamed to ReadWriteDirectories
; ReadWritePaths=/opt/digd.js /srv/digd.js /var/log/digd.js
# The following additional security directives only work with systemd v229 or later.
# They further retrict privileges that can be gained by digd.js.
# Note that you may have to add capabilities required by any plugins in use.
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
# Caveat: Some features may need additional capabilities.
# For example an "upload" may need CAP_LEASE
; CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_LEASE
; AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_LEASE
; NoNewPrivileges=true
[Install]
WantedBy=multi-user.target

35
install.sh Normal file
View File

@ -0,0 +1,35 @@
#!/bin/bash
sudo adduser --home /opt/digd.js --gecos '' --disabled-password digd
sudo mkdir -p /opt/digd.js/ /srv/digd.js /var/log/digd.js
sudo mkdir -p /opt/digd.js /srv/digd.js
#chown -R $(whoami):$(whoami) /opt/digd.js /srv/digd.js
chown -R digd:digd /opt/digd.js /srv/digd.js
echo "v8.9.3" > /tmp/NODEJS_VER
export NODE_PATH=/opt/digd.js/lib/node_modules
export NPM_CONFIG_PREFIX=/opt/digd.js
curl -fsSL https://git.coolaj86.com/coolaj86/node-installer.sh/raw/master/install.sh -o ./node-installer.sh.tmp
bash ./node-installer.sh.tmp
rm ./node-installer.sh.tmp
/opt/digd.js/bin/node /opt/digd.js/bin/npm install -g npm@4
git clone https://git.coolaj86.com/coolaj86/digd.js /opt/digd.js/lib/node_modules/digd.js
pushd /opt/digd.js/lib/node_modules/digd.js
git checkout v1.2
/opt/digd.js/bin/node /opt/digd.js/bin/npm install
popd
sudo rsync -v /opt/digd.js/lib/node_modules/digd.js/dist/etc/systemd/system/digd.js.service /etc/systemd/system/
sudo rsync -v /opt/digd.js/lib/node_modules/digd.js/samples/db.json /srv/digd.js/db.json
sudo ln -s /opt/digd.js/lib/node_modules/digd.js/bin/digd.js /opt/digd.js/bin/
sudo chown -R digd:digd /opt/digd.js/ /srv/digd.js /var/log/digd.js
sudo systemctl daemon-reload
sudo systemctl restart digd.js
dig @localhost -p 53 example.com
#sudo journalctl -xefu digd.js
sudo journalctl -xeu digd.js

482
lib/digd.js Normal file
View File

@ -0,0 +1,482 @@
(function () {
'use strict';
/*
module.exports.ask = function (query, cb) {
};
*/
var NOERROR = 0;
var NXDOMAIN = 3;
var REFUSED = 5;
function getRecords(engine, qname, cb) {
var delMe = {};
var dns = require('dns');
// SECURITY XXX TODO var dig = require('dig.js/dns-request');
var count;
return engine.getRecords({ name: qname }, function (err, myRecords) {
if (err) { cb(err); return; }
function checkCount() {
var ready;
count -= 1;
ready = count <= 0;
if (!ready) {
return;
}
myRecords = myRecords.filter(function (r) {
return !delMe[r.id];
});
// There are a number of ways to interpret the wildcard rules
var hasWild = false;
var hasMatch = false;
myRecords.some(function (r) {
if (qname === r.name) {
hasMatch = true;
return true;
}
if ('*' === r.name[0]) {
hasWild = true;
}
});
if (hasMatch) {
myRecords = myRecords.filter(function (r) {
if ('*' !== r.name[0]) { return true; }
});
}
/*
// no need to filter out records if wildcard is used
else {
records = records.filter(function (r) {
if ('*' === r.name[0]) { return true; }
});
}
*/
cb(null, myRecords);
}
function getRecord(r) {
// TODO allow multiple records to be returned(?)
return function (err, addresses) {
if (err || !addresses.length) {
r.id = r.id || Math.random();
delMe[r.id] = true;
} else if (addresses.length > 1) {
r._address = addresses[Math.floor(Math.random() * addresses.length)];
} else {
r._address = addresses[0];
}
checkCount();
};
}
count = myRecords.length;
myRecords.forEach(function (r) {
if (r.aname && !r.address) {
if ('A' === r.type) {
// SECURITY XXX TODO dig.resolveJson(query, opts);
dns.resolve4(r.aname, getRecord(r));
return;
}
if ('AAAA' === r.type) {
// SECURITY XXX TODO dig.resolveJson(query, opts);
dns.resolve6(r.aname, getRecord(r));
return;
}
}
checkCount();
});
if (!myRecords.length) {
checkCount();
}
});
}
function dbToResourceRecord(r) {
return {
name: r.name
, typeName: r.typeName || r.type // NS
, className: 'IN'
, ttl: r.ttl || 300
// SOA
/*
, "primary": "ns1.yahoo.com"
, "admin": "hostmaster.yahoo-inc.com"
, "serial": 2017092539
, "refresh": 3600
, "retry": 300
, "expiration": 1814400
, "minimum": 600
*/
// A, AAAA
, address: -1 !== [ 'A', 'AAAA' ].indexOf(r.type) ? (r._address || r.address || r.value) : undefined
// CNAME, NS, PTR || TXT
, data: -1 !== [ 'CNAME', 'NS', 'PTR', 'TXT' ].indexOf(r.type) ? (r.data || r.value || r.values) : undefined
// MX, SRV
, priority: r.priority
// MX
, exchange: r.exchange
// SRV
, weight: r.weight
, port: r.port
, target: r.target
};
}
function getNs(engine, ds, results, cb) {
console.log('[DEV] getNs entered with domains', ds);
var d = ds.shift();
console.log('[DEV] trying another one', d);
if (!d) {
results.header.rcode = NXDOMAIN;
cb(null, results);
return;
}
var qn = d.id.toLowerCase();
return getRecords(engine, qn, function (err, records) {
if (err) { cb(err); return; }
records.forEach(function (r) {
if ('NS' !== r.type) {
return;
}
var ns = {
name: r.name
, typeName: r.type // NS
, className: r.class || 'IN'
, ttl: r.ttl || 300
, data: r.data || r.value || r.address
};
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(engine, ds, results, cb);
}
// 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 !== engine.primaryNameservers.indexOf(ns.data.toLowerCase());
})) {
results.authority.length = 0;
results.authority.push(domainToSoa(engine.primaryNameservers, d));
results.header.rcode = NXDOMAIN;
}
cb(null, results);
return;
});
}
function domainToSoa(primaryNameservers, domain) {
var nameservers = domain.vanityNs || primaryNameservers;
var index = Math.floor(Math.random() * nameservers.length) % nameservers.length;
var nameserver = nameservers[index];
return {
name: domain.id
, typeName: 'SOA'
, className: 'IN'
, ttl: domain.ttl || 60
// nameserver -- select an NS at random if they're all in sync
, primary: nameserver
, name_server: nameserver
// admin -- email address or domain for admin
, admin: domain.admin || ('admin.' + domain.id)
, email_addr: domain.admin || ('admin.' + domain.id)
// serial -- the version, for cache-busting of secondary nameservers. suggested format: YYYYMMDDnn
, serial: domain.serial || Math.round((domain.updatedAt || domain.createdAt || 0) / 1000)
, sn: domain.serial || Math.round((domain.updatedAt || domain.createdAt || 0) / 1000)
// refresh -- only used when nameservers following the DNS NOTIFY spec talk
, refresh: domain.refresh || 1800
, ref: domain.refresh || 1800
// retry -- only used when nameservers following the DNS NOTIFY spec talk
, retry: domain.retry || 600
, ret: domain.retry || 600
// expiration -- how long other nameservers should continue when the primary goes down
, expiration: domain.expiration || 2419200
, ex: domain.expiration || 2419200
// 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(primaryNameservers, domain, results, cb, answerSoa) {
console.log('[DEV] getSoa entered');
if (!answerSoa) {
results.authority.push(domainToSoa(primaryNameservers, domain));
} else {
results.answer.push(domainToSoa(primaryNameservers, domain));
}
cb(null, results);
return;
}
module.exports.query = function (engine, query, cb) {
/*
var fs = require('fs');
fs.readFile(input, 'utf8', function (err, text) {
if (err) { cb(err); return; }
var records;
try {
records = JSON.parse(text);
} catch(e) { cb(e); return; }
});
*/
var qname;
if (!Array.isArray(query.question) || query.question.length < 1) {
cb(new Error("query is missing question section"));
return;
}
if (1 !== query.question.length) {
cb(new Error("query should have exactly one question (for now)"));
return;
}
if (!query.question[0] || 'string' !== typeof query.question[0].name) {
cb(new Error("query's question section should exist and have a String name property"));
return;
}
qname = query.question[0].name.toLowerCase();
var results = {
header: {
id: query.header.id // same as request
, qr: 1
, opcode: 0 // pretty much always 0 QUERY
, aa: 1 // TODO right now we assume that if we have the record, we're authoritative
// but in reality we could be hitting a cache and then recursing on a cache miss
, tc: 0
, rd: query.header.rd // duh
, ra: 0 // will be changed by cli.norecurse
, rcode: NOERROR // 0 NOERROR, 3 NXDOMAIN, 5 REFUSED
}
, question: [ query.question[0] ], answer: [], authority: [], additional: []
};
function getNsAndSoa(getNsAlso, answerSoa) {
// 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
}
console.log('[DEV] getNsAlso?', getNsAlso);
console.log('[DEV] answerSoa?', answerSoa);
console.log('[DEV] qnames');
console.log(qnames);
return engine.getSoas({ names: qnames}, function (err, myDomains) {
console.log('[SOA] looking for', qnames, 'and proudly serving', err, myDomains);
if (err) { cb(err); return; }
// 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;
}
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 (!getNsAlso) {
return getSoa(engine.primaryNameservers, myDomains[0], results, cb, answerSoa);
}
return getNs(engine, /*myDomains.slice(0)*/qnames.map(function (qn) { return { id: qn }; }), results, function (err, results) {
//console.log('[DEV] getNs complete');
if (err) { cb(err, results); return; }
// 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
return getSoa(engine.primaryNameservers, myDomains[0], results, cb);
});
});
}
if ('SOA' === query.question[0].typeName) {
return getNsAndSoa(false, true);
}
//console.log('[DEV] QUERY NAME', qname);
return getRecords(engine, qname, function (err, someRecords) {
var myRecords;
var nsRecords = [];
if (err) { cb(err); return; }
// There are two special cases
// NS records are returned as ANSWER for NS and ANY, and as AUTHORITY when an externally-delegated domain would return an SOA (no records)
// SOA records are returned as ANSWER for SOA and ANY, and as AUTHORITY when no records are found, but the domain is controlled here
console.log("[DEV] has", someRecords.length, "records");
// filter out NS (delegation) records, unless that is what is intended
someRecords = someRecords.filter(function (r) {
// If it's not an NS record, it's a potential result
if ('NS' !== r.type && 'NS' !== r.typeName) {
return true;
}
console.log("It's NS");
// If it's a vanity NS, it's not a valid NS for lookup
// NOTE: I think that the issue here is EXTERNAL vs INTERNAL vanity NS
// We _should_ reply for EXTERNAL vanity NS... but not when it's listed on the SOA internally?
// It's surrounding the problem of what if I do sub domain delegation to the same server.
if (-1 === engine.primaryNameservers.indexOf(r.data.toLowerCase())) {
console.log("It's a vanity NS");
return false;
}
// If the query was for NS, it's a potential result
if ('ANY' === query.question[0].typeName || 'NS' === query.question[0].typeName) {
return true;
}
nsRecords.push(r);
return false;
});
myRecords = someRecords;
// If we had an ANY query then we don't need to filter out results
if (255 !== query.question[0].type && 'ANY' !== query.question[0].typeName) {
var hasA = false;
var hasCname = false;
// We should only return the records that match the query,
// except in the case of A/AAAA in which case we should also collect the CNAME
myRecords = myRecords.filter(function (r) {
var passCnames = false;
if (!hasA && ('A' === query.question[0].typeName || 'AAAA' === query.question[0].typeName)) {
passCnames = ('CNAME' === r.type ||'CNAME' === r.typeName);
hasCname = hasCname || passCnames;
}
hasA = hasA || ('A' === r.type || 'A' === r.typeName || 'AAAA' === r.type || 'AAAA' === r.typeName);
return passCnames || ((r.type && r.type === query.question[0].type)
|| (r.type && r.type === query.question[0].typeName)
|| (r.typeName && r.typeName === query.question[0].typeName)
);
});
// A and AAAA will also return CNAME records
// but we filter out the CNAME records unless there are no A / AAAA records
if (hasA && hasCname && ('A' === query.question[0].typeName || 'AAAA' === query.question[0].typeName)) {
myRecords = myRecords.forEach(function (r) {
return 'CNAME' !== r.type && 'CNAME' !== r.typeName;
});
}
}
if (myRecords.length) {
myRecords.forEach(function (r) {
results.answer.push(dbToResourceRecord(r));
});
results.header.rcode = NOERROR;
//console.log('[DEV] ANSWER results', results);
if (255 === query.question[0].type || 'ANY' === query.question[0].typeName) {
getNsAndSoa(false, true);
return;
}
cb(null, results);
return;
}
else if (nsRecords.length) {
nsRecords.forEach(function (r) {
results.authority.push(dbToResourceRecord(r));
});
results.header.rcode = NOERROR;
//console.log('[DEV] AUTHORITY results', results);
cb(null, results);
return;
}
console.log("[DEV] Gonna get NS and SOA");
// !myRecords.length
getNsAndSoa(true);
});
};
}());

View File

@ -1,22 +0,0 @@
(function () {
'use strict';
/*
var fs = require('fs');
module.exports.ask = function (query, cb) {
};
*/
module.exports.query = function (input, query, cb) {
process.nextTick(function () {
cb(new Error('No local lookup method for DNS records defined.'));
});
/*
query.question.forEach(function (q) {
module.exports.ask(q);
});
*/
};
}());

37
lib/store.json.js Normal file
View File

@ -0,0 +1,37 @@
'use strict';
module.exports.create = function (opts) {
// opts = { filepath };
var engine = { db: null };
var db = require(opts.filepath);
engine.primaryNameservers = db.primaryNameservers;
engine.getSoas = function (query, cb) {
var myDomains = db.domains.filter(function (d) {
return -1 !== query.names.indexOf(d.id.toLowerCase());
});
process.nextTick(function () {
cb(null, myDomains);
});
};
engine.getRecords = function (query, cb) {
var myRecords = db.records.slice(0).filter(function (r) {
if ('string' !== typeof r.name) {
return false;
}
// TODO use IN in masterquest (or implement OR)
// Only return single-level wildcard?
if (query.name === r.name || ('*.' + query.name.split('.').slice(1).join('.')) === r.name) {
return true;
}
});
process.nextTick(function () {
cb(null, myRecords);
});
};
return engine;
};

70
lib/tcpd.js Normal file
View File

@ -0,0 +1,70 @@
'use strict';
module.exports.create = function (cli, dnsd) {
function runTcp() {
var tcpServer = require('net').createServer({ }, function (c) {
c.on('error', function (err) {
console.warn("TCP Connection Error:");
console.warn(err);
});
c.on('data', function (nb) {
//console.log('TCP data.length:', nb.length);
//console.log(nb.toString('hex'));
// DNS packets include a 2-byte length header
var count = nb.length;
var length = nb[0] << 8;
length = length | nb[1];
count -= 2;
// TODO slice?
nb._dnsByteOffset = nb.byteOffset + 2;
if (length !== count) {
console.error("Handling TCP packets > 512 bytes not implemented.");
c.end();
return;
}
// TODO pad two bytes for lengths
dnsd.onMessage(nb, function (err, newAb, dbgmsg) {
var lenbuf = Buffer.from([ newAb.length >> 8, newAb.length & 255 ]);
// TODO XXX generate legit error packet
if (err) { console.error("Error", err); c.end(); return; }
console.log('TCP ' + dbgmsg);
c.write(lenbuf);
c.end(newAb);
});
});
c.on('end', function () {
console.log('TCP client disconnected from server');
});
});
tcpServer.on('error', function (err) {
if ('EADDRINUSE' === err.code) {
console.error("Port '" + cli.port + "' is already in use.");
tcpServer.close();
process.exit(0);
}
if ('EACCES' === err.code) {
console.error("Could not bind on port '" + cli.port + "': EACCESS (you probably need root permissions)");
tcpServer.close();
process.exit(0);
}
console.error("TCP Server Error:");
console.error(err);
tcpServer.close(function () {
setTimeout(runTcp, 1000);
});
});
tcpServer.listen(cli.port, function () {
console.log('TCP Server bound');
});
return tcpServer;
}
return runTcp();
};

58
lib/udpd.js Normal file
View File

@ -0,0 +1,58 @@
'use strict';
module.exports.create = function (cli, dnsd) {
var server = require('dgram').createSocket({
type: cli.udp6 ? 'udp6' : 'udp4'
, reuseAddr: true
});
server.bind({
port: cli.port
, address: cli.address
});
var handlers = {};
handlers.onError = function (err) {
if ('EACCES' === err.code) {
console.error("");
console.error("EACCES: Couldn't bind to port. You probably need to use sudo, authbind, or setcap.");
console.error("");
process.exit(123);
return;
}
console.error("error:", err.stack);
server.close();
};
handlers.onMessage = function (nb, rinfo) {
//console.log('[DEBUG] got a UDP message', nb.length);
//console.log(nb.toString('hex'));
dnsd.onMessage(nb, function (err, newAb, dbgmsg) {
// TODO send legit error message
if (err) { server.send(Buffer.from([0x00])); return; }
server.send(newAb, rinfo.port, rinfo.address, function () {
console.log(dbgmsg, rinfo.port, rinfo.address);
});
});
};
handlers.onListening = function () {
/*jshint validthis:true*/
var server = this;
if (cli.mdns || '224.0.0.251' === cli.nameserver) {
server.setBroadcast(true);
server.addMembership(cli.nameserver);
}
console.log('');
console.log('Bound and Listening:');
console.log(server.address().address + '#' + server.address().port + ' (' + server.type + ')');
};
server.on('error', handlers.onError);
server.on('message', handlers.onMessage);
server.on('listening', handlers.onListening);
return server;
};

View File

@ -1,8 +1,9 @@
{
"name": "digd.js",
"version": "1.0.0",
"version": "1.2.1",
"description": "A lightweight DNS / mDNS daemon (server) for creating and capturing DNS and mDNS query and response packets to disk as binary and/or JSON. Options are similar to the Unix dig command.",
"main": "bin/digd.js",
"homepage": "https://git.coolaj86.com/coolaj86/digd.js",
"bin": {
"digd.js": "bin/digd.js"
},
@ -11,7 +12,7 @@
},
"repository": {
"type": "git",
"url": "git@git.daplie.com:Daplie/digd.js.git"
"url": "git://git.coolaj86.com:coolaj86/digd.js.git"
},
"keywords": [
"mdig",
@ -46,7 +47,7 @@
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com)",
"license": "MIT OR Apache-2.0",
"dependencies": {
"dig.js": "^1.2.1",
"hexdump.js": "^1.0.4"
"dig.js": "git+https://git.coolaj86.com/coolaj86/dig.js#v1.3",
"hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4"
}
}

View File

@ -0,0 +1,60 @@
#!/bin/bash
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com ns:set -n hellabit.com --nameserver ns1.daplie.com,ns2.daplie.domains,ns3.hellabit.com
# glue setting is rate-limited, or so it would seem
echo "sleeping between setting glue for rate limit"
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com glue:set -a 45.55.1.122 -n ns1.daplie.com
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com glue:set -a 45.55.1.122 -n ns1.daplie.domains
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com glue:set -a 45.55.1.122 -n ns1.daplie.me --tld me
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com glue:set -a 45.55.1.122 -n ns1.hellabit.com --tld com
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com devices:attach -d ns1-do -a 45.55.1.122 -n ns1.daplie.com
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com devices:attach -d ns1-do -a 45.55.1.122 -n ns1.daplie.domains
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com devices:attach -d ns1-do -a 45.55.1.122 -n ns1.daplie.me --tld me
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com devices:attach -d ns1-do -a 45.55.1.122 -n ns1.hellabit.com --tld com
# glue setting is rate-limited, or so it would seem
echo "sleeping between setting glue for rate limit"
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com glue:set -a 45.55.254.197 -n ns2.daplie.com
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com glue:set -a 45.55.254.197 -n ns2.daplie.domains
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com glue:set -a 45.55.254.197 -n ns2.daplie.me --tld me
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com glue:set -a 45.55.254.197 -n ns2.hellabit.com --tld com
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com devices:attach -d ns2-do -a 45.55.254.197 -n ns2.daplie.com
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com devices:attach -d ns2-do -a 45.55.254.197 -n ns2.daplie.domains
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com devices:attach -d ns2-do -a 45.55.254.197 -n ns2.daplie.me --tld me
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com devices:attach -d ns2-do -a 45.55.254.197 -n ns2.hellabit.com --tld com
# glue setting is rate-limited, or so it would seem
echo "sleeping between setting glue for rate limit"
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com glue:set -a 159.203.25.112 -n ns3.daplie.com
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com glue:set -a 159.203.25.112 -n ns3.daplie.domains
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com glue:set -a 159.203.25.112 -n ns3.daplie.me --tld me
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com glue:set -a 159.203.25.112 -n ns3.hellabit.com --tld com
sleep 5
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com devices:attach -d ns3-do -a 159.203.25.112 -n ns3.daplie.com
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com devices:attach -d ns3-do -a 159.203.25.112 -n ns3.daplie.domains
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com devices:attach -d ns3-do -a 159.203.25.112 -n ns3.daplie.me --tld me
node bin/daplie.js --oauth3-config ~/.oauth3+domains@daplie.com devices:attach -d ns3-do -a 159.203.25.112 -n ns3.hellabit.com --tld com

7
samples/HOWTO-NS.md Normal file
View File

@ -0,0 +1,7 @@
```bash
# Create a glue record for the nameserver
daplie glue:set -n ns1.example.com -a 12.55.12.33
# Set the nameservers for a domain
daplie ns:set -n example.com --nameservers ns1.example.com
```

View File

@ -0,0 +1,227 @@
DNS Delegation
==============
Tracing NS records trying to understand DNS delegation better.
Root Servers
------------
The root servers will return the TLD records for any domain
```bash
dig A @m.root-servers.net www.example.daplie.me
```
```
; <<>> DiG 9.8.3-P1 <<>> A @m.root-servers.net www.aj.daplie.me
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 34843
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 5, ADDITIONAL: 10
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
;www.aj.daplie.me. IN A
;; AUTHORITY SECTION:
me. 172800 IN NS c0.nic.me.
me. 172800 IN NS b0.nic.me.
me. 172800 IN NS a0.nic.me.
me. 172800 IN NS b2.nic.me.
me. 172800 IN NS a2.nic.me.
;; ADDITIONAL SECTION:
a0.nic.me. 172800 IN A 199.253.59.1
a2.nic.me. 172800 IN A 199.249.119.1
b0.nic.me. 172800 IN A 199.253.60.1
b2.nic.me. 172800 IN A 199.249.127.1
c0.nic.me. 172800 IN A 199.253.61.1
a0.nic.me. 172800 IN AAAA 2001:500:53::1
a2.nic.me. 172800 IN AAAA 2001:500:47::1
b0.nic.me. 172800 IN AAAA 2001:500:54::1
b2.nic.me. 172800 IN AAAA 2001:500:4f::1
c0.nic.me. 172800 IN AAAA 2001:500:55::1
;; Query time: 141 msec
;; SERVER: 202.12.27.33#53(202.12.27.33)
;; WHEN: Tue Oct 3 15:47:25 2017
;; MSG SIZE rcvd: 343
```
GTLD Servers
------------
The GTLD Servers will return the glue records for the nameserver which hosts the SLD in question
```bash
dig A @b0.nic.me www.aj.daplie.me
```
```
; <<>> DiG 9.8.3-P1 <<>> A @b0.nic.me www.aj.daplie.me
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52062
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 0
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
;www.aj.daplie.me. IN A
;; AUTHORITY SECTION:
daplie.me. 86400 IN NS ns2.redirect-www.org.
daplie.me. 86400 IN NS ns1.redirect-www.org.
;; Query time: 29 msec
;; SERVER: 199.253.60.1#53(199.253.60.1)
;; WHEN: Tue Oct 3 15:48:41 2017
;; MSG SIZE rcvd: 86
```
If the nameserver were "in bailiwick" then it's A/AAAA records would be returned in an additional section.
SLD Nameservers
---------------
There are three main conditions:
* Exists
* Doesn't exist (but lives here)
* Delegated
There's also the condition of *"doesn't exist (and doesn't live here)"*, but we'll save that for another day
(or try `dig @ns1.google.com A yahoo.com`).
### Exists
```bash
dig A @ns2.redirect-www.org www.aj.daplie.me
```
```
; <<>> DiG 9.8.3-P1 <<>> A @ns2.redirect-www.org www.aj.daplie.me
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52373
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
;www.aj.daplie.me. IN A
;; ANSWER SECTION:
www.aj.daplie.me. 300 IN A 45.56.59.142
;; Query time: 83 msec
;; SERVER: 66.172.33.29#53(66.172.33.29)
;; WHEN: Tue Oct 3 15:57:14 2017
;; MSG SIZE rcvd: 50
```
### Doesn't Exist (but would)
```bash
dig A @ns2.redirect-www.org doesntexist.aj.daplie.me
```
```
; <<>> DiG 9.8.3-P1 <<>> A @ns2.redirect-www.org doesntexist.aj.daplie.me
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19993
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
;doesntexist.aj.daplie.me. IN A
;; AUTHORITY SECTION:
daplie.me. 1 IN SOA ns1.redirect-www.org. hostmaster.daplie.me. 2017020100 10800 3600 1209600 1800
;; Query time: 68 msec
;; SERVER: 66.172.33.29#53(66.172.33.29)
;; WHEN: Tue Oct 3 15:59:25 2017
;; MSG SIZE rcvd: 109
```
### Delegated Subdomain
Should look something like this, I'm pretty sure:
```
;; QUESTION SECTION:
;john.daplie.me. IN A
;; AUTHORITY SECTION:
john.daplie.me. 86400 IN NS ns2.dns-host.org.
john.daplie.me. 86400 IN NS ns1.dns-host.org.
```
I think that in practice anything matching `*.john.daplie.me` would be delegated,
but I but you could do something weird like host `whatever.john.daplie.me` on the original
nameserver by A) answering to it directly on the main nameserver and B) delegating
from `whatever.john.daplie.me` back to the original nameserver in case the resolving
client makes intelligent assumptions and caching.
When a domain doesn't exist
---------------------------
### NXDOMAIN
This nameserver can respond for that domain, but no record exists
```
dig @ns1.google.com doesntexist.google.com
```
```
; <<>> DiG 9.8.3-P1 <<>> @ns1.google.com doesntexist.google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 45549
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
;doesntexist.google.com. IN A
;; AUTHORITY SECTION:
google.com. 60 IN SOA ns2.google.com. dns-admin.google.com. 170961396 900 900 1800 60
;; Query time: 50 msec
;; SERVER: 216.239.32.10#53(216.239.32.10)
;; WHEN: Wed Oct 4 01:14:09 2017
;; MSG SIZE rcvd: 90
```
### REFUSED
This nameserver does not store records for that domain
(and would appreciated it if you didn't ask)
```bash
dig @ns1.google.com daplie.com
```
```
; <<>> DiG 9.8.3-P1 <<>> @ns1.google.com daplie.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 47317
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
;daplie.com. IN A
;; Query time: 52 msec
;; SERVER: 216.239.32.10#53(216.239.32.10)
;; WHEN: Wed Oct 4 01:14:20 2017
;; MSG SIZE rcvd: 28
```

View File

@ -0,0 +1,86 @@
{ "primaryNameservers": [ "ns1.daplie.com", "ns2.daplie.com", "ns3.daplie.com" ]
, "domains": [
{ "id": "daplie.com", "revokedAt": 0 }
, { "id": "daplie.domains", "revokedAt": 0, "vanityNs": [ "ns1.daplie.domains", "ns2.daplie.domains", "ns3.daplie.domains" ] }
, { "id": "daplie.me", "revokedAt": 0, "vanityNs": [ "ns1.daplie.me", "ns2.daplie.me", "ns3.daplie.me" ] }
, { "id": "oauth3.org", "revokedAt": 0 }
]
, "records": [
{"zone":"daplie.com","name":"daplie.com","type":"NS","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ns1","data":"ns1.daplie.com"}
, {"zone":"daplie.com","name":"daplie.com","type":"NS","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ns2","data":"ns2.daplie.com"}
, {"zone":"daplie.com","name":"daplie.com","type":"NS","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ns3","data":"ns3.daplie.com"}
, {"zone":"daplie.com","name":"ns1.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ns1","address":"45.55.1.122"}
, {"zone":"daplie.com","name":"ns2.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ns2","address":"45.55.254.197"}
, {"zone":"daplie.com","name":"ns3.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ns3","address":"159.203.25.112"}
, {"zone":"daplie.me","name":"daplie.me","type":"NS","class":"IN","ttl":43200,"tld":"me","sld":"daplie","sub":"ns1","data":"ns1.daplie.me"}
, {"zone":"daplie.me","name":"daplie.me","type":"NS","class":"IN","ttl":43200,"tld":"me","sld":"daplie","sub":"ns2","data":"ns2.daplie.me"}
, {"zone":"daplie.me","name":"daplie.me","type":"NS","class":"IN","ttl":43200,"tld":"me","sld":"daplie","sub":"ns3","data":"ns3.daplie.me"}
, {"zone":"daplie.me","name":"ns1.daplie.me","type":"A","class":"IN","ttl":5,"tld":"me","sld":"daplie","sub":"ns1","address":"45.55.1.122"}
, {"zone":"daplie.me","name":"ns2.daplie.me","type":"A","class":"IN","ttl":5,"tld":"me","sld":"daplie","sub":"ns2","address":"45.55.254.197"}
, {"zone":"daplie.me","name":"ns3.daplie.me","type":"A","class":"IN","ttl":5,"tld":"me","sld":"daplie","sub":"ns3","address":"159.203.25.112"}
, {"zone":"oauth3.org","name":"ns1.oauth3.org","type":"A","class":"IN","ttl":5,"tld":"org","sld":"oauth3","sub":"ns1","address":"45.55.1.122"}
, {"zone":"oauth3.org","name":"ns2.oauth3.org","type":"A","class":"IN","ttl":5,"tld":"org","sld":"oauth3","sub":"ns2","address":"45.55.254.197"}
, {"zone":"oauth3.org","name":"ns3.oauth3.org","type":"A","class":"IN","ttl":5,"tld":"org","sld":"oauth3","sub":"ns3","address":"159.203.25.112"}
, {"zone":"oauth3.org","name":"oauth3.org","type":"NS","class":"IN","ttl":43200,"tld":"me","sld":"oauth3","sub":"ns1","data":"ns1.oauth3.org"}
, {"zone":"oauth3.org","name":"oauth3.org","type":"NS","class":"IN","ttl":43200,"tld":"me","sld":"oauth3","sub":"ns2","data":"ns2.oauth3.org"}
, {"zone":"oauth3.org","name":"oauth3.org","type":"NS","class":"IN","ttl":43200,"tld":"me","sld":"oauth3","sub":"ns3","data":"ns3.oauth3.org"}
, {"zone":"daplie.com","name":"daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"daplie.com","type":"TXT","class":"IN","ttl":43200,"tld":"com","sld":"daplie","data":["v=spf1 include:mailgun.org include:spf.mandrillapp.com include:_spf.google.com include:servers.mcsv.net include:mail.zendesk.com ~all"]}
, {"zone":"daplie.com","name":"www.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"www","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"daplie.com","type":"MX","class":"IN","ttl":43200,"tld":"com","sld":"daplie","exchange":"mxa.mailgun.org","priority":10}
, {"zone":"daplie.com","name":"daplie.com","type":"MX","class":"IN","ttl":43200,"tld":"com","sld":"daplie","exchange":"mxb.mailgun.org","priority":10}
, {"zone":"daplie.com","name":"email.daplie.com","type":"CNAME","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"email","data":"mailgun.org"}
, {"zone":"daplie.com","name":"k1._domainkey.daplie.com","type":"CNAME","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"k1._domainkey","data":"dkim.mcsv.net"}
, {"zone":"daplie.com","name":"smtp._domainkey.daplie.com","type":"TXT","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"smtp._domainkey","data":["k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdEzzYX8U31O5p5Uvyb1B50/JPMcKnsnIQcPDWWYkBUQxMt+FyD1SRZLCaVxWybZ8eFQUwxlh0qFeLd/mIIGhCazQ74a3AH+TJhz4gOAvNQHmWvS0Sv9ZZjGuDM/RdOAFSwZET8+WUpJfDADfijihj5KqMab13NDDLOQ96wObuwQIDAQAB"]}
, {"zone":"daplie.com","name":"mandrill._domainkey.daplie.com","type":"TXT","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"mandrill._domainkey","data":["v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrLHiExVd55zd/IQ/J/mRwSRMAocV/hMB3jXwaHH36d9NaVynQFYV8NaWi69c1veUtRzGt7yAioXqLj7Z4TeEUoOLgrKsn8YnckGs9i3B3tVFB+Ch/4mPhXWiNfNdynHWBcPcbJ8kjEQ2U8y78dHZj1YeRXXVvWob2OaKynO8/lQIDAQAB;"]}
, {"zone":"daplie.com","name":"iqqsuxwfyvyw.daplie.com","type":"CNAME","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"iqqsuxwfyvyw","data":"gv-roynzijsoqayyg.dv.googlehosted.com"}
, {"zone":"daplie.com","name":"support.daplie.com","type":"CNAME","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"support","data":"daplie.zendesk.com"}
, {"zone":"daplie.com","name":"proxy.tardigrade.devices.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"proxy.tardigrade.devices","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"redleader.devices.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"redleader.devices","address":"104.36.98.166"}
, {"zone":"daplie.com","name":"beast.devices.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"beast.devices","address":"96.19.92.42"}
, {"zone":"daplie.com","name":"ossus.devices.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"ossus.devices","address":"73.65.206.97"}
, {"zone":"daplie.com","name":"leo.devices.daplie.com","type":"A","class":"IN","ttl":3600,"tld":"com","sld":"daplie","sub":"leo.devices","address":"45.56.59.142"}
, {"zone":"daplie.com","name":"proxy.leo.devices.daplie.com","type":"A","class":"IN","ttl":3600,"tld":"com","sld":"daplie","sub":"proxy.leo.devices","address":"45.56.59.142"}
, {"zone":"daplie.com","name":"git.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"git","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"tunnel.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"tunnel","address":"162.243.160.23"}
, {"zone":"daplie.com","name":"api.daplie.com","type":"A","class":"IN","ttl":43200,"tld":"com","sld":"daplie","sub":"api","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"preorder.daplie.com","type":"CNAME","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"preorder","data":"daplie.myshopify.com"}
, {"zone":"daplie.com","name":"rvpn.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"rvpn","address":"104.236.182.24"}
, {"zone":"daplie.com","name":"mailapp.daplie.com","type":"CNAME","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"mailapp","data":"mandrillapp.com"}
, {"zone":"daplie.com","name":"hero.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"hero","address":"138.197.54.15"}
, {"zone":"daplie.com","name":"mattermost.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"mattermost","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"china-ftp.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"china-ftp","address":"210.5.144.209"}
, {"zone":"daplie.com","name":"shop.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"shop","address":"23.227.38.32"}
, {"zone":"daplie.com","name":"shop.daplie.com","type":"CNAME","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"shop","data":"shops.myshopify.com"}
, {"zone":"daplie.com","name":"new.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"new","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"media.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"media","address":"45.56.59.142"}
, {"zone":"daplie.com","name":"domains.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"domains","address":"23.228.168.108"}
, {"zone":"daplie.com","name":"labs.daplie.com","type":"A","class":"IN","ttl":5,"tld":"com","sld":"daplie","sub":"labs","address":"23.228.168.108"}
, {"zone":"daplie.domains","name":"daplie.domains","type":"NS","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"ns1","data":"ns1.daplie.domains"}
, {"zone":"daplie.domains","name":"daplie.domains","type":"NS","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"ns2","data":"ns2.daplie.domains"}
, {"zone":"daplie.domains","name":"daplie.domains","type":"NS","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"ns3","data":"ns3.daplie.domains"}
, {"zone":"daplie.domains","name":"ns1.daplie.domains","type":"A","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"ns1","address":"45.55.1.122"}
, {"zone":"daplie.domains","name":"ns2.daplie.domains","type":"A","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"ns2","address":"45.55.254.197"}
, {"zone":"daplie.domains","name":"ns3.daplie.domains","type":"A","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"ns3","address":"159.203.25.112"}
, {"zone":"daplie.domains","name":"leo.devices.daplie.domains","type":"A","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"leo.devices","address":"45.56.59.142"}
, {"zone":"daplie.domains","name":"daplie.domains","type":"A","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"","address":"45.56.59.142","aname":"leo.devices.daplie.domains"}
, {"zone":"daplie.domains","name":"daplie.domains","type":"A","class":"IN","ttl":5,"tld":"domains","sld":"daplie","sub":"www","address":"45.56.59.142","aname":"leo.devices.daplie.domains"}
]
}

125
samples/db.js Normal file
View File

@ -0,0 +1,125 @@
'use strict';
module.exports =
{
"primaryNameservers": [ "localhost" ] // 'ns1.vanity-dns.org'
, "domains": [
{ "id": "example.com", "revokedAt": 0 }
, { "id": "smith.example.com", "revokedAt": 0 }
, { "id": "in-delegated.example.com", "revokedAt": 0 }
, { "id": "john.smith.example.com", "revokedAt": 0, "vanityNs": [ "ns1.dns-server.net", "ns2.dns-server.net" ] }
// test and probably remove
//, { "id": "out-delegated.example.com", "revokedAt": 0 }
]
, "records": [
// zone example.com should be able to have some records on its own
{ "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "A", "address": "1.2.3.4", "aname": "fido.devices.example.com" }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "MX", "priority": 10, "exchange": "mxa.example.org" }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "MX", "priority": 10, "exchange": "mxb.example.org" }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "SRV", "priority": 10, "weight": 20, "port": 65065, "target": "spot.devices.example.com" }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "TXT", "data": [ "foo bar baz" ] }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "TXT", "data": [ "foo", "bar", "baz" ] }
// A, CNAME, ANAME, MX, SRV, TXT
, { "zone": "example.com", "name": "a.example.com", "tld": "com", "sld": "example", "sub": "a"
, "type": "A", "address": "4.3.2.1" }
, { "zone": "example.com", "name": "aaaa.example.com", "tld": "com", "sld": "example", "sub": "aaaa"
, "type": "AAAA", "address": "::1" }
, { "zone": "example.com", "name": "aname.example.com", "tld": "com", "sld": "example", "sub": "aname"
, "type": "A", "aname": "amazon.com" }
, { "zone": "example.com", "name": "devname.example.com", "tld": "com", "sld": "example", "sub": "devname"
, "type": "A", "address": "1.2.3.4", "aname": "fido.devices.example.com" }
, { "zone": "example.com", "name": "cname.example.com", "tld": "com", "sld": "example", "sub": "cname"
, "type": "CNAME", "data": "example.com" } // TODO should return additional
, { "zone": "example.com", "name": "mx.example.com", "tld": "com", "sld": "example", "sub": "mx"
, "type": "MX", "priority": 10, "exchange": "mxa.example.org" }
, { "zone": "example.com", "name": "mx.example.com", "tld": "com", "sld": "example", "sub": "mx"
, "type": "MX", "priority": 10, "exchange": "mxb.example.org" }
, { "zone": "example.com", "name": "srv.example.com", "tld": "com", "sld": "example", "sub": "srv"
, "type": "SRV", "priority": 10, "weight": 20, "port": 65065, "target": "spot.devices.example.com" }
, { "zone": "example.com", "name": "txt.example.com", "tld": "com", "sld": "example", "sub": "txt"
, "type": "TXT", "data": [ "foo bar baz" ] }
, { "zone": "example.com", "name": "mtxt.example.com", "tld": "com", "sld": "example", "sub": "mtxt"
, "type": "TXT", "data": [ "foo", "bar", "baz" ] }
, { "zone": "example.com", "type": "NS", "name": "ns.example.com"
, "tld": "com", "sld": "example", "sub": "ns", "data": "ns1.vanity-dns.org" }
, { "zone": "example.com", "type": "NS", "name": "ns.example.com"
, "tld": "com", "sld": "example", "sub": "ns", "data": "ns2.vanity-dns.org" }
// www., email., etc just for fun
, { "zone": "example.com", "name": "www.example.com", "tld": "com", "sld": "example", "sub": "www"
, "type": "A", "address": "1.2.3.4", "aname": "fido.devices.example.com" }
, { "zone": "example.com", "name": "email.example.com", "tld": "com", "sld": "example", "sub": "email"
, "type": "CNAME", "data": "mailgun.org" }
// a wildcard domain
// wild.example.com does NOT match
// exists.wild.example.com DOES match, statically
// doesntexist.wild.example.com DOES match, wildly
, { "zone": "example.com", "name": "*.wild.example.com", "tld": "com", "sld": "example", "sub": "*.wild"
, "type": "A", "address": "12.34.56.78" }
, { "zone": "example.com", "name": "exists.wild.example.com", "tld": "com", "sld": "example", "sub": "exists.wild"
, "type": "A", "address": "123.0.0.45" }
// Out-delegated Domains
, { "zone": "example.com", "type": "NS", "name": "out-delegated.example.com"
, "tld": "com", "sld": "example", "sub": "out-delegated", "data": "ns1.vanity-dns.org" }
, { "zone": "example.com", "type": "NS", "name": "out-delegated.example.com"
, "tld": "com", "sld": "example", "sub": "out-delegated", "data": "ns2.vanity-dns.org" }
// In-delegated Domains
, { "zone": "example.com", "type": "NS", "name": "in-delegated.example.com"
, "tld": "com", "sld": "example", "sub": "in-delegated", "data": "localhost" }
, { "zone": "example.com", "name": "fido.devices.example.com", "tld": "com", "sld": "example", "sub": "fido.devices"
, "device": "abcdef123"
, "type": "ANAME", "address": "1.2.3.4" }
// zone example.com can delegate smith.example.com 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. example.com -> www.example.com
// to let someone exclusively "own" the root domain, but none of the children?
, { "zone": "example.com", "type": "NS", "name": "smith.example.com"
, "tld": "com", "sld": "example", "sub": "smith", "data": "ns1.vanity-dns.org" }
, { "zone": "example.com", "name": "smith.example.com", "tld": "com", "sld": "example", "sub": "smith"
, "type": "NS", "data": "ns2.vanity-dns.org" }
//
// now the zone "smith.example.com" can be independently owned (and delegated)
// ... but what about email for john@example.com with john@example.com?
, { "zone": "smith.example.com", "name": "smith.example.com", "tld": "example.com", "sld": "smith", "sub": ""
, "type": "A", "address": "45.56.59.142", "aname": "rex.devices.smith.example.com" }
, { "zone": "smith.example.com", "name": "www.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "www"
, "type": "CNAME", "data": "smith.example.com" }
, { "zone": "smith.example.com", "name": "john.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "john"
, "type": "NS", "data": "ns1.vanity-dns.org" }
, { "zone": "smith.example.com", "name": "john.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "john"
, "type": "NS", "data": "ns2.vanity-dns.org" }
// there can be a wildcard, to which a delegation is the exception
, { "zone": "smith.example.com", "name": "*.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "*"
, "type": "A", "address": "45.56.59.142", "aname": "rex.devices.smith.example.com" }
// there can be an exception to the delegation
, { "zone": "smith.example.com", "name": "exception.john.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "exception.john"
, "type": "A", "address": "45.56.59.142", "aname": "rex.devices.smith.example.com" }
//
// john.smith.example.com
//
, { "zone": "john.smith.example.com", "name": "john.smith.example.com", "tld": "smith.example.com", "sld": "john", "sub": ""
, "type": "A", "address": "45.56.59.142", "aname": "rex.devices.smith.example.com" }
]
}
;

98
samples/db.json Normal file
View File

@ -0,0 +1,98 @@
{
"primaryNameservers": [ "localhost" ]
, "domains": [
{ "id": "example.com", "revokedAt": 0 }
, { "id": "smith.example.com", "revokedAt": 0 }
, { "id": "in-delegated.example.com", "revokedAt": 0 }
, { "id": "john.smith.example.com", "revokedAt": 0, "vanityNs": [ "ns1.dns-server.net", "ns2.dns-server.net" ] }
]
, "records": [
{ "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "A", "address": "1.2.3.4", "aname": "fido.devices.example.com" }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "MX", "priority": 10, "exchange": "mxa.example.org" }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "MX", "priority": 10, "exchange": "mxb.example.org" }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "SRV", "priority": 10, "weight": 20, "port": 65065, "target": "spot.devices.example.com" }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "TXT", "data": [ "foo bar baz" ] }
, { "zone": "example.com", "name": "example.com", "tld": "com", "sld": "example", "sub": ""
, "type": "TXT", "data": [ "foo", "bar", "baz" ] }
, { "zone": "example.com", "name": "a.example.com", "tld": "com", "sld": "example", "sub": "a"
, "type": "A", "address": "4.3.2.1" }
, { "zone": "example.com", "name": "aaaa.example.com", "tld": "com", "sld": "example", "sub": "aaaa"
, "type": "AAAA", "address": "::1" }
, { "zone": "example.com", "name": "aname.example.com", "tld": "com", "sld": "example", "sub": "aname"
, "type": "A", "aname": "amazon.com" }
, { "zone": "example.com", "name": "devname.example.com", "tld": "com", "sld": "example", "sub": "devname"
, "type": "A", "address": "1.2.3.4", "aname": "fido.devices.example.com" }
, { "zone": "example.com", "name": "cname.example.com", "tld": "com", "sld": "example", "sub": "cname"
, "type": "CNAME", "data": "example.com" }
, { "zone": "example.com", "name": "mx.example.com", "tld": "com", "sld": "example", "sub": "mx"
, "type": "MX", "priority": 10, "exchange": "mxa.example.org" }
, { "zone": "example.com", "name": "mx.example.com", "tld": "com", "sld": "example", "sub": "mx"
, "type": "MX", "priority": 10, "exchange": "mxb.example.org" }
, { "zone": "example.com", "name": "srv.example.com", "tld": "com", "sld": "example", "sub": "srv"
, "type": "SRV", "priority": 10, "weight": 20, "port": 65065, "target": "spot.devices.example.com" }
, { "zone": "example.com", "name": "txt.example.com", "tld": "com", "sld": "example", "sub": "txt"
, "type": "TXT", "data": [ "foo bar baz" ] }
, { "zone": "example.com", "name": "mtxt.example.com", "tld": "com", "sld": "example", "sub": "mtxt"
, "type": "TXT", "data": [ "foo", "bar", "baz" ] }
, { "zone": "example.com", "type": "NS", "name": "ns.example.com"
, "tld": "com", "sld": "example", "sub": "ns", "data": "ns1.vanity-dns.org" }
, { "zone": "example.com", "type": "NS", "name": "ns.example.com"
, "tld": "com", "sld": "example", "sub": "ns", "data": "ns2.vanity-dns.org" }
, { "zone": "example.com", "name": "www.example.com", "tld": "com", "sld": "example", "sub": "www"
, "type": "A", "address": "1.2.3.4", "aname": "fido.devices.example.com" }
, { "zone": "example.com", "name": "email.example.com", "tld": "com", "sld": "example", "sub": "email"
, "type": "CNAME", "data": "mailgun.org" }
, { "zone": "example.com", "name": "*.wild.example.com", "tld": "com", "sld": "example", "sub": "*.wild"
, "type": "A", "address": "12.34.56.78" }
, { "zone": "example.com", "name": "exists.wild.example.com", "tld": "com", "sld": "example", "sub": "exists.wild"
, "type": "A", "address": "123.0.0.45" }
, { "zone": "example.com", "type": "NS", "name": "out-delegated.example.com"
, "tld": "com", "sld": "example", "sub": "out-delegated", "data": "ns1.vanity-dns.org" }
, { "zone": "example.com", "type": "NS", "name": "out-delegated.example.com"
, "tld": "com", "sld": "example", "sub": "out-delegated", "data": "ns2.vanity-dns.org" }
, { "zone": "example.com", "type": "NS", "name": "in-delegated.example.com"
, "tld": "com", "sld": "example", "sub": "in-delegated", "data": "localhost" }
, { "zone": "example.com", "name": "fido.devices.example.com", "tld": "com", "sld": "example", "sub": "fido.devices"
, "device": "abcdef123"
, "type": "ANAME", "address": "1.2.3.4" }
, { "zone": "example.com", "type": "NS", "name": "smith.example.com"
, "tld": "com", "sld": "example", "sub": "smith", "data": "ns1.vanity-dns.org" }
, { "zone": "example.com", "name": "smith.example.com", "tld": "com", "sld": "example", "sub": "smith"
, "type": "NS", "data": "ns2.vanity-dns.org" }
, { "zone": "smith.example.com", "name": "smith.example.com", "tld": "example.com", "sld": "smith", "sub": ""
, "type": "A", "address": "45.56.59.142", "aname": "rex.devices.smith.example.com" }
, { "zone": "smith.example.com", "name": "www.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "www"
, "type": "CNAME", "data": "smith.example.com" }
, { "zone": "smith.example.com", "name": "john.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "john"
, "type": "NS", "data": "ns1.vanity-dns.org" }
, { "zone": "smith.example.com", "name": "john.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "john"
, "type": "NS", "data": "ns2.vanity-dns.org" }
, { "zone": "smith.example.com", "name": "*.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "*"
, "type": "A", "address": "45.56.59.142", "aname": "rex.devices.smith.example.com" }
, { "zone": "smith.example.com", "name": "exception.john.smith.example.com", "tld": "example.com", "sld": "smith", "sub": "exception.john"
, "type": "A", "address": "45.56.59.142", "aname": "rex.devices.smith.example.com" }
, { "zone": "john.smith.example.com", "name": "john.smith.example.com", "tld": "smith.example.com", "sld": "john", "sub": ""
, "type": "A", "address": "45.56.59.142", "aname": "rex.devices.smith.example.com" }
]
}

23
samples/hellabit.enom.sh Normal file
View File

@ -0,0 +1,23 @@
daplie domains:list # shows hellabit.com in my list of domains
# for hellabit.com to lookup itself (chicken and egg problem),
# we must first set glue records
daplie glue:set -n ns1.hellabit.com --tld com -a 138.197.72.1
daplie glue:set -n ns2.hellabit.com --tld com -a 162.243.136.134
# now we can set hellabit.com to use nsx.hellabit.com nameservers
daplie ns:set -n hellabit.com --tld com --nameservers ns1.hellabit.com,ns2.hellabit.com
# now we can't use the dns tools because digd.js does not (yet) have oauth3 compatible apis
# these won't work
# daplie devices:set -d sfo2.devices.hellabit.com -a 138.197.216.176
# daplie devices:attach -d sfo2.devices.hellabit.com -n hellabit.com
# daplie devices:attach -d sfo2.devices.hellabit.com -n www.hellabit.com
# now you can test that your hard work worked
# < ==== NOTE ==== > It may take a few minutes before this starts to work as you'd expect
dig +trace ns1.hellabit.com
dig +trace ns2.hellabit.com
dig +trace hellabit.com
dig +trace www.hellabit.com

73
samples/parse-records.js Normal file
View File

@ -0,0 +1,73 @@
'use strict';
/*
* parses files in the format "type|domain|value|json\n"
*/
var filename = process.argv[2];
var fs = require('fs');
var file = fs.readFileSync(filename, 'utf8');
var zone = 'daplie.com';
file.trim().split(/\n/).forEach(function (line) {
var parts = line.split(/\|/);
var type = parts[0];
var name = parts[1] || zone;
var domain = name.split('.');
var thing = parts[2];
var json = JSON.parse(parts[3]);
var address; // A, AAAA
var flag, tag, value; // CAA
var data; // CNAME, NS, PTR, ... TXT (as array)
var priority; // MX, SRV
var exchange; // MX
var weight, port, target; // SRV
if (/^(A|AAAA)$/.test(type)) {
address = thing;
}
if (/^(CNAME|NS|PTR)$/.test(type)) {
data = thing;
}
if (/^(TXT)$/.test(type)) {
data = [ thing ];
}
if (/^(MX)$/.test(type)) {
exchange = thing;
}
if (/^(MX|SRV)$/.test(type)) {
priority = json.priority || 10;
}
var obj = {
zone: zone
, name: name
, type: type
, class: 'IN'
, ttl: 5 // 12 hours 43200 // 3 days 259200
, tld: domain.pop()
, sld: domain.pop()
, sub: domain.join('.') || undefined
, address: address
, aname: undefined
, flag: flag
, tag: tag
, value: value
, data: data
, exchange: exchange
, priority: priority
, weight: weight
, port: port
, target: target
};
console.log(",", JSON.stringify(obj));
});

6
tests/BUGS.txt Normal file
View File

@ -0,0 +1,6 @@
dig @localhost -p 65053 ANY aaaa.example.com
# Using the wrong type (A instead of AAAA) results in an erroneous packet
{ "zone": "example.com", "name": "aaaa.example.com", "tld": "com", "sld": "example", "sub": "aaaa"
, "type": "A", "address": "::1" }

141
tests/README.md Normal file
View File

@ -0,0 +1,141 @@
Tests todo:
A - SERVFAIL - should return empty response, ra 0
A - NXDOMAIN - should return SOA
A - NOERROR - should return A record
- or delegated NS record
Record delegated to self should return SOA record rather than NS delegation
Casing should match on question section, always.
Casing should match on other sections if a direct match? (not required, but efficient for compression pointers)
- www.EXample.COm + example.com = EXample.COm
- EXample.COm + www.example.com = www.EXample.COm
- (this should be taken care of by compression pointer logic, once implemented)
Send malformed packets (both as queries and as answers):
- bad question count, answer count, etc
- bad rdata (too long, too short)
- proper packets, truncated
- 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
Test that A queries for ANAME-enabled records (but no address) recurse (regardless of general recursion settings).
* 0-anames.example.com
* 1-aname.example.com
* 2-anames.example.com
Generally speaking test the cases of 0, 1, and 2 records of any given type (null case, single case, multi case)
### Variables
```
port=65053
ns=localhost
# For the sake of accuracy, it's most important to test with the standard unix dig tool
digcmd="dig"
# For the sake of completeness, it's important to test with our very own dig tool
#digcmd="node bin/dig.js"
```
### Run the server
```
# Serve:
node bin/digd.js +norecurse -p $port --input sample/db.json
```
### Manual Tests
```
# Sample Data:
# no A records for out-delegated.example.com
# two external NS records for delegted.example.com
# zone example.com exists
# Test:
# should return NS records in AUTHORITY section, nothing else
$digcmd @$ns -p $port A out-delegated.example.com
$digcmd @$ns -p $port ANY out-delegated.example.com
# should return SOA records in AUTHORITY section, nothing else
$digcmd @$ns -p $port A in-delegated.example.com
$digcmd @$ns -p $port ANY in-delegated.example.com
# should return NS records in ANSWER section, nothing else
$digcmd @$ns -p $port NS out-delegated.example.com
$digcmd @$ns -p $port NS in-delegated.example.com
# Sample Data:
# two A records for example.com
# no NS records
# Test:
# should return records in ANSWER section, nothing else
$digcmd @$ns -p $port A example.com
$digcmd @$ns -p $port AAAA example.com
$digcmd @$ns -p $port CNAME example.com
$digcmd @$ns -p $port MX example.com
$digcmd @$ns -p $port SRV example.com
$digcmd @$ns -p $port TXT example.com
$digcmd @$ns -p $port ANY example.com
# should return SOA records in AUTHORITY section, nothing else
$digcmd @$ns -p $port A doesntexist.example.com
$digcmd @$ns -p $port NS doesntexist.example.com
# Sample Data:
# two A records for a.example.com
# has **internal** NS records
# Test:
# should return record of correct type in ANSWER section, nothing else
$digcmd @$ns -p $port A a.example.com
$digcmd @$ns -p $port AAAA aaaa.example.com
$digcmd @$ns -p $port CNAME cname.example.com
$digcmd @$ns -p $port A cname.example.com # Special Case, should return CNAME record
$digcmd @$ns -p $port MX mx.example.com
$digcmd @$ns -p $port SRV srv.example.com
$digcmd @$ns -p $port TXT txt.example.com
$digcmd @$ns -p $port TXT mtxt.example.com
# should return record of correct type in ANSWER section, and SOA / NS
$digcmd @$ns -p $port ANY a.example.com
$digcmd @$ns -p $port ANY aaaa.example.com
$digcmd @$ns -p $port ANY mx.example.com
$digcmd @$ns -p $port ANY srv.example.com
$digcmd @$ns -p $port ANY txt.example.com
$digcmd @$ns -p $port ANY mtxt.example.com
# Test:
# all subdomains of a delegated domain should return NS for that domain
$digcmd @$ns -p 65053 ANY ns.example.com
$digcmd @$ns -p 65053 ANY foo.ns.example.com
$digcmd @$ns -p 65053 ANY bar.foo.ns.example.com
# should return SOA record in AUTHORITY section, nothing else
$digcmd @$ns -p $port A doesntexist.a.example.com
# should return NS records in ANSWER section, nothing else
$digcmd @$ns -p $port NS a.example.com
# Test:
# wildcard record should match after static records
$digcmd @$ns -p $port ANY wild.example.com # no record
$digcmd @$ns -p $port ANY exists.wild.example.com # static record, before wildcard
$digcmd @$ns -p $port ANY foo.exists.wild.example.com # no record
$digcmd @$ns -p $port ANY doesntexist.wild.example.com # single-level wildcard
$digcmd @$ns -p $port ANY alsedoesntexist.wild.example.com # single-level wildcard
$digcmd @$ns -p $port ANY foo.doesntexist.wild.example.com # no second-level wildcard
```