This commit is contained in:
AJ ONeal 2017-09-29 17:59:57 -06:00
commit 4dadc1c443
42 changed files with 1344 additions and 105 deletions

View File

@ -92,27 +92,24 @@ Usage
* CLI
* API
**CLI**
### CLI Usage
You can work directly from `node_modules/dns-suite`:
When installed globally you can use these commands:
```bash
pushd node_modules/dns-suite/
```
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
mdns-capture.js <prefixname> [startnum] # listen and save all mDNS packets, numbering by sequence of arrival
```
Parsing a saved packet
You can also access them directly from `node_modules/dns-suite` in a project:
```bash
# example
# node bin/dns-parse.js </path/to/packet.dns.bin>
node bin/dns-parse.js samples/a-0.mdns.bin
node node_modules/dns-suite/bin/dns-parse.js node_modules/dns-suite/samples/a-0.mdns.bin
```
You can also parse a saved packet from the `native-dns-packet` directory.
these test packets have the binary for each record type and what it's parsed output
should be.
**Library**
### Library API
* `DNSPacket.parse(nodeOrArrayBuffer)` returns json (as shown above)
* `DNSPacket.pack(packet)` returns ArrayBuffer (browser and node)
@ -180,7 +177,7 @@ 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`
1) Update `dns.types.js` if it's not there already.
```
A: 0x01 // 1

62
bin/dns-pack.js Executable file
View File

@ -0,0 +1,62 @@
#!/usr/bin/env node
'use strict';
// EXAMPLE:
// node bin/dns-parse.js samples/a-0.mdns.bin
var path = require('path');
// pass a terminal arg
var filename = process.argv[2];
var outname = process.argv[3];
if (!filename) {
console.error("Usage: node bin/dns-pack.js <path/to/sample.json> <path/to/output.bin>");
console.error("Example: node bin/dns-pack.js ./samples/services-0.mdns.json ./services-0.mdns.bin");
process.exit(1);
return;
}
if (!outname) {
console.warn('');
console.warn(
"Usage: node bin/dns-pack.js '" + filename + "' './" + path.basename(filename).replace(path.extname(filename), '') + ".bin'"
);
console.warn('');
}
var PromiseA = require('bluebird');
var fs = PromiseA.promisifyAll(require('fs'));
var dnsjs = require('../').DNSPacket;
fs.readFileAsync(filename, null).then(function (nb) {
//
// current reference impl
//
//console.log(require('native-dns-packet').parse(nb));
//
// other reference impl
//
//console.log(require('dns-js').DNSPacket.parse(nb));
// nb is a Uint8Array (ArrayBufferView) for nb.buffer
// nb.buffer is the actual ArrayBuffer
//var ab = nb.buffer.slice(nb.byteOffset, nb.byteOffset + nb.byteLength);
var packet = dnsjs.write(JSON.parse(nb.toString('ascii')));
//console.log('[packet]', nb.byteLength, 'bytes:');
//console.log(JSON.stringify(packet, null, 2));
//TODO hexdump packet
var hexdump = require('hexdump.js').hexdump;
var str = hexdump(packet);
console.log(str);
if (outname) {
fs.writeFileSync(outname, packet, null);
console.log('');
console.log("wrote '" + outname + "'");
}
});

1
bin/dns-parse.js Normal file → Executable file
View File

@ -1,3 +1,4 @@
#!/usr/bin/env node
'use strict';
// EXAMPLE:

126
bin/dns-test.js Executable file
View File

@ -0,0 +1,126 @@
#!/usr/bin/env node
'use strict';
// EXAMPLE:
// node bin/dns-parse.js samples/a-0.mdns.bin
var path = require('path');
// pass a terminal arg
var filename = process.argv[2];
var outname = process.argv[3];
if (!filename || /(-h)|(--help)/.test(process.argv.join(' '))) {
console.error("");
console.error("Accepts a DNS packet (binary or json), converts it, and then converts it back to verify the function of the parser and packer");
console.error("");
console.error("Usage: node bin/dns-test.js <path/to/sample.json|.bin> <path/to/sample.bin|.json>");
console.error("Example: node bin/dns-test.js ./samples/services-0.mdns.json ./samples/services-0.mdns.bin");
process.exit(1);
return;
}
var PromiseA = require('bluebird');
var fs = PromiseA.promisifyAll(require('fs'));
var dnsjs = require('../').DNSPacket;
var extname = path.extname(filename).substr(1).toLowerCase();
if ('json' !== extname && 'bin' !== extname) {
console.error("The file extension must end in .json or .bin (raw DNS packet)");
process.exit(3);
return;
}
var hexdump = require('hexdump.js').hexdump;
function testJsonAsync(nb) {
var packet = dnsjs.write(JSON.parse(nb.toString('ascii')));
var str = hexdump(packet);
console.info(str);
var json = dnsjs.parse(packet);
if (json.error) {
console.error(json);
process.exit(37);
return;
}
console.info("[OK] JSON -> binary -> JSON");
if (!outname) {
console.warn('');
console.warn(
"Usage: node bin/dns-test.js '" + filename + "' './" + path.basename(filename).replace(path.extname(filename), '.bin') + "'"
);
console.warn('');
return;
}
fs.writeFileSync(outname, packet, null);
}
function testBinAsync(nb) {
var bin = nb.buffer.slice(nb.byteOffset, nb.byteOffset + nb.byteLength);
var json = dnsjs.parse(bin);
if (json.error) {
console.error(json);
process.exit(38);
return;
}
var bin2 = dnsjs.write(json);
//var debugname = outname || path.basename(filename);
//fs.writeFileSync(debugname.replace(path.extname(debugname), '.bin'), bin2, null);
var json2 = dnsjs.parse(bin2);
if (json2.error) {
console.error(json2);
process.exit(37);
return;
}
var assert = require('assert');
// EXPLANATION:
// The strategy used for compression pointers has not yet been proven
// as deterministic... and we don't implement them anyway, so this may not be the same
json = JSON.parse(JSON.stringify(json)
.replace(/,"labels":.*?\]/, '')
.replace(/,"cpcount":\d+/, '')
);
json2 = JSON.parse(JSON.stringify(json2)
.replace(/,"labels":.*?\]/, '')
.replace(/,"cpcount":\d+/, '')
);
//json2 = JSON.parse(JSON.stringify(json2));
try {
assert.deepEqual(json, json2);
} catch(e) {
console.error('');
console.error('Original');
console.error(JSON.stringify(json, null, 2));
console.error('');
console.error('Converted');
console.error(JSON.stringify(json2, null, 2));
console.error('');
process.exit(422);
return;
}
console.info("[OK] binary -> JSON -> binary -> JSON");
console.warn("(compression pointer support needs to be improved in order to support direct bin -> JSON -> bin testing)");
if (!outname) {
console.warn('');
console.warn(
"Usage: node bin/dns-test.js '" + filename + "' './" + path.basename(filename).replace(path.extname(filename), '.json') + "'"
);
console.warn('');
return;
}
fs.writeFileSync(outname, JSON.stringify(json, null, 2), null);
}
if ('json' === extname) {
return fs.readFileAsync(filename, null).then(testJsonAsync);
}
if ('bin' === extname) {
return fs.readFileAsync(filename, null).then(testBinAsync);
}

8
bin/mdns-capture.js Normal file → Executable file
View File

@ -1,11 +1,13 @@
#!/usr/bin/env node
'use strict';
// pass a terminal arg
var type = process.argv[2];
var count = parseInt(process.argv[3]) || 0;
if (!type) {
console.error("Usage: node aj-listener.js <type> [count]");
console.error("Example: node aj-listener.js _service 0");
console.error("Usage: mdns-capture.js <filename-prefix> [start-number]");
console.error("Example: mdns-capture.js _service 0");
console.error("Output: _service-0.mdns.bin");
process.exit(1);
}
@ -41,6 +43,8 @@ handlers.onListening = function () {
server.setBroadcast(true);
server.addMembership('224.0.0.251');
console.log('CTRL+C to quit');
};

10
dns.js
View File

@ -5,6 +5,8 @@ var Parser = (exports.DNS_PARSER || require('./dns.parser.js').DNS_PARSER);
var Packer = (exports.DNS_PACKER || require('./dns.packer.js').DNS_PACKER);
//var classes = exports.DNS_CLASSES || require('./dns.classes.js').DNS_CLASSES;
//var types = exports.DNS_TYPES || require('./dns.types.js').DNS_TYPES;
var logged = {};
exports.DNSPacket = {
parse: function (nb) {
// backwards compat with node buffer
@ -21,8 +23,14 @@ exports.DNSPacket = {
record = Parser.unpackRdata(ab, packet, record);
} catch (e) {
console.error('[Error] unpackRdata: ' + e.message);
record.error = e;
if (!/^support for dns/i.test(e.message)) {
console.error('[Error] unpackRdata: ' + e.message);
}
else if (!logged[e.message]) {
console.error('[Error] unpackRdata: ' + e.message);
logged[e.message] = true;
}
}
}

View File

@ -37,6 +37,11 @@ var dnspack = exports.DNS_PACKER = {
if (!r.className) {
throw new Error("no className");
}
if (!classes[r.className]) {
console.warn("ignoring invalid class '" + r.className + "' for '" + r.name);
} else {
r.class = classes[r.className];
}
}
if (!r.type) {
@ -44,7 +49,9 @@ var dnspack = exports.DNS_PACKER = {
throw new Error("no typeName");
}
if (!types[r.typeName]) {
console.warn("ignoring invalid type '" + r.type + "' for '" + r.name + "', ignoring");
console.warn("ignoring invalid type '" + r.typeName + "' for '" + r.name);
} else {
r.type = types[r.typeName];
}
}
}

View File

@ -31,13 +31,17 @@ pdns.unpackHeader = function (i) {
pdns._unpackLabels = exports.DNS_UNPACK_LABELS || require('./dns.unpack-labels.js').DNS_UNPACK_LABELS;
var optWarned = false;
pdns.unpackOpt = function (ab, packet, rec) {
var dv;
// https://tools.ietf.org/html/rfc6891#section-6
console.log('OPT is not yet supported');
if (!optWarned) {
console.warn('OPT is not yet supported');
optWarned = true;
}
if ('undefined' !== typeof packet.edns_version) {
console.warn("More that one OPT, should respond with FORMERR, but not implmentede");
console.warn("More that one OPT, should respond with FORMERR, but not implemented");
}
if (packet.name) {
console.warn("name '" + packet.name + "' should not exist for type OPT 0x29 41");
@ -60,11 +64,11 @@ pdns.unpackOpt = function (ab, packet, rec) {
packet.edns_version = dv.getUint8(1, false);
packet.do = dv.getUint8(2, false) & 0x8000; // 1000 0000
packet.z = dv.getUint16(2, false) & 0x7FFF; // 0111 1111
/*
"edns_options": [],
"payload": 4096,
"edns_version": 0,
"do": 0
/*
"edns_options": [],
"payload": 4096,
"edns_version": 0,
"do": 0
*/
};
pdns.unpack = function (ab) {

View File

@ -2,22 +2,55 @@
'use strict';
var types = exports.DNS_TYPES = {
A: 0x01 // 1
, NS: 0x02 // 2
, CNAME: 0x05 // 5
, SOA: 0x06 // 6
, PTR: 0x0c // 12
, MX: 0x0f // 15
, TXT: 0x10 // 16
, AAAA: 0x1c // 28
, SRV: 0x21 // 33
, OPT: 0x29 // 41
, ANY: 0xff // 255
A: 0x1 // 1
, NS: 0x2 // 2
, CNAME: 0x5 // 5
, SOA: 0x6 // 6
, NULL: 0xa // 10
, PTR: 0xc // 12
, HINFO: 0xd // 13
, MX: 0xf // 15
, TXT: 0x10 // 16
, RP: 0x11 // 17
, AFSDB: 0x12 // 18
, SIG: 0x18 // 24
, KEY: 0x19 // 25
, AAAA: 0x1c // 28
, LOC: 0x1d // 29
, SRV: 0x21 // 33
, NAPTR: 0x23 // 35
, KX: 0x24 // 36
, CERT: 0x25 // 37
, DNAME: 0x27 // 39
, OPT: 0x29 // 41
, APL: 0x2a // 42
, DS: 0x2b // 43
, SSHFP: 0x2c // 44
, IPSECKEY: 0x2d // 45
, RRSIG: 0x2e // 46
, NSEC: 0x2f // 47
, DNSKEY: 0x30 // 48
, DHCID: 0x31 // 49
, NSEC3: 0x32 // 50
, NSEC3PARAM: 0x33 // 51
, TLSA: 0x34 // 52
, HIP: 0x37 // 55
, CDS: 0x3b // 59
, CDNSKEY: 0x3c // 60
, SPF: 0x63 // 99
, TKEY: 0xf9 // 249
, TSIG: 0xfa // 250
, IXFR: 0xfb // 251
, AXFR: 0xfc // 252
, ANY: 0xff // 255
, CAA: 0x101 // 257
, TA: 0x8000 // 32768
, DLV: 0x8001 // 32769
};
// and in reverse
Object.keys(types).forEach(function (key) {
for (var key in types) {
types[types[key]] = key;
});
}
}('undefined' !== typeof window ? window : exports));

View File

@ -29,49 +29,55 @@ exports.DNS_UNPACK_LABELS = function (ui8, ptr, q) {
break;
}
if (0xc0 === len) {
var cpptr = ui8[total + 1];
// Handle message compression pointers. See 4.1.4 of RFC1035 for details.
if (len >= 0xc0) {
var cpptr = ((ui8[total] & 0x3f) << 8) | ui8[total + 1];
// we're not coming back!
ptr = cpptr;
// We're not coming back, so if this is the first time we're following one of
// these pointers we up the byteLength to mark the pointer as part of the label
if (!q.cpcount) {
q.byteLength += 2; // cp and len
}
q.cpcount += 1;
// recursion
return exports.DNS_UNPACK_LABELS(ui8, ptr, q);
return exports.DNS_UNPACK_LABELS(ui8, cpptr, q);
}
//str.length = 0; // fast empty array
if (ui8.byteLength - total < len) {
//console.error('-1', ui8[total - 1]);
//console.error(' 0', ui8[total]);
//console.error(' 1', ui8[total + 1]);
//console.error(' 1', ui8[total + 2]);
throw new Error(
"Expected a string of length " + len
+ " but packet itself has " + (ui8.byteLength - total) + " bytes remaining"
);
}
// Advance the pointer to after the length indicator, then read the string in.
total += 1;
for (i = 0; i < len; i += 1) {
total += 1;
// TODO check url-allowable characters
label.push(String.fromCharCode(ui8[total]));
total += 1;
}
if (label.length) {
q.labels.push(label.join(''));
}
total += 1;
// It is important this be done every time, so that if we run into a message compression
// pointer later we still have a record of what was consumed before that.
if (0 === q.cpcount) {
q.byteLength = total - ptr;
}
} while (0 !== len && undefined !== len);
//str.pop(); // remove trailing '.'
q.name = q.labels.join('.');
if (0 === q.cpcount) {
q.byteLength = total - ptr;
}
return q;
};

View File

@ -1,23 +1,57 @@
'use strict';
module.exports.respond = function (socket, packets, rinfo) {
var dns = require('dns-js');
var dns = require('../');
var os = require('os');
var queryname = '_cloud._tcp.local';
console.log(packets);
packets.forEach(function (packet) {
// Only respond to queries, otherwise we'll end up responding to ourselves forever.
if (packet.header.qr !== 0) {
return;
}
packet.question.forEach(function (q) {
if (queryname !== q.name) {
return;
}
console.log('question', q.name, q.typeName, q.className, q.flag, q);
var rpacket = new dns.DNSPacket();
var rpacket = {
header: {
id: packet.header.id
, qr: 1
, opcode: 0
, aa: 1
, tc: 0
, rd: 0
, ra: 0
, res1: 0
, res2: 0
, res3: 0
, rcode: 0
, }
, question: [q]
, answer: []
, authority: []
, additional: []
, edns_options: []
};
var myRndId = 'be1af7a';
rpacket.answer.push({
name: q.name
, typeName: 'PTR'
, ttl: 10
, className: 'IN'
, data: myRndId + '.' + queryname
});
var ifaces = os.networkInterfaces();
//var llRe = /^(fe80|169)/i; // link-local
Object.keys(ifaces).forEach(function (iname) {
var iface = ifaces[iname];
@ -28,55 +62,37 @@ module.exports.respond = function (socket, packets, rinfo) {
iface.forEach(function (pface) {
rpacket.additional.push({
name: q.name
, type: ('IPv4' === pface.family ? dns.DNSRecord.Type.A : dns.DNSRecord.Type.AAAA)
name: myRndId + '.' + q.name
, typeName: ('IPv4' === pface.family ? 'A' : 'AAAA')
, ttl: 10
, class: dns.DNSRecord.Class.IN
, className: 'IN'
, address: pface.address // '_workstation._tcp.local'
});
});
});
var myRndId = 'be1af7a';
rpacket.answer.push({
name: q.name
, type: dns.DNSRecord.Type.PTR
, ttl: 10
, class: dns.DNSRecord.Class.IN
, data: myRndId + '.' + queryname
});
rpacket.question.push(new dns.DNSRecord(
queryname // Name
, dns.DNSRecord.Type.PTR // Type
, dns.DNSRecord.Class.IN // Class
//, null // TTL
));
rpacket.additional.push({
name: myRndId + '.' + queryname
, type: dns.DNSRecord.Type.SRV
, typeName: 'SRV'
, ttl: 10
, class: dns.DNSRecord.Class.IN
, priority: 0
, className: 'IN'
, priority: 1
, weight: 0
, port: 443
, target: myRndId + ".local"
});
rpacket.additional.push({
name: myRndId + '.' + '_device-info._tcp.local'
, type: dns.DNSRecord.Type.TXT
, typeName: 'TXT'
, ttl: 10
, class: dns.DNSRecord.Class.IN
, className: 'IN'
, data: ["model=CloudHome1,1", "dappsvers=1"]
});
rpacket.header.id = packet.header.id;
rpacket.header.aa = 1;
rpacket.header.qr = 1;
rpacket.header.rd = 0;
console.log('');
console.log('START JSON PACKET');
console.log(rpacket);
var buf = dns.DNSPacket.toBuffer(rpacket);
var buf = dns.DNSPacket.write(rpacket);
console.log(buf.toString('hex'));
console.log('END JSON PACKET');
console.log('');
@ -89,8 +105,7 @@ module.exports.respond = function (socket, packets, rinfo) {
console.log('');
socket.send(buf, rinfo.port, rinfo.address);
});
/*
*/
packet.answer.forEach(function (a) {
console.log('answer', a.name, a.typeName, a.className, a.flag, a);
});

View File

@ -1,7 +1,6 @@
'use strict';
var dgram = require('dgram');
var dnsjs = require('dns-js');
// SO_REUSEADDR and SO_REUSEPORT are set because
// the system mDNS Responder may already be listening on this port

View File

@ -5,7 +5,7 @@ var socket = dgram.createSocket({
type: 'udp4'
, reuseAddr: true
});
var dns = require('dns-js');
var dns = require('../');
//var DNSPacket = dns.DNSPacket;
var broadcast = '224.0.0.251'; // mdns
@ -31,14 +31,11 @@ return binString;
}
socket.on('message', function (message, rinfo) {
console.log('Received %d bytes from %s:%d\n',
message.length, rinfo.address, rinfo.port);
console.log('Received %d bytes from %s:%d', message.length, rinfo.address, rinfo.port);
//console.log(msg.toString('utf8'));
message.forEach(function(byte){
console.log(pad(byte.toString(2), 8,'0'));
console.log(pad(byte.toString(2), 8, '0'));
});

114
examples/test-output Normal file
View File

@ -0,0 +1,114 @@
I created a subdomain for `daplie.pe` named `delegated.daplie.pe`.
Below are the results of the `whois`, `dig`, and `dig.js` commands.
============================
Users-MBP:~ jim$ whois daplie.pe
Domain Name: daplie.pe
WHOIS Server: NIC .PE
Sponsoring Registrar: 1API GmbH
Domain Status: clientTransferProhibited
Domain Status: clientUpdateProhibited
Domain Status: clientDeleteProhibited
Domain Status: clientRenewProhibited
Registrant Name: Daplie Labs
Admin Name: Daplie Labs
Admin Email: domains@daplie.com
Name Server: ns27.domaincontrol.com
Name Server: ns28.domaincontrol.com
DNSSEC: unsigned
>>> Last update of WHOIS database: 2017-08-15T19:49:36.243Z <<<
================================
Users-MBP:~ jim$ dig delegated.daplie.pe @ns27.domaincontrol.com
; <<>> DiG 9.8.3-P1 <<>> delegated.daplie.pe @ns27.domaincontrol.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37275
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 0
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
;delegated.daplie.pe. IN A
;; ANSWER SECTION:
delegated.daplie.pe. 600 IN A 50.63.202.28
;; AUTHORITY SECTION:
daplie.pe. 3600 IN NS ns28.domaincontrol.com.
daplie.pe. 3600 IN NS ns27.domaincontrol.com.
;; Query time: 29 msec
;; SERVER: 216.69.185.14#53(216.69.185.14)
;; WHEN: Tue Aug 15 13:54:23 2017
;; MSG SIZE rcvd: 108
===================================
Finding JSON information through dig -- used website `dig.jsondns.org` with the query format (URL)
`dig.jsondns.org/IN/delegated.daplie.pe/A` :
{
"header": {
"id": 59404,
"qr": true,
"opcode": "Query",
"aa": false,
"ad": false,
"tc": false,
"rd": true,
"ra": true,
"cd": true,
"rcode": "NOERROR",
"qdcount": 1,
"nscount": 0,
"ancount": 1,
"arcount": 0
},
"question": [
{
"qname": "delegated.daplie.pe",
"qtype": "A",
"qclass": "IN"
}
],
"answer": [
{
"name": "delegated.daplie.pe",
"type": "A",
"class": "IN",
"ttl": 600,
"rdata": "184.168.221.5"
}
],
"authority": [
],
"additional": [
]
}
==================================================
Output from `dig.js delegated.daplie.pe`:
Users-MBP:dig.js jim$ dig.js delegated.daplie.pe
; <<>> dig.js v0.0.0 <<>> delegated.daplie.pe
;; Got answer:
;; ->>HEADER<<-
{"id":24943,"qr":1,"opcode":0,"aa":0,"tc":0,"rd":1,"ra":1,"res1":0,"res2":0,"res3":0,"rcode":0}
;; QUESTION SECTION:
;delegated.daplie.pe. IN ANY
;; ANSWER SECTION:
;delegated.daplie.pe. 599 IN A 50.63.202.18
;; MSG SIZE rcvd: 53

View File

@ -1,8 +1,14 @@
{
"name": "dns-suite",
"version": "1.0.3",
"version": "1.2.2",
"description": "testing dns",
"main": "dns.js",
"bin": {
"dns-pack.js": "bin/dns-pack.js",
"dns-parse.js": "bin/dns-parse.js",
"dns-test.js": "bin/dns-test.js",
"mdns-capture.js": "bin/mdns-capture.js"
},
"scripts": {
"test": "node test/parser.js && node test/packer.js"
},

View File

@ -15,14 +15,14 @@ exports.DNS_PACKER_TYPE_CNAME = function (ab, dv, total, record) {
// RDATA
// a sequence of labels
record.data.split('.').forEach(function (label) {
cnameLen += 1 + label.length;
cnameLen += 1 + label.length;
dv.setUint8(total, label.length, false);
dv.setUint8(total, label.length, false);
total += 1;
label.split('').forEach(function (ch) {
dv.setUint8(total, ch.charCodeAt(0), false);
total += 1;
label.split('').forEach(function (ch) {
dv.setUint8(total, ch.charCodeAt(0), false);
total += 1;
});
});

38
packer/type.ns.js Normal file
View File

@ -0,0 +1,38 @@
(function (exports) {
'use strict';
// NS name for the supplied domain. May be label, pointer or any combination
exports.DNS_PACKER_TYPE_NS = function (ab, dv, total, record) {
if(!record.data){
throw new Error("no data on NS record");
}
var nsLen = 0;
var rdLenIndex = total;
total +=2;
// RDATA
// a sequence of labels
record.data.split('.').forEach(function(label) {
nsLen += 1 + label.length;
dv.setUint8(total, label.length, false);
total += 1;
label.split('').forEach(function (ch){
dv.setUint8(total, ch.charCodeAt(0), false);
total += 1;
});
});
// RDLENGTH
dv.setUint16(rdLenIndex, record.data.length + 1, false);
return total;
};
}('undefined' !== typeof window ? window : exports));

37
packer/type.ptr.js Normal file
View File

@ -0,0 +1,37 @@
(function (exports) {
'use strict';
// The host name that represents the supplied UP address
// May be a label, pointer or any combination
exports.DNS_PACKER_TYPE_PTR = function (ab, dv, total, record) {
if (!record.data) {
throw new Error("no data for PTR record");
}
var ptrLen = 0;
var rdLenIndex = total;
total += 2;
// RDATA
// a sequence of labels
record.data.split('.').forEach(function (label){
console.log("the labels are: " + label);
ptrLen += 1 + label.length;
dv.setUint8(total, label.length, false);
total += 1;
label.split('').forEach(function (ch){
dv.setUint8(total, ch.charCodeAt(0), false);
total += 1;
});
});
// RDLENGTH
dv.setUint16(rdLenIndex, record.data.length + 1, false);
return total;
};
}('undefined' !== typeof window ? window : exports));

99
packer/type.soa.js Normal file
View File

@ -0,0 +1,99 @@
(function (exports) {
'use strict';
// http://www.zytrax.com/books/dns/ch8/soa.html
// Value Meaning/Use
// Primary NS Variable length. The name of the Primary Master for the domain. May be a label, pointer, or any combination
// Admin MB Variable length. The administrator's mailbox. May be a label, pointer, or any combination
// Serial Number Unsigned 32-bit integer
// Refresh Interval Unsigned 32-bit integer
// Retry Interval Unsigned 32-bit integer
// Expiration Limit Unsigned 32-bit integer
// Minimum TTL Unsigned 32-bit integer
exports.DNS_PACKER_TYPE_SOA = function (ab, dv, total, record) {
if(!record.name_server){
throw new Error("no name server for SOA record");
}
if(!record.email_addr){
throw new Error("ne email address for SOA record");
}
if(!record.sn){
throw new Error("no serial number for SOA record");
}
if(!record.ref){
throw new Error("no refresh for SOA record");
}
if(!record.ret){
throw new Error("no update retry for SOA record");
}
if(!record.ex){
throw new Error("no expiry for SOA record");
}
if(!record.nx){
throw new Error("no nxdomain for SOA record");
}
var soaLen = 20; // take into account sn, ref, ret, ex, and nx
// (32-bits each. 4Bytes * 5 = 20)
var rdLenIndex = total;
total += 2; // Save space for RDLENGTH
console.log('record.name_server', 1 + record.name_server.length, record.name_server);
// pack name_server which is a sequence of labels
record.name_server.split('.').forEach(function (label) {
soaLen += (1 + label.length);
dv.setUint8(total, label.length, false);
total += 1;
label.split('').forEach(function(ch) {
dv.setUint8(total, ch.charCodeAt(0), false);
total += 1;
});
});
// must be terminated by null when not using write null
dv.setUint8(total, 0, false);
total += 1;
soaLen += 1;
console.log('record.email_addr', 1 + record.email_addr.length, record.email_addr);
// pack email address which is a sequence of labels
record.email_addr.split('.').forEach(function (label) {
soaLen += 1 + label.length;
dv.setUint8(total, label.length, false);
total += 1;
label.split('').forEach(function (ch){
dv.setUint8(total, ch.charCodeAt(0), false);
total += 1;
});
});
// must be terminated by null when not using write null
dv.setUint8(total, 0, false);
total += 1;
soaLen += 1;
// pack all 32-bit values
dv.setUint32(total, parseInt(record.sn, 10), false);
total+=4;
dv.setUint32(total, parseInt(record.ref, 10), false);
total+=4;
dv.setUint32(total, parseInt(record.ret, 10), false);
total+=4;
dv.setUint32(total, parseInt(record.ex, 10), false);
total+=4;
dv.setUint32(total, parseInt(record.nx, 10), false);
total+=4;
// RDLENGTH
console.log('rdAt', rdLenIndex);
console.log('soaLen (lables + 2 + 20)', soaLen);
dv.setUint16(rdLenIndex, soaLen, false);
return total;
};
}('undefined' !== typeof window ? window : exports));

69
packer/type.srv.js Normal file
View File

@ -0,0 +1,69 @@
(function (exports) {
'use strict';
// SRV RDATA contains:
// Priority: The relative priority of this service. 16-bit (range 0-65535)
// Weight: Used when more than one serivice has the same priority. 16-bit
// (range 0-65535)
// Port: Port number assigned to the symbolic service. 16-bit (range 0-65535)
// Target: The name of the host that will provide service.
exports.DNS_PACKER_TYPE_SRV = function (ab, dv, total, record) {
// maybe these should be changed to 'hasOwnProperty' for all of these
// TODO: Check that number is in range 1-64k
if (!record.priority){
throw new Error("no priority for SRV record");
}
if (!record.hasOwnProperty('weight')){
throw new Error("no weight for SRV record");
}
if (!record.port){
throw new Error("no port for SRV record");
}
if (!record.target) {
throw new Error("no target for SRV record");
}
// console.log("record length, priority, weight, port, then target");
// console.log("record priority is: " + record.priority);
// console.log("record weight is: " + record.weight);
// console.log("record port is: " + record.port);
// console.log("record target is: " + record.target);
// console.log("total length currently is: " + total);
var srvLen = 6; // 16-bit priority, weight and port = 6 Bytes
var rdLenIndex = total;
total+=2; // space for RDLENGTH
dv.setUint16(total, parseInt(record.priority, 10), false);
total+=2;
dv.setUint16(total,parseInt(record.weight, 10), false);
total+=2;
dv.setUint16(total, parseInt(record.port, 10), false);
total+=2;
record.target.split('.').forEach(function (label){
srvLen += 1 + label.length;
dv.setUint8(total, label.length, false);
total += 1;
label.split('').forEach(function (ch) {
dv.setUint8(total, ch.charCodeAt(0), false);
total += 1;
});
});
// RDLENGTH
dv.setUint16(rdLenIndex, srvLen, false);
return total;
};
}('undefined' !== typeof window ? window : exports));

35
packer/type.txt.js Normal file
View File

@ -0,0 +1,35 @@
(function (exports) {
'use strict';
// Record type is just any text.
exports.DNS_PACKER_TYPE_TXT = function (ab, dv, total, record) {
if (!record.data){
throw new Error("no data for TXT record");
}
var txtLen = 0;
var rdLenIndex = total;
total += 3;
// RDATA
record.data.forEach(function(str){
str.split('').forEach(function(ch){
txtLen += 1;
// console.log(chcim);
dv.setUint8(total, ch.charCodeAt(0), false);
total += 1;
});
});
dv.setUint16(rdLenIndex, txtLen+1, false);
dv.setUint8(rdLenIndex+2, txtLen, false);
// total +=1;
return total;
};
}('undefined' !== typeof window ? window : exports));

View File

@ -27,6 +27,20 @@ exports.DNS_PARSER_TYPE_AAAA = function (ab, packet, record) {
s += dv.getUint16(i, false).toString(16);
}
// Represent the string address as recommended on the wikipedia page
// https://en.wikipedia.org/wiki/IPv6_address#Recommended_representation_as_text.
// (shorten the longest section of 0's as long as it's more than one section, replacing
// the left-most instance in the event of ties.)
var re = /:(0:)+/g;
var match;
var longest = '_BAD';
while (!!(match = re.exec(s))) {
if (match[0].length > longest.length) {
longest = match[0];
}
}
s = s.replace(longest, '::');
record.address = s;
return record;
};

44
parser/type.caa.js Normal file
View File

@ -0,0 +1,44 @@
(function (exports) {
'use strict';
// A Certification Authority Authorization (CAA) record is used to specify which
// certificate authorities (CAs) are allowed to issue certificates for a domain.
// Value Meaning/Use
//
// Flag An unsigned integer between 0-255.
// It is currently used to represent the critical flag, that has a
// specific meaning per RFC 6844
// Tag An ASCII string that represents the identifier of the property
// represented by the record.
// Value The value associated with the tag.
// The RFC currently defines 3 available tags:
//
// - issue: explicity authorizes a single certificate authority to issue a
// certificate (any type) for the hostname.
// - issuewild: explicity authorizes a single certificate authority to issue a
// wildcard certificate (and only wildcard) for the hostname.
// - iodef: specifies an URL to which a certificate authority may report
// policy violations.
exports.DNS_PARSER_TYPE_CAA = function (ab, packet, record) {
var data = new Uint8Array(ab);
var i = record.rdstart;
var flag = data[i++];
var mid = data[i++];
mid += i;
var end = record.rdstart + record.rdlength;
var tag = '', value = '';
while (i < mid) { tag += String.fromCharCode(data[i++]); }
while (i < end) { value += String.fromCharCode(data[i++]); }
record.flag = flag;
record.tag = tag;
record.value = value;
return record;
};
}('undefined' !== typeof window ? window : exports));

View File

@ -1,21 +1,71 @@
(function (exports) {
'use strict';
// TODO. Not yet implemented
// Value Meaning/Use
// Primary NS Variable length. The name of the Primary Master for the domain. May be a label, pointer, or any combination
// Admin MB Variable length. The administrator's mailbox. May be a label, pointer, or any combination
// Serial Number Unsigned 32-bit integer
// Refresh Interval Unsigned 32-bit integer
// Retry Interval Unsigned 32-bit integer
// Retry Interval Unsigned 32-bit integer
// Expiration Limit Unsigned 32-bit integer
// Minimum TTL Unsigned 32-bit integer
var unpackLabels = exports.DNS_UNPACK_LABELS || require('../dns.unpack-labels.js').DNS_UNPACK_LABELS;
exports.DNS_PARSER_TYPE_SOA = function (ab, packet, record) {
var rdataAb = ab.slice(record.rdstart, record.rdstart + record.rdlength);
var dv = new DataView(rdataAb);
// we need this information for this parser
var labelInfo = unpackLabels(new Uint8Array(ab), record.rdstart, { byteLength: 0, cpcount: 0, labels: [], name: '' });
var cpcount = labelInfo.cpcount;
var offset = labelInfo.byteLength;
var labels = labelInfo.labels;
// Primary NS
record.name_server = labelInfo.name;
// TODO delete this commented out code after some testing
// (pretty sure it was unnecessary and it seemed to work on code with compression pointers just fine)
/*
// if there exists compression pointers in the rdata
if (cpcount > 0) {
// do something awesome with compression pointers to get the email address
// I need the length of all the data before the email address starts.
// if there are compression pointers then there will be a byte to indicate the length of each label, the label,
// then there will be a compression pointer to grab the longest label.
var start = 2; // start or email_addr. take into account compression pointer and address length
for (var i = 0; i < labels.length; i += 1) {
// increase start by the label length. the +1 is to take into account the next label size byte
start = start + labels[i].length + 1;
// check for cpcount. 2 counts behind
if (parseInt(dv.getUint8(start - 2), 10) === 192) {
record.email_addr = unpackLabels(new Uint8Array(ab), record.rdstart + start ,{ byteLength: 0, cpcount: 0, labels: [], name: '' }).name;
}
}
} // if there are no compression pointers, we can get the email address directly from the offset
else {
record.email_addr = unpackLabels(new Uint8Array(ab), record.rdstart + offset, { byteLength: 0, cpcount: 0, labels: [], name: '' }).name;
}
*/
record.email_addr = unpackLabels(new Uint8Array(ab), record.rdstart + offset, { byteLength: 0, cpcount: 0, labels: [], name: '' }).name;
// Serial Number
record.sn = dv.getUint32(dv.byteLength - 20, false);
// Refresh Interval
record.ref = dv.getUint32(dv.byteLength - 16, false);
// Retry Interval
record.ret = dv.getUint32(dv.byteLength - 12, false);
// Expiration Limit
record.ex = dv.getUint32(dv.byteLength - 8, false);
// Minimum TTL
record.nx = dv.getUint32(dv.byteLength - 4, false);
return record;
};
}('undefined' !== typeof window ? window : exports));

View File

@ -1,6 +1,5 @@
(function (exports) {
'use strict';
// TODO. Not yet implemented
// SRV identifies the host(s) that will support a particular service. It
// is a general purpose RR to discover any service.

BIN
test/fixtures/canone30522.local.bin vendored Normal file

Binary file not shown.

59
test/fixtures/canone30522.local.js vendored Normal file
View File

@ -0,0 +1,59 @@
module.exports = {
"header": {
"id": 0,
"qr": 1,
"opcode": 0,
"aa": 0,
"tc": 0,
"rd": 0,
"ra": 0,
"res1": 0,
"res2": 0,
"res3": 0,
"rcode": 0
},
"question": [],
"answer": [
{
"name": "_pdl-datastream._tcp.local",
"type": 12,
"class": 1,
"ttl": 255,
"data": "Canon MF620C Series._pdl-datastream._tcp.local"
}
],
"additional": [
{
"name": "Canone30522.local",
"type": 1,
"class": 32769,
"ttl": 255,
},
{
"name": "Canon MF620C Series._pdl-datastream._tcp.local",
"type": 33,
"class": 32769,
"ttl": 255,
},
{
"name": "Canon MF620C Series._pdl-datastream._tcp.local",
"type": 16,
"class": 32769,
"ttl": 255,
},
{
"name": "Canone30522.local",
"type": 47,
"class": 32769,
"ttl": 255,
},
{
"name": "Canon MF620C Series._pdl-datastream._tcp.local",
"type": 47,
"class": 32769,
"ttl": 255,
}
],
"authority": [],
"edns_options": []
};

Binary file not shown.

View File

@ -0,0 +1,63 @@
{
"header": {
"id": 52080,
"qr": 1,
"opcode": 0,
"aa": 0,
"tc": 0,
"rd": 1,
"ra": 1,
"res1": 0,
"res2": 0,
"res3": 0,
"rcode": 0
},
"qdcount": 1,
"ancount": 1,
"nscount": 0,
"arcount": 0,
"question": [
{
"name": "google.com",
"type": 6,
"typeName": "SOA",
"class": 1,
"className": "IN",
"byteLength": 16,
"labels": [
"google",
"com"
],
"cpcount": 0
}
],
"answer": [
{
"name": "google.com",
"type": 6,
"typeName": "SOA",
"class": 1,
"className": "IN",
"byteLength": 50,
"labels": [
"google",
"com"
],
"cpcount": 1,
"rdstart": 40,
"rdlength": 38,
"ttl": 59,
"name_server": "ns3.google.com",
"email_addr": "dns-admin.google.com",
"sn": 150355194,
"ref": 900,
"ret": 900,
"ex": 1800,
"nx": 60
}
],
"authority": [],
"edns_options": [],
"additional": [],
"byteLength": 78
}

Binary file not shown.

View File

@ -0,0 +1,19 @@
{
"header": {
"id": 52080,
"qr": 0,
"opcode": 0,
"aa": 0,
"tc": 0,
"rd": 1,
"ra": 0,
"rcode": 0
},
"question": [
{
"name": "google.com",
"typeName": "SOA",
"className": "IN"
}
]
}

Binary file not shown.

View File

@ -0,0 +1,8 @@
0000000 c1 0b 81 83 00 01 00 00 00 01 00 00 14 73 6e 74
0000010 68 61 6f 75 65 73 6e 74 68 61 6f 75 65 6e 73 74
0000020 68 05 79 61 68 6f 6f 03 63 6f 6d 00 00 ff 00 01
0000030 c0 21 00 06 00 01 00 00 02 57 00 31 03 6e 73 31
0000040 c0 21 0a 68 6f 73 74 6d 61 73 74 65 72 09 79 61
0000050 68 6f 6f 2d 69 6e 63 c0 27 78 3a 63 bb 00 00 0e
0000060 10 00 00 01 2c 00 1b af 80 00 00 02 58
000006d

View File

@ -0,0 +1,64 @@
{
"header": {
"id": 49419,
"qr": 1,
"opcode": 0,
"aa": 0,
"tc": 0,
"rd": 1,
"ra": 1,
"res1": 0,
"res2": 0,
"res3": 0,
"rcode": 3
},
"qdcount": 1,
"ancount": 0,
"nscount": 1,
"arcount": 0,
"question": [
{
"name": "snthaouesnthaouensth.yahoo.com",
"type": 255,
"typeName": "ANY",
"class": 1,
"className": "IN",
"byteLength": 36,
"labels": [
"snthaouesnthaouensth",
"yahoo",
"com"
],
"cpcount": 0
}
],
"answer": [],
"authority": [
{
"name": "yahoo.com",
"type": 6,
"typeName": "SOA",
"class": 1,
"className": "IN",
"byteLength": 61,
"labels": [
"yahoo",
"com"
],
"cpcount": 1,
"rdstart": 60,
"rdlength": 49,
"ttl": 599,
"name_server": "ns1.yahoo.com",
"email_addr": "hostmaster.yahoo-inc.com",
"sn": 2017092539,
"ref": 3600,
"ret": 300,
"ex": 1814400,
"nx": 600
}
],
"edns_options": [],
"additional": [],
"byteLength": 109
}

Binary file not shown.

View File

@ -0,0 +1,4 @@
0000000 c1 0b 01 00 00 01 00 00 00 00 00 00 14 73 6e 74
0000010 68 61 6f 75 65 73 6e 74 68 61 6f 75 65 6e 73 74
0000020 68 05 79 61 68 6f 6f 03 63 6f 6d 00 00 ff 00 01
0000030

View File

@ -0,0 +1,21 @@
{
"header": {
"id": 49419,
"qr": 0,
"opcode": 0,
"aa": 0,
"tc": 0,
"rd": 1,
"ra": 0,
"rcode": 0
},
"question": [
{
"name": "snthaouesnthaouensth.yahoo.com",
"typeName": "ANY",
"className": "IN",
"class": 1,
"type": 255
}
]
}

View File

@ -0,0 +1,17 @@
{ "header":
{ "id":19900,"qr":1,"opcode":0,"aa":0,"tc":0,"rd":1,"ra":1,"res1":0,"res2":0,"res3":0,"rcode":3},
"qdcount":1,
"ancount":0,
"nscount":1,
"arcount":0,
"question":[
{"name":"snthaouesnthaouensth.yahoo.com","type":255,"typeName":"ANY","class":1,"className":"IN","byteLength":36,"labels":["snthaouesnthaouensth","yahoo","com"],"cpcount":0}
],
"answer":[],
"authority":[
{"name":"yahoo.com","type":6,"typeName":"SOA","class":1,"className":"IN","byteLength":61,"labels":["yahoo","com"],"cpcount":1,"rdstart":60,"rdlength":49,"ttl":599,"name_server":"ns1.yahoo.com","email_addr":"hostmaster.yahoo-inc.com","sn":2017092539,"ref":3600,"ret":300,"ex":1814400,"nx":600}
],
"edns_options":[],
"additional":[],
"byteLength":109
}

Binary file not shown.

View File

@ -0,0 +1,201 @@
{
"header": {
"id": 430,
"qr": 1,
"opcode": 0,
"aa": 0,
"tc": 0,
"rd": 1,
"ra": 1,
"res1": 0,
"res2": 0,
"res3": 0,
"rcode": 0
},
"qdcount": 1,
"ancount": 1,
"nscount": 4,
"arcount": 4,
"question": [
{
"name": "google.com",
"type": 16,
"typeName": "TXT",
"class": 1,
"className": "IN",
"byteLength": 16,
"labels": [
"google",
"com"
],
"cpcount": 0
}
],
"answer": [
{
"name": "google.com",
"type": 16,
"typeName": "TXT",
"class": 1,
"className": "IN",
"byteLength": 48,
"labels": [
"google",
"com"
],
"cpcount": 1,
"rdstart": 40,
"rdlength": 36,
"ttl": 2930,
"data": [
"v=spf1 include:_spf.google.com ~all"
]
}
],
"authority": [
{
"name": "google.com",
"type": 2,
"typeName": "NS",
"class": 1,
"className": "IN",
"byteLength": 18,
"labels": [
"google",
"com"
],
"cpcount": 1,
"rdstart": 88,
"rdlength": 6,
"ttl": 82790,
"data": "ns3.google.com"
},
{
"name": "google.com",
"type": 2,
"typeName": "NS",
"class": 1,
"className": "IN",
"byteLength": 18,
"labels": [
"google",
"com"
],
"cpcount": 1,
"rdstart": 106,
"rdlength": 6,
"ttl": 82790,
"data": "ns4.google.com"
},
{
"name": "google.com",
"type": 2,
"typeName": "NS",
"class": 1,
"className": "IN",
"byteLength": 18,
"labels": [
"google",
"com"
],
"cpcount": 1,
"rdstart": 124,
"rdlength": 6,
"ttl": 82790,
"data": "ns1.google.com"
},
{
"name": "google.com",
"type": 2,
"typeName": "NS",
"class": 1,
"className": "IN",
"byteLength": 18,
"labels": [
"google",
"com"
],
"cpcount": 1,
"rdstart": 142,
"rdlength": 6,
"ttl": 82790,
"data": "ns2.google.com"
}
],
"edns_options": [],
"additional": [
{
"name": "ns1.google.com",
"type": 1,
"typeName": "A",
"class": 1,
"className": "IN",
"byteLength": 16,
"labels": [
"ns1",
"google",
"com"
],
"cpcount": 2,
"rdstart": 160,
"rdlength": 4,
"ttl": 255588,
"address": "216.239.32.10"
},
{
"name": "ns2.google.com",
"type": 1,
"typeName": "A",
"class": 1,
"className": "IN",
"byteLength": 16,
"labels": [
"ns2",
"google",
"com"
],
"cpcount": 2,
"rdstart": 176,
"rdlength": 4,
"ttl": 255577,
"address": "216.239.34.10"
},
{
"name": "ns3.google.com",
"type": 1,
"typeName": "A",
"class": 1,
"className": "IN",
"byteLength": 16,
"labels": [
"ns3",
"google",
"com"
],
"cpcount": 2,
"rdstart": 192,
"rdlength": 4,
"ttl": 255599,
"address": "216.239.36.10"
},
{
"name": "ns4.google.com",
"type": 1,
"typeName": "A",
"class": 1,
"className": "IN",
"byteLength": 16,
"labels": [
"ns4",
"google",
"com"
],
"cpcount": 2,
"rdstart": 208,
"rdlength": 4,
"ttl": 255595,
"address": "216.239.38.10"
}
],
"byteLength": 212
}

Binary file not shown.

View File

@ -0,0 +1,19 @@
{
"header": {
"id": 430,
"qr": 0,
"opcode": 0,
"aa": 0,
"tc": 0,
"rd": 1,
"ra": 0,
"rcode": 0
},
"question": [
{
"name": "google.com",
"typeName": "TXT",
"className": "IN"
}
]
}