diff --git a/index.js b/index.js index be85d48..aeab452 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,6 @@ var types = require('./types') +var rcodes = require('./rcodes') +var opcodes = require('./opcodes') var ip = require('ip') var Buffer = require('safe-buffer').Buffer @@ -126,6 +128,16 @@ header.decode = function (buf, offset) { id: buf.readUInt16BE(offset), type: flags & RESPONSE_FLAG ? 'response' : 'query', flags: flags & 32767, + flag_qr: ((flags >> 15) & 0x1) === 1, + opcode: opcodes.toString((flags >> 11) & 0xf), + flag_auth: ((flags >> 10) & 0x1) === 1, + flag_trunc: ((flags >> 9) & 0x1) === 1, + flag_rd: ((flags >> 8) & 0x1) === 1, + flag_ra: ((flags >> 7) & 0x1) === 1, + flag_z: ((flags >> 6) & 0x1) === 1, + flag_ad: ((flags >> 5) & 0x1) === 1, + flag_cd: ((flags >> 4) & 0x1) === 1, + rcode: rcodes.toString(flags & 0xf), questions: new Array(buf.readUInt16BE(offset + 4)), answers: new Array(buf.readUInt16BE(offset + 6)), authorities: new Array(buf.readUInt16BE(offset + 8)), diff --git a/opcodes.js b/opcodes.js new file mode 100644 index 0000000..6b42db3 --- /dev/null +++ b/opcodes.js @@ -0,0 +1,48 @@ +/* + * Traditional DNS header OPCODEs (4-bits) defined by IANA in + * https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5 + */ + +exports.toString = function (opcode) { + switch (opcode) { + case 0: return 'QUERY' + case 1: return 'IQUERY' + case 2: return 'STATUS' + case 3: return 'OPCODE_3' + case 4: return 'NOTIFY' + case 5: return 'UPDATE' + case 6: return 'OPCODE_6' + case 7: return 'OPCODE_7' + case 8: return 'OPCODE_8' + case 9: return 'OPCODE_9' + case 10: return 'OPCODE_10' + case 11: return 'OPCODE_11' + case 12: return 'OPCODE_12' + case 13: return 'OPCODE_13' + case 14: return 'OPCODE_14' + case 15: return 'OPCODE_15' + } + return 'OPCODE_' + opcode +} + +exports.toOpcode = function (code) { + switch (code.toUpperCase()) { + case 'QUERY': return 0 + case 'IQUERY': return 1 + case 'STATUS': return 2 + case 'OPCODE_3': return 3 + case 'NOTIFY': return 4 + case 'UPDATE': return 5 + case 'OPCODE_6': return 6 + case 'OPCODE_7': return 7 + case 'OPCODE_8': return 8 + case 'OPCODE_9': return 9 + case 'OPCODE_10': return 10 + case 'OPCODE_11': return 11 + case 'OPCODE_12': return 12 + case 'OPCODE_13': return 13 + case 'OPCODE_14': return 14 + case 'OPCODE_15': return 15 + } + return 0 +} diff --git a/package.json b/package.json index 736ba25..5f646d3 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,9 @@ ], "files": [ "index.js", - "types.js" + "types.js", + "rcodes.js", + "opcodes.js" ], "author": "Mathias Buus", "license": "MIT" diff --git a/rcodes.js b/rcodes.js new file mode 100644 index 0000000..492b2f3 --- /dev/null +++ b/rcodes.js @@ -0,0 +1,48 @@ +/* + * Traditional DNS header RCODEs (4-bits) defined by IANA in + * https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml + */ + +exports.toString = function (rcode) { + switch (rcode) { + case 0: return 'NOERROR' + case 1: return 'FORMERR' + case 2: return 'SERVFAIL' + case 3: return 'NXDOMAIN' + case 4: return 'NOTIMP' + case 5: return 'REFUSED' + case 6: return 'YXDOMAIN' + case 7: return 'YXRRSET' + case 8: return 'NXRRSET' + case 9: return 'NOTAUTH' + case 10: return 'NOTZONE' + case 11: return 'RCODE_11' + case 12: return 'RCODE_12' + case 13: return 'RCODE_13' + case 14: return 'RCODE_14' + case 15: return 'RCODE_15' + } + return 'RCODE_' + rcode +} + +exports.toRcode = function (code) { + switch (code.toUpperCase()) { + case 'NOERROR': return 0 + case 'FORMERR': return 1 + case 'SERVFAIL': return 2 + case 'NXDOMAIN': return 3 + case 'NOTIMP': return 4 + case 'REFUSED': return 5 + case 'YXDOMAIN': return 6 + case 'YXRRSET': return 7 + case 'NXRRSET': return 8 + case 'NOTAUTH': return 9 + case 'NOTZONE': return 10 + case 'RCODE_11': return 11 + case 'RCODE_12': return 12 + case 'RCODE_13': return 13 + case 'RCODE_14': return 14 + case 'RCODE_15': return 15 + } + return 0 +} diff --git a/test.js b/test.js index e8ef6b2..fad0ddc 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,7 @@ var tape = require('tape') var packet = require('./') +var rcodes = require('./rcodes') +var opcodes = require('./opcodes') var Buffer = require('safe-buffer').Buffer tape('unknown', function (t) { @@ -173,6 +175,44 @@ tape('response', function (t) { t.end() }) +tape('rcode', function (t) { + const errors = ['NOERROR', 'FORMERR', 'SERVFAIL', 'NXDOMAIN', 'NOTIMP', 'REFUSED', 'YXDOMAIN', 'YXRRSET', 'NXRRSET', 'NOTAUTH', 'NOTZONE', 'RCODE_11', 'RCODE_12', 'RCODE_13', 'RCODE_14', 'RCODE_15'] + for (var i in errors) { + var code = rcodes.toRcode(errors[i]) + t.ok(errors[i] === rcodes.toString(code), 'rcode conversion from/to string matches: ' + rcodes.toString(code)) + } + + const ops = ['QUERY', 'IQUERY', 'STATUS', 'OPCODE_3', 'NOTIFY', 'UPDATE', 'OPCODE_6', 'OPCODE_7', 'OPCODE_8', 'OPCODE_9', 'OPCODE_10', 'OPCODE_11', 'OPCODE_12', 'OPCODE_13', 'OPCODE_14', 'OPCODE_15'] + for (i in ops) { + code = opcodes.toOpcode(ops[i]) + t.ok(ops[i] === opcodes.toString(code), 'opcode conversion from/to string matches: ' + opcodes.toString(code)) + } + + var buf = packet.encode({ + type: 'response', + id: 45632, + flags: 0x8480, + answers: [{ + type: 'A', + name: 'hello.example.net', + data: '127.0.0.1' + }] + }) + var val = packet.decode(buf) + t.ok(val.type === 'response', 'decode type') + t.ok(val.opcode === 'QUERY', 'decode opcode') + t.ok(val.flag_qr === true, 'decode flag_auth') + t.ok(val.flag_auth === true, 'decode flag_auth') + t.ok(val.flag_trunc === false, 'decode flag_trunc') + t.ok(val.flag_rd === false, 'decode flag_rd') + t.ok(val.flag_ra === true, 'decode flag_ra') + t.ok(val.flag_z === false, 'decode flag_z') + t.ok(val.flag_ad === false, 'decode flag_ad') + t.ok(val.flag_cd === false, 'decode flag_cd') + t.ok(val.rcode === 'NOERROR', 'decode rcode') + t.end() +}) + function testEncoder (t, packet, val) { var buf = packet.encode(val) var val2 = packet.decode(buf)