Fast, lightweight, easy-to-extend, easy-to-test, pure JavaScript (ES5.1) implementation for DNS / mDNS.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
AJ ONeal 727e1c506b docs: fix link typo 1 年之前
bin v1.2.13: update docs 5 年之前
examples v1.2.13: update docs 5 年之前
packer add CAA packer, expand CCA parser 7 年之前
parser add CAA packer, expand CCA parser 7 年之前
samples finished PTR parser 7 年之前
test v1.2.13: update docs 5 年之前
.gitignore ignore generated json files 7 年之前
.gitmodules reorganzing 7 年之前
.prettierrc v1.2.13: update docs 5 年之前
CHANGELOG add standard files 7 年之前
LICENSE v1.2.13: update docs 5 年之前
ORIGINAL_PROBLEM.md update docs 7 年之前
README.md docs: fix link typo 1 年之前
browser.js started browser code 7 年之前
demo.css started fixing format to match native dns. Spotted bug in unpacking labels. Not fixed yet 7 年之前
demo.html more dir restructure updates 7 年之前
dns.classes.js in reverse also 7 年之前
dns.js limited how often we log about unsupported features 7 年之前
dns.opcodes.js add OPCODES 7 年之前
dns.packer.js allow empty string in query 6 年之前
dns.parser.js limited how often we log about unsupported features 7 年之前
dns.rcodes.js add RCODES 7 年之前
dns.rdata.pack.js dir for packer 7 年之前
dns.rdata.parse.js load parsers correctly 7 年之前
dns.type.any.js clean up 7 年之前
dns.types.js prefer forEach to for 7 年之前
dns.unpack-labels.js disallow forward compression pointers 7 年之前
notes.md renamed some files and added objective statement and purpose of document 7 年之前
package-lock.json v1.2.13: update docs 5 年之前
package.json v1.2.13: update docs 5 年之前
parse-binary-test.js added the test code that AJ wrote 7 年之前

README.md

dns-suite.js

| Built by Root for Telebit and Hub

| dns-suite.js | dig.js | mdig.js | digd.js

Fast, lightweight, and easy-to-extend Vanilla JS (ES5.1) implementation of DNS / mDNS for Node.js and Browsers.

  • Full DNS Support
    • Queries
    • Answers
    • Authority
    • Additional
  • Built for Debugging
    • capture
    • packing (JSON to DNS/mDNS)
    • parsing (DNS/mDNS to JSON)
    • linting (finding errors in packets)

Uses DataView, Uint8Array, Uint16Array, and ArrayBuffer

Similar API to dns.js and native-dns-packet.

Example Query

var DNSPacket = require('dns-suite').DNSPacket;

var query = {
	header: {
		id: rnd,
		qr: 0,
		opcode: 0,
		aa: 0,
		rd: 1,
		ra: 0,
		rcode: 0
	},
	question: [
		{
			name: 'google.com',
			typeName: 'A',
			className: 'IN'
		}
	]
};
var buffer = DNSPacket.pack(query);

Example Response

var DNSPacket = require('dns-suite').DNSPacket;
DNSPacket.parse(buffer);
{
	"header": {
		"id": 5423,
		"qr": 0,
		"opcode": 0,
		"aa": 0,
		"tc": 0,
		"rd": 1,
		"ra": 0,
		"res1": 0,
		"res2": 0,
		"res3": 0,
		"rcode": 0
	},
	"question": [
		{
			"name": "bowie._sftp-ssh._tcp.local",
			"type": 1,
			"typeName": "A",
			"class": 1,
			"className": "IN",
			"byteLength": 32
		}
	],
	"answer": [],
	"authority": [],
	"additional": [],
	"edns_options": [],
	"byteLength": 44
}

Install

npm install --save dns-suite

Test:

node ./node_modules/dns-suite/examples/dns-pack.js

Usage

  • CLI
  • API

CLI Usage

When installed globally you can use these commands:

dns-parse.js </path/to/packet.dns.bin> [out.json]   # parses a saved DNS packet to JSON
dns-pack.js </path/to/packet.dns.json> [out.bin]    # packs a JSON DNS packet to binary
dns-test.js </path/to/packet.dns(.json|.bin)>       # convert a packet back and forth to test reciprocity of the packer and parser

For capturing packets you should use dig.js with the --output option. It can capture mDNS as well. See https://git.coolaj86.com/coolaj86/dig.js#options.

