dns-suite.js/dns.packer.js

162 lines
4.5 KiB
JavaScript
Raw Normal View History

(function (exports) {
'use strict';
2017-02-16 21:16:21 +00:00
var classes = exports.DNS_CLASSES || require('./dns.classes.js').DNS_CLASSES;
var types = exports.DNS_TYPES || require('./dns.types.js').DNS_TYPES;
2017-02-11 22:41:01 +00:00
var dnspack = exports.DNS_PACKER = {
pack: function (packet) {
2017-02-16 21:16:21 +00:00
// TODO update for edns payload size
var ab = new ArrayBuffer(576); // http://serverfault.com/a/587626/93930
var dv = new DataView(ab);
var labelsMap = {};
2017-02-11 22:41:01 +00:00
var total = 0;
var id = packet.header.id; // 2 bytes
var header = dnspack.packHeader(packet.header); // 2 bytes
2017-02-11 23:01:02 +00:00
total = 12; // 2+2+2+2 bytes ({qd,an,ns,ar}count)
2017-02-11 22:41:01 +00:00
2017-02-18 01:32:45 +00:00
if (!id) {
throw new Error("'id' should be set to a crypto random id");
}
2017-02-11 22:41:01 +00:00
dv.setUint16(0, id, false);
dv.setUint16(2, header, false);
2017-02-11 23:01:02 +00:00
dv.setUint16(4, packet.question.length, false);
2017-02-18 01:57:25 +00:00
dv.setUint16(6, (packet.answer||[]).length, false);
dv.setUint16(8, (packet.authority||[]).length, false);
2017-02-11 23:01:02 +00:00
// EDNS is added as an additional with TYPE 41 (OPT, 0x29)
2017-02-18 01:57:25 +00:00
dv.setUint16(10, (packet.additional||[]).length + (packet.payload ? 1 : 0), false);
2017-02-11 22:41:01 +00:00
2017-02-16 21:16:21 +00:00
function lint(r) {
if (!r.name) {
throw new Error("missing name");
}
if (!r.class) {
if (!r.className) {
throw new Error("no className");
}
}
if (!r.type) {
if (!r.typeName) {
throw new Error("no typeName");
}
2017-02-18 01:57:25 +00:00
if (!types[r.typeName]) {
2017-02-16 21:16:21 +00:00
console.warn("ignoring invalid type '" + r.type + "' for '" + r.name + "', ignoring");
}
}
}
2017-02-16 23:29:23 +00:00
function packLabelSequence(sequence, terminates) {
if (labelsMap[sequence]) {
// minimal compression pointer 0xc0 (192)
dv.setUint8(total, 0xc0, false);
total += 1;
dv.setUint8(total, labelsMap[sequence].total, false);
total += 1;
return;
}
2017-02-16 21:16:21 +00:00
if (terminates) {
// we don't want non-terminating rdata cached, just terminating names
labelsMap[total] = { total: total, name: sequence };
labelsMap[sequence] = labelsMap[total];
}
sequence.split('.').forEach(function (label) {
dv.setUint8(total, label.length, false);
total += 1;
label.split('').forEach(function (ch) {
dv.setUint8(total, ch.charCodeAt(0), false);
total += 1;
});
});
2017-02-16 23:29:23 +00:00
if (terminates) {
// trailing 0 (null) as label sequence terminator
dv.setUint8(total, 0, false);
total += 1;
}
2017-02-16 21:16:21 +00:00
}
function packQuestion(q) {
lint(q);
2017-02-16 23:29:23 +00:00
packLabelSequence(q.name, true);
2017-02-16 21:16:21 +00:00
2017-02-18 01:57:25 +00:00
dv.setUint16(total, q.type || types[q.typeName], false);
2017-02-16 23:29:23 +00:00
total += 2;
2017-02-18 01:57:25 +00:00
dv.setUint16(total, q.class || classes[q.className], false);
2017-02-16 23:29:23 +00:00
total += 2;
2017-02-16 21:16:21 +00:00
}
function packAnswer(a) {
packQuestion(a);
if ('undefined' === typeof a.ttl) {
throw new Error("missing ttl");
}
// 32-bit ttl
dv.setUint32(total, a.ttl, false);
total += 4;
if (a.rdata) {
// 16-bit
dv.setUint16(total, a.rdata.byteLength, false);
total += 2;
(new Uint8Array(a.rdata)).forEach(function (b) {
dv.setUint8(total, b, false);
total += 1;
});
} else {
2017-02-16 22:57:15 +00:00
total = dnspack.packRdata(ab, dv, total, a);
2017-02-16 21:16:21 +00:00
}
}
packet.question.forEach(packQuestion);
2017-02-18 01:57:25 +00:00
(packet.answer||[]).forEach(packAnswer);
(packet.authority||[]).forEach(packAnswer);
(packet.additional||[]).forEach(packAnswer);
2017-02-16 21:16:21 +00:00
// TODO handle compression pointers
// TODO handle edns
return ab.slice(0, total);
}
/*
, packLabels: function(map, labels) {
2017-02-11 22:41:01 +00:00
}
2017-02-16 21:16:21 +00:00
*/
2017-02-11 22:41:01 +00:00
, packHeader: function(h) {
var val = 0;
2017-02-18 01:32:45 +00:00
var req = h.opcode;
var res = h.qr || h.aa || h.ra || h.rcode;
if (req && res) {
throw new Error("Both request-only and response-only headers are set: " + JSON.stringify(h));
}
2017-02-11 22:41:01 +00:00
2017-02-18 01:32:45 +00:00
// TODO check if is request or response
val += ((h.qr || 0) << 15) & 0x8000;
val += ((h.opcode || 0) << 11) & 0x7800;
val += ((h.aa || 0) << 10) & 0x400;
val += ((h.tc || 0) << 9) & 0x200;
val += ((h.rd || 0) << 8) & 0x100;
val += ((h.ra || 0) << 7) & 0x80;
val += ((h.res1 || 0) << 6) & 0x40;
val += ((h.res2 || 0) << 5) & 0x20;
val += ((h.res3 || 0) << 4) & 0x10;
val += (h.rcode || 0) & 0xF;
2017-02-11 22:41:01 +00:00
return val;
}
2017-02-16 21:16:21 +00:00
};
2017-02-16 22:52:07 +00:00
dnspack.packRdata = exports.DNS_RDATA_PACK || require('./dns.rdata.pack.js').DNS_RDATA_PACK;
}('undefined' !== typeof window ? window : exports));