diff --git a/README.md b/README.md index 3371777..7b3b2c7 100644 --- a/README.md +++ b/README.md @@ -18,21 +18,25 @@ Detailed error checking makes it great for Similar API to `dns.js` and `native-dns-packet`. ```json -{ "id": 54231 -, "qr": 0 -, "opcode": 0 -, "aa": 0 -, "tc": 0 -, "rd": 1 -, "ra": 0 -, "res1": 0 -, "res2": 0 -, "res3": 0 -, "rcode": 0 +{ "header": { + "id": 54231 + , "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 } ] @@ -120,6 +124,99 @@ var packet = pdns.unpack(arrayBuffer); console.log(packet); ``` +Contributing and Development +============================ + +How to add a new parser +----------------------- + +Each RR (aka Resource Record or RData) parser is individual. Examples include: + +* A (`dns.type.a.js`) +* AAAA (`dns.type.aaaa.js`) +* CNAME (`dns.type.cname.js`) +* TXT (`dns.type.txt.js`) +* SRV (`dns.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 `dns.type..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` + +``` + A: 0x01 // 1 +, NS: 0x02 // 2 +, CNAME: 0x05 // 5 // I would simply add this line +, SOA: 0x06 // 6 +``` + +2) Capture a packet to `test/fixtures/...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/...json` + +```bash +node bin/capture-query.js --name www.google.com --type CNAME +``` + +3) Create `dns.type.cname.js` + +Copy `dns.type.TEMPLATE.js` to the type for which you wish to create support +(`dns.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_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)); +``` + +4) Document what you've learned in `doc/.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. + +5) 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.type.cname.js +├── dns.types.js +├── package.json (bump the minor version) +└── test + └── fixtures +    ├── www.google.com.cname.bin +    └── www.google.com.cname.js +``` + + mDNS Documentation ==== diff --git a/dns.type.TEMPLATE.js b/dns.type.TEMPLATE.js new file mode 100644 index 0000000..af7fe00 --- /dev/null +++ b/dns.type.TEMPLATE.js @@ -0,0 +1,116 @@ +(function (exports) { +'use strict'; + +// Put some documentation here in these comments. +// See examples of documentation in dns.type.a.js +// and dns.type.mx.js + +// If the data type you're wanting to unpack contains labels +// (meaning text that will be represented as a period-separated +// domain name, i.e. www.google.com) then you will need to use +// `unpackLabels`, which has compression pointer support +var unpackLabels = exports.DNS_UNPACK_LABELS || require('./dns.unpack-labels.js').DNS_UNPACK_LABELS; + +// The parser receives exactly 3 parameters as follows: +// +// ab - an ArrayBuffer containing the whole of the packet as binary +// you will use Uint8Array (for which endianness doesn't matter) +// and DataView (with wich you MUST always specify 'false' for +// Big Endian, which is "network byte order") +// +// packet - a plain javascript value object (i.e. to/from JSON) that +// has all of the currently parsed fields (generally not used), +// containing meta data in `header`, and arrays of potentially +// parsed (or not yet parsed) records in `question`, `answer`, +// `authority`, `additional` +// +// record - an element of an array in packet (meaning one of `question`, +// `answer`, `authority`, `additional`) which has potentially +// helpful data about the record such as `rdstart` and `rdlength`, +// signifying the type at which the binary segment for this record +// begins and its length +// +exports.DNS_TYPE_MX = function (ab, packet, record) { + // + // Slicing RData + // + + // For various reasons it may be easier to work with a slice of the + // ArrayBuffer container the binary packet that just represents the + // RData you want to work with starting at 0 and ending at the end of + // the Resource Record (aka RData) rather than always adding `rdstart` + // to some offset and checking that you don't read past the record's + // end (`rdstart + rdlength`) + var rdataAb = ab.slice(record.rdstart, record.rdstart + record.rdlength); + + + + // + // Using Uint8Array + // + + // it's very likely that you'll want to process individual bytes, + // for which you would use Uint8Array - for example, if some significant + // portion of the record is to be read as a non-label string + var ui8 = new Uint8Array(rdataAb); + + // Example: reading a string whose length is defined by the first byte + var len = ui8[0]; + var i; + record.value = ''; + for (i = 0; i < len; i += 1) { + record.value += String.fromCharCode(ui8[i]); + } + + // Example: reading a string whose length is terminated with 0 + var i; + record.value = ''; + for (i = 0; i < len; i += 1) { + if (0 === ui8[i]) { + break; + } + record.value += String.fromCharCode(ui8[i]); + } + + + + // + // Using DataView + // + + // it's also very likely that you'll want to interpret some variable + // byte-width data, such as an id or type number, something of that nature + var dv = new DataView(rdataAb); + + // Example: reading a single-octet type, a sexdectet id, and quad-octet date + record.rtype = dv.getUint8(0, false); // start at 0 + record.rid = dv.getUint16(1, false); // next is at 1 + record.date = new Date(dv.getUint32(3, false) * 1000); // advance 2 more bytes to 3 + // the next read, if any, would be at 7 + + + + // + // Unpacking Labels + // + + // if your data type contains labels (i.e. www.google.com would be represented + // as 3 labels and would have the byte sequence 0x03"www"0x06"google"0x03"com), + // they may contain compression pointers (represented as 0xc0, meaning 192 in + // decimal - outside the ascii range) and they may be terminated either by 0x00 + // or by the end of the record, so you should use unpackLabels and provide an + // ArrayBuffer that is sliced to the end of your record (otherwise record-length + // terminated strings would be misinterpretted overflow) + + // Example: assuming some label started at the 7th byte + var truncatedAb = new Uint8Array(ab.slice(0, record.rdstart + record.rdlength)); + var labelData = unpackLabels(truncatedAb, record.rdstart+7, { byteLength: 0, cpcount: 0, labels: [], name: '' }); + record.deviceName = labelData.name; + + + + // finally, return the record + return record; +}; + +}('undefined' !== typeof window ? window : exports));