You can also access them directly from node_modules/dns-suite in a project:

node node_modules/dns-suite/bin/dns-parse.js node_modules/dns-suite/samples/a-0.mdns.bin

Library API

  • DNSPacket.parse(nodeOrArrayBuffer) returns json (as shown above)
  • DNSPacket.pack(packet) returns ArrayBuffer (browser and node)
  • DNSPacket.write(packet) returns NodeBuffer (node only)

node.js:

var nodeBuffer = fs.readFileSync('./samples/a-0.mdns.bin');
var arrayBuffer = nodeBuffer.buffer;

var DNSPacket = require('dns-suite').DNSPacket;
var packet = DNSPacket.parse(arrayBuffer);
var ab = DNSPacket.pack(packet);

console.log(packet);
console.log(new Uint8Array(ab));

Browser:

var arrayBuffer = new Uint8Array.from([
	/* bytes */
]).buffer;

var packet = DNSPacket.parse(arrayBuffer);
var ab = DNSPacket.pack(packet);

console.log(packet);
console.log(new Uint8Array(ab));

Capturing Packets

We have a command line tool for that! See dig.js.

# Install
npm install -g 'git+https://git.coolaj86.com/coolaj86/dig.js.git'

# Use with DNS
dig.js A coolaj86.com --output .

# Use with mDNS
dig.js --mdns PTR _services._dns-sd._udp.local --output .

Resource Record Examples

  • SOA
  • NS
  • A
  • AAAA
  • CNAME
  • MX
  • TXT
  • SRV
  • PTR

SOA

I'm pretty sure that the SOA only goes in the authority section (except when SOA is queried explicitly) and that it's only given as a response to any empty set (where RCODE == NXDOMAIN) to affirm "yes, I am responsible for this domain but, no, I don't have a record for it".

If another nameserver has been delegated authority for a particular subdomain a set of NS records should be returned instead.

{
	"name": "yahoo.com",
	"type": 6,
	"typeName": "SOA",
	"class": 1,
	"className": "IN",
	"ttl": 599,
	"primary": "ns1.yahoo.com",
	"admin": "hostmaster.yahoo-inc.com",
	"serial": 2017092539,
	"refresh": 3600,
	"retry": 300,
	"expiration": 1814400,
	"minimum": 600
}

NS

I'm also pretty sure that the NS only goes in the authority section (except when NS is queried explicitly) and that it's given as a successful response (RCODE == SUCCESS) to any query type (A or AAAA, MX, TXT, or SRV) where the answer sections is an empty set because the records in question have been delegated to another nameserver.

{
	"name": "google.com",
	"type": 2,
	"typeName": "NS",
	"class": 1,
	"className": "IN",
	"ttl": 82790,
	"data": "ns3.google.com"
}

A

The most common type of record. Returns the IPv4 address for a given domain.

{
	"name": "www.linode.com",
	"type": 1,
	"typeName": "A",
	"class": 1,
	"className": "IN",
	"ttl": 291,
	"address": "72.14.191.202"
}

AAAA

Returns the IPv6 address for a given domain.

{
	"name": "irc6.geo.oftc.net",
	"type": 28,
	"typeName": "AAAA",
	"class": 1,
	"className": "IN",
	"ttl": 59,
	"address": "2607:f8f0:610:4000:211:11ff:fe1c:7bec"
}

CNAME

The CNAME is used to look up the IP address for the given alias. (the alias is often referred to incorrectly as a CNAME but it is, in fact, the alias)

{
	"name": "www.nodejs.org",
	"type": 5,
	"typeName": "CNAME",
	"class": 1,
	"className": "IN",
	"ttl": 3600,
	"data": "nodejs.org"
}

MX

Mail Exchange Records show the alias that should be looked up to know where incoming mail should be sent.

{
	"name": "microsoft.com",
	"type": 15,
	"typeName": "MX",
	"class": 1,
	"className": "IN",
	"ttl": 197,
	"priority": 10,
	"exchange": "microsoft-com.mail.protection.outlook.com"
}

TXT

Often used for outgoing mail validations, public keys, lots of arbitrary stuff.

{
	"name": "aol.com",
	"type": 16,
	"typeName": "TXT",
	"class": 1,
	"className": "IN",
	"ttl": 1926,
	"data": ["v=spf1 ptr:mx.aol.com ?all"]
}

SRV

A way to associate a service with a port and other relevant information. Used for federated / dencentralized protocols (like XMPP) and mDNS/DLNA/UPnP/DNS-SD type stuff.

{
	"name": "_xmpp-server._tcp.gmail.com",
	"type": 33,
	"typeName": "SRV",
	"class": 1,
	"className": "IN",
	"ttl": 900,
	"priority": 5,
	"weight": 0,
	"port": 5269,
	"target": "xmpp-server.l.google.com"
}

PTR

Used for mDNS/DNS-SD type discoveries and anti-spam reverse lookup verification for mail servers.

{
	"name": "_pdl-datastream._tcp.local",
	"type": 12,
	"typeName": "PTR",
	"class": 1,
	"className": "IN",
	"ttl": 255,
	"data": "Canon MF620C Series._pdl-datastream._tcp.local"
}

All Properties

For simplicity, here's a list of all properties, just for fun:

{
  // All RRs
  "name": "example.com",
  "type": 1,
  "typeName": "A",
  "class": 1,
  "className": "IN",
  "ttl": 600,

  // SOA
  "primary": "ns1.yahoo.com",
  "admin": "hostmaster.yahoo-inc.com",
  "serial": 2017092539,
  "refresh": 3600,
  "retry": 300,
  "expiration": 1814400,
  "minimum": 600,

  // A, AAAA
  "address": "72.14.191.202",

  // CNAME, NS, PTR
  "data": "ns3.google.com",

  // TXT
  // "data": [ "v=spf1 ptr:mx.aol.com ?all" ],

  // MX
  "priority": 10,
  "exchange": "microsoft-com.mail.protection.outlook.com",

  // SRV
  "priority": 5,
  "weight": 0,
  "port": 5269,
  "target": "xmpp-server.l.google.com"
}

Contributing and Development

How to add a new parser

Each RR (aka Resource Record or RData) parser is individual. Examples include:

  • A (parser/type.a.js)
  • AAAA (parser/type.aaaa.js)
  • CNAME (parser/type.cname.js)
  • TXT (parser/type.txt.js)
  • SRV (parser/type.srv.js)

Let's say that To create a parser for a type which we don't currently support, just add the appropriate information to dns.types.js and create a file for the name of the type in the format parser/type.<typename>.js.

For example, if CNAME wasn't already supported and I wanted to add support for it I would follow these steps:

  1. Update dns.types.js if it's not there already.
  A: 			0x01	//   1
, NS: 		0x02  //   2
, CNAME: 	0x05  //   5    // I would simply add this line
, SOA: 		0x06  //   6
  1. Capture a packet to test/fixtures/<domain>.<tld>.<type>.bin

This will construct and send a DNS query and save the first result that comes back.

In some cases (such as CNAME), the typical (or required) way to illicit the desired response is to make a request of a different type.

If that's the case, manually rename the the file afterwards.

Ideally you should have some idea of what the result file should look like and should place that in test/fixtures/<domain>.<tld>.<type>.json

npm install -g dig.js
dig.js --name www.google.com --type CNAME --output ./samples/
  1. Create parser/type.cname.js

Copy parser/type.TEMPLATE.js to the type for which you wish to create support (parser/type.cname.js in this example) and fill in the blanks.

var unpackLabels = exports.DNS_UNPACK_LABELS || require('./dns.unpack-labels.js').DNS_UNPACK_LABELS;
exports.DNS_PARSER_TYPE_CNAME = function (ab, packet, record) {
  // record = { rdstart, rdlength, type, class }
  // example of not parsing and just leaving as binary data
  record.data = new Uint8Array(ab.slice(record.rdstart, record.rdstart + record.rdlength));

  return record;
};

}('undefined' !== typeof window ? window : exports));
  1. Document what you've learned in doc/<type>.txt

You may be right or you might be wrong, but you might be right.

In any case, take a minute to document some of the gritty details of what you learned about this record type - tips, tricks, little-known facts, etc.

This may help (or wildly mislead) others if there's a bug in your parser that they need to track down. At the very least someone can follow a few links you followed and your thought process.

  1. Check that my changes include these files
├── README.md
├── demo.html         (add the appropriate script tag)
├── doc
|   └── cname.txt
├── dns.classes.js    (not necessarily, but potentially)
├── dns.types.js
├── package.json      (bump the minor version)
├── packer
|   └── type.cname.js
├── parser
|   └── type.cname.js
└── test
    └── fixtures
        ├── www.google.com.cname.bin
        └── www.google.com.cname.js