@sintaxi/dnsd
Version:
Dynamic authoritative name server for Iris services
306 lines (244 loc) • 7.6 kB
JavaScript
// Copyright 2012 Iris Couch, all rights reserved.
//
// Parse DNS messages
var util = require('util')
var constants = require('./constants')
module.exports = { 'id': id
, 'qr': qr
, 'aa': aa
, 'tc': tc
, 'rd': rd
, 'ra': ra
, 'ad': ad
, 'cd': cd
, 'rcode': rcode
, 'opcode': opcode
, 'record_count': record_count
, 'record_name' : record_name
, 'record_class': record_class
, 'record_ttl' : record_ttl
, 'record_type' : record_type
, 'record_data' : record_data
, 'uncompress' : uncompress
, 'sections' : sections
, 'mx': mx
, 'srv': srv
, 'soa': soa
, 'txt': txt
}
function id(msg) {
return msg.readUInt16BE(0)
}
function qr(msg) {
return msg.readUInt8(2) >> 7
}
function opcode(msg) {
return (msg.readUInt8(2) >> 3) & 0x0f
}
function aa(msg) {
return (msg.readUInt8(2) >> 2) & 0x01
}
function tc(msg) {
return (msg.readUInt8(2) >> 1) & 0x01
}
function rd(msg) {
return msg.readUInt8(2) & 0x01
}
function ra(msg) {
return msg.readUInt8(3) >> 7
}
function ad(msg) {
return msg.readUInt8(3) >> 5 & 0x01
}
function cd(msg) {
return msg.readUInt8(3) >> 4 & 0x01
}
function rcode(msg) {
return msg.readUInt8(3) & 0x0f
}
function record_count(msg, name) {
if(name == 'question')
return msg.readUInt16BE(4)
else if(name == 'answer')
return msg.readUInt16BE(6)
else if(name == 'authority')
return msg.readUInt16BE(8)
else if(name == 'additional')
return msg.readUInt16BE(10)
else
throw new Error('Unknown section name: ' + name)
}
function record_name(msg, section_name, offset) {
var rec = record(msg, section_name, offset)
return rec.name
}
function record_class(msg, section_name, offset) {
var rec = record(msg, section_name, offset)
return rec.class
}
function record_type(msg, section_name, offset) {
var rec = record(msg, section_name, offset)
return rec.type
}
function record_ttl(msg, section_name, offset) {
var rec = record(msg, section_name, offset)
return rec.ttl
}
function record_data(msg, section_name, offset) {
var rec = record(msg, section_name, offset)
return rec.data
}
function record_class(msg, section_name, offset) {
var rec = record(msg, section_name, offset)
return rec.class
}
function record(msg, section_name, offset) {
if(typeof offset != 'number' || isNaN(offset) || offset < 0)
throw new Error('Offset must be a natural number')
// Support msg being a previously-parsed sections object.
var sects = Buffer.isBuffer(msg)
? sections(msg)
: msg
var records = sects[section_name]
if(!records)
throw new Error('No such section: "'+section_name+'"')
var rec = records[offset]
if(!rec)
throw new Error('Bad offset for section "'+section_name+'": ' + offset)
return rec
}
function sections(msg) {
// Count the times this message has been parsed, for debugging and testing purposes.
if('__parsed' in msg)
msg.__parsed += 1
var position = 12 // First byte of the first section
, result = {'question':[], 'answer':[], 'authority':[], 'additional':[]}
, need = { 'question' : record_count(msg, 'question')
, 'answer' : record_count(msg, 'answer')
, 'authority' : record_count(msg, 'authority')
, 'additional': record_count(msg, 'additional')
}
var states = ['question', 'answer', 'authority', 'additional', 'done']
, state = states.shift()
while(true) {
if(state == 'done')
return result
else if(result[state].length == need[state])
state = states.shift()
else if(!state)
throw new Error('Unknown parsing state at position '+position+': '+JSON.stringify(state))
else
add_record()
}
function add_record() {
var record = {}
var data = domain_parts(msg, position)
record.name = data.parts.join('.')
position += data.length
record.type = msg.readUInt16BE(position + 0)
record.class = msg.readUInt16BE(position + 2)
position += 4
if(state != 'question') {
record.ttl = msg.readUInt32BE(position + 0)
var rdata_len = msg.readUInt16BE(position + 4)
position += 6
record.data = msg.slice(position, position + rdata_len)
position += rdata_len
if(constants.type(record.type) === 'OPT') {
// EDNS
if(record.name !== '')
throw new Error('EDNS record option for non-root domain: ' + record.name)
record.udp_size = record.class
delete record.class
record.extended_rcode = (record.ttl >> 24)
record.edns_version = (record.ttl >> 16) & 0xff
record.zero = (record.ttl >> 8)
delete record.ttl
record.data = Array.prototype.slice.call(record.data)
}
}
result[state] = result[state] || []
result[state].push(record)
}
}
function mx(msg, data) {
return [ data.readUInt16BE(0)
, uncompress(msg, data.slice(2))
]
}
function srv(msg, data) {
return { 'priority': data.readUInt16BE(0)
, 'weight' : data.readUInt16BE(2)
, 'port' : data.readUInt16BE(4)
, 'target' : uncompress(msg, data.slice(6)) // Techncially compression is not allowed in RFC 2782.
}
}
function soa(msg, data) {
var result = domain_parts(msg, data)
, offset = result.length
, mname = result.parts.join('.')
result = domain_parts(msg, data.slice(offset))
var rname = result.parts.join('.')
offset += result.length
return { 'mname' : mname
, 'rname' : rname //.replace(/\./, '@')
, 'serial' : data.readUInt32BE(offset + 0)
, 'refresh': data.readUInt32BE(offset + 4)
, 'retry' : data.readUInt32BE(offset + 8)
, 'expire' : data.readUInt32BE(offset + 12)
, 'ttl' : data.readUInt32BE(offset + 16)
}
}
function txt(msg, data) {
var parts = []
while(data.length) {
var len = data.readUInt8(0)
parts.push(data.slice(1, 1+len).toString('ascii'))
data = data.slice(1+len)
}
return parts
}
function uncompress(msg, offset) {
var data = domain_parts(msg, offset)
return data.parts.join('.')
}
function domain_parts(msg, offset) {
if(Buffer.isBuffer(offset)) {
var full_message = msg
msg = offset
offset = 0
}
if(typeof offset != 'number' || isNaN(offset) || offset < 0 || offset > msg.length)
throw new Error('Bad offset: ' + offset)
var parts = []
, real_length = 0
, jumped = false
var i = 0
while(true) {
if(++i >= 100)
throw new Error('Too many iterations uncompressing name')
var byte = msg.readUInt8(offset)
, flags = byte >> 6
, len = byte & 0x3f // 0 - 63
offset += 1
add_length(1)
if(flags === 0x03) {
offset = (len << 8) + msg.readUInt8(offset)
add_length(1)
jumped = true
// If processing so far has just been on some given fragment, begin using the full message now.
msg = full_message || msg
}
else if(len == 0)
return {'parts':parts, 'length':real_length}
else {
parts.push(msg.toString('ascii', offset, offset + len))
offset += len
add_length(len)
}
}
function add_length(amount) {
if(! jumped)
real_length += amount
}
}