Packet.parse = function(msg, socket) { var state, len, pos, val, rdata_len, rdata, label_index = {}, counts = {}, section, count; var packet = new Packet(socket); pos = 0; state = 'HEADER'; msg = BufferCursor(msg); len = msg.length; while (true) { switch (state) { case 'HEADER': packet.header.id = msg.readUInt16BE(); val = msg.readUInt16BE(); packet.header.qr = (val & 0x8000) >> 15; packet.header.opcode = (val & 0x7800) >> 11; packet.header.aa = (val & 0x400) >> 10; packet.header.tc = (val & 0x200) >> 9; packet.header.rd = (val & 0x100) >> 8; packet.header.ra = (val & 0x80) >> 7; packet.header.res1 = (val & 0x40) >> 6; packet.header.res2 = (val & 0x20) >> 5; packet.header.res3 = (val & 0x10) >> 4; packet.header.rcode = (val & 0xF); counts.qdcount = msg.readUInt16BE(); counts.ancount = msg.readUInt16BE(); counts.nscount = msg.readUInt16BE(); counts.arcount = msg.readUInt16BE(); state = 'QUESTION'; break; case 'QUESTION': val = {}; val.name = name_unpack(msg, label_index); val.type = msg.readUInt16BE(); val.class = msg.readUInt16BE(); packet.question.push(val); // TODO handle qdcount > 0 in practice no one sends this state = 'RESOURCE_RECORD'; section = 'answer'; count = 'ancount'; break; case 'RESOURCE_RECORD': if (counts[count] === packet[section].length) { switch (section) { case 'answer': section = 'authority'; count = 'nscount'; break; case 'authority': section = 'additional'; count = 'arcount'; break; case 'additional': state = 'END'; break; } } else { state = 'RR_UNPACK'; } break; case 'RR_UNPACK': val = {}; val.name = name_unpack(msg, label_index); val.type = msg.readUInt16BE(); val.class = msg.readUInt16BE(); val.ttl = msg.readUInt32BE(); rdata_len = msg.readUInt16BE(); rdata = msg.slice(rdata_len); state = consts.QTYPE_TO_NAME[val.type]; break; case 'RESOURCE_DONE': packet[section].push(val); state = 'RESOURCE_RECORD'; break; case 'A': val.address = new ipaddr.IPv4(rdata.toByteArray()); val.address = val.address.toString(); state = 'RESOURCE_DONE'; break; case 'AAAA': val.address = new ipaddr.IPv6(rdata.toByteArray('readUInt16BE')); val.address = val.address.toString(); state = 'RESOURCE_DONE'; break; case 'NS': case 'CNAME': case 'PTR': pos = msg.tell(); msg.seek(pos - rdata_len); val.data = name_unpack(msg, label_index); msg.seek(pos); state = 'RESOURCE_DONE'; break; case 'TXT': val.data = ''; while (!rdata.eof()) { val.data += rdata.toString('ascii', rdata.readUInt8()); } state = 'RESOURCE_DONE'; break; case 'MX': val.priority = rdata.readUInt16BE(); pos = msg.tell(); msg.seek(pos - rdata_len + rdata.tell()); val.exchange = name_unpack(msg, label_index); msg.seek(pos); state = 'RESOURCE_DONE'; break; case 'SRV': val.priority = rdata.readUInt16BE(); val.weight = rdata.readUInt16BE(); val.port = rdata.readUInt16BE(); pos = msg.tell(); msg.seek(pos - rdata_len + rdata.tell()); val.target = name_unpack(msg, label_index); msg.seek(pos); state = 'RESOURCE_DONE'; break; case 'SOA': pos = msg.tell(); msg.seek(pos - rdata_len + rdata.tell()); val.primary = name_unpack(msg, label_index); val.admin = name_unpack(msg, label_index); rdata.seek(msg.tell() - (pos - rdata_len + rdata.tell())); msg.seek(pos); val.serial = rdata.readUInt32BE(); val.refresh = rdata.readInt32BE(); val.retry = rdata.readInt32BE(); val.expiration = rdata.readInt32BE(); val.minimum = rdata.readInt32BE(); state = 'RESOURCE_DONE'; break; case 'OPT': // assert first entry in additional counts[count] -= 1; packet.payload = val.class; pos = msg.tell(); msg.seek(pos - 6); packet.header.rcode = (msg.readUInt8() << 4) + packet.header.rcode; packet.edns_version = msg.readUInt8(); val = msg.readUInt16BE(); msg.seek(pos); packet.do = (val & 0x8000) << 15; while (!rdata.eof()) { packet.edns_options.push({ code: rdata.readUInt16BE(), data: rdata.slice(rdata.readUInt16BE()).buffer }); } state = 'RESOURCE_RECORD'; break; case 'NAPTR': val.order = rdata.readUInt16BE(); val.preference = rdata.readUInt16BE(); pos = rdata.readUInt8(); val.flags = rdata.toString('ascii', pos); pos = rdata.readUInt8(); val.service = rdata.toString('ascii', pos); pos = rdata.readUInt8(); val.regexp = rdata.toString('ascii', pos); pos = rdata.readUInt8(); val.replacement = rdata.toString('ascii', pos); state = 'RESOURCE_DONE'; break; case 'END': return packet; break; default: //console.log(state, val); state = 'RESOURCE_DONE'; break; } } };
exports.parse = function(msg) { var state, pos, val, rdata, counts = {}, section, count; var packet = new DNSRecord(); pos = 0; state = PARSE_HEADER; if (typeof msg !== 'Buffer') { msg = new Buffer(msg,'binary'); } msg = BufferCursor(msg, true); while (true) { switch (state) { case PARSE_HEADER: state = parseHeader(msg, packet, counts); break; case PARSE_QUESTION: state = parseQuestion(msg, packet); section = 'answer'; count = 0; break; case PARSE_RESOURCE_RECORD: if (count === packet[section].length) { switch (section) { case 'answer': section = 'authority'; count = 0; break; case 'authority': section = 'additional'; count = 0; break; case 'additional': state = PARSE_END; break; } } else { state = PARSE_RR_UNPACK; } break; case PARSE_RR_UNPACK: val = {}; rdata = {}; state = parseRR(msg, val, rdata); break; case PARSE_RESOURCE_DONE: packet[section][count] = val; count++; state = PARSE_RESOURCE_RECORD; break; case PARSE_A: state = parseA(val, msg); break; case PARSE_AAAA: state = parseAAAA(val, msg); break; case PARSE_NS: case PARSE_CNAME: case PARSE_PTR: state = parseCname(val, msg); break; case PARSE_SPF: case PARSE_TXT: state = parseTxt(val, msg, rdata); break; case PARSE_MX: state = parseMx(val, msg); break; case PARSE_SRV: state = parseSrv(val, msg); break; case PARSE_SOA: state = parseSoa(val, msg); break; case PARSE_OPT: // assert first entry in additional rdata.buf = msg.slice(rdata.len); counts[count] -= 1; packet.payload = val.class; pos = msg.tell(); msg.seek(pos - 6); packet.header.rcode = (msg.readUInt8() << 4) + packet.header.rcode; packet.edns_version = msg.readUInt8(); val = msg.readUInt16BE(); msg.seek(pos); packet.do = (val & 0x8000) << 15; while (!rdata.buf.eof()) { packet.edns_options.push({ code: rdata.buf.readUInt16BE(), data: rdata.buf.slice(rdata.buf.readUInt16BE()).buffer }); } state = PARSE_RESOURCE_RECORD; break; case PARSE_NAPTR: state = parseNaptr(val, msg); break; case PARSE_END: return packet; break; default: //console.log(state, val); val.data = msg.slice(rdata.len); state = PARSE_RESOURCE_DONE; break; } } };
Packet.write = function(buff, packet) { var state, next, name, val, section, count, pos, rdata_pos, last_resource, label_index = {}; buff = BufferCursor(buff); if (typeof(packet.edns_version) !== 'undefined') { state = 'EDNS'; } else { state = 'HEADER'; } while (true) { try { switch (state) { case 'EDNS': val = { name: '', type: consts.NAME_TO_QTYPE.OPT, class: packet.payload }; pos = packet.header.rcode; val.ttl = packet.header.rcode >> 4; packet.header.rcode = pos - (val.ttl << 4); val.ttl = (val.ttl << 8) + packet.edns_version; val.ttl = (val.ttl << 16) + (packet.do << 15) & 0x8000; packet.additional.splice(0, 0, val); state = 'HEADER'; break; case 'HEADER': buff.writeUInt16BE(packet.header.id); val = 0; val += (packet.header.qr << 15) & 0x8000; val += (packet.header.opcode << 11) & 0x7800; val += (packet.header.aa << 10) & 0x400; val += (packet.header.tc << 9) & 0x200; val += (packet.header.rd << 8) & 0x100; val += (packet.header.ra << 7) & 0x80; val += (packet.header.res1 << 6) & 0x40; val += (packet.header.res1 << 5) & 0x20; val += (packet.header.res1 << 4) & 0x10; val += packet.header.rcode & 0xF; buff.writeUInt16BE(val); // TODO assert on question.length > 1, in practice multiple questions // aren't used buff.writeUInt16BE(1); // answer offset 6 buff.writeUInt16BE(packet.answer.length); // authority offset 8 buff.writeUInt16BE(packet.authority.length); // additional offset 10 buff.writeUInt16BE(packet.additional.length); state = 'QUESTION'; break; case 'TRUNCATE': buff.seek(2); val = buff.readUInt16BE(); val |= (1 << 9) & 0x200; buff.seek(2); buff.writeUInt16BE(val); switch (section) { case 'answer': pos = 6; // seek to authority and clear it and additional out buff.seek(8); buff.writeUInt16BE(0); buff.writeUInt16BE(0); break; case 'authority': pos = 8; // seek to additional and clear it out buff.seek(10); buff.writeUInt16BE(0); break; case 'additional': pos = 10; break; } buff.seek(pos); buff.writeUInt16BE(count - 1); buff.seek(last_resource); state = 'END'; break; case 'NAME_PACK': name_pack(name, buff, label_index); state = next; break; case 'QUESTION': val = packet.question[0]; name = val.name; state = 'NAME_PACK'; next = 'QUESTION_NEXT'; break; case 'QUESTION_NEXT': buff.writeUInt16BE(val.type); buff.writeUInt16BE(val.class); state = 'RESOURCE_RECORD'; section = 'answer'; count = 0; break; case 'RESOURCE_RECORD': last_resource = buff.tell(); if (packet[section].length == count) { switch (section) { case 'answer': section = 'authority'; state = 'RESOURCE_RECORD'; break; case 'authority': section = 'additional'; state = 'RESOURCE_RECORD'; break; case 'additional': state = 'END'; break; } count = 0; } else { state = 'RESOURCE_WRITE'; } break; case 'RESOURCE_WRITE': val = packet[section][count]; name = val.name; state = 'NAME_PACK'; next = 'RESOURCE_WRITE_NEXT'; break; case 'RESOURCE_WRITE_NEXT': buff.writeUInt16BE(val.type); buff.writeUInt16BE(val.class); buff.writeUInt32BE(val.ttl); // where the rdata length goes rdata_pos = buff.tell(); buff.writeUInt16BE(0); state = consts.QTYPE_TO_NAME[val.type]; break; case 'RESOURCE_DONE': pos = buff.tell(); buff.seek(rdata_pos); buff.writeUInt16BE(pos - rdata_pos - 2); buff.seek(pos); count += 1; state = 'RESOURCE_RECORD'; break; case 'A': val = ipaddr.parse(val.address).toByteArray(); val.forEach(function(b) { buff.writeUInt8(b); }); state = 'RESOURCE_DONE'; break; case 'AAAA': val = ipaddr.parse(val.address).toByteArray(); val.forEach(function(b) { buff.writeUInt16BE(b); }); state = 'RESOURCE_DONE'; break; case 'NS': case 'CNAME': case 'PTR': name = val.data; state = 'NAME_PACK'; next = 'RESOURCE_DONE'; break; case 'TXT': //TODO XXX FIXME -- split on max char string and loop buff.writeUInt8(val.data.length); buff.write(val.data, val.data.length, 'ascii'); state = 'RESOURCE_DONE'; break; case 'MX': buff.writeUInt16BE(val.priority); name = val.exchange; state = 'NAME_PACK'; next = 'RESOURCE_DONE'; break; case 'SRV': buff.writeUInt16BE(val.priority); buff.writeUInt16BE(val.weight); buff.writeUInt16BE(val.port); name = val.target; state = 'NAME_PACK'; next = 'RESOURCE_DONE'; break; case 'SOA': name = val.primary; state = 'NAME_PACK'; next = 'SOA_ADMIN'; break; case 'SOA_ADMIN': name = val.admin; state = 'NAME_PACK'; next = 'SOA_NEXT'; break; case 'SOA_NEXT': buff.writeUInt32BE(val.serial); buff.writeInt32BE(val.refresh); buff.writeInt32BE(val.retry); buff.writeInt32BE(val.expiration); buff.writeInt32BE(val.minimum); state = 'RESOURCE_DONE'; break; case 'OPT': while (packet.edns_options.length) { val = packet.edns_options.pop(); buff.writeUInt16BE(val.code); buff.writeUInt16BE(val.data.length); for (pos = 0; pos < val.data.length; pos++) { buff.writeUInt8(val.data.readUInt8(pos)); } } state = 'RESOURCE_DONE'; break; case 'NAPTR': buff.writeUInt16BE(val.order); buff.writeUInt16BE(val.preference); buff.writeUInt8(val.flags.length); buff.write(val.flags, val.flags.length, 'ascii'); buff.writeUInt8(val.service.length); buff.write(val.service, val.service.length, 'ascii'); buff.writeUInt8(val.regexp.length); buff.write(val.regexp, val.regexp.length, 'ascii'); buff.writeUInt8(val.replacement.length); buff.write(val.replacement, val.replacement.length, 'ascii'); state = 'RESOURCE_DONE'; break; case 'END': return buff.tell(); break; default: throw new Error('WTF No State While Writing'); break; } } catch (e) { if (e instanceof BufferCursorOverflow) { state = 'TRUNCATE'; } else { throw e; } } } };
exports.write = function(buff, packet) { var state, val, section, count, rdata, last_resource, label_index = {}; buff = BufferCursor(buff, true); if (typeof(packet.edns_version) !== 'undefined') { state = WRITE_EDNS; } else { state = WRITE_HEADER; } while (true) { try { switch (state) { case WRITE_EDNS: state = writeEns(packet); break; case WRITE_HEADER: state = writeHeader(buff, packet); break; case WRITE_TRUNCATE: state = writeTruncate(buff, packet, section, last_resource); break; case WRITE_QUESTION: state = writeQuestion(buff, packet.question[0], label_index); section = 'answer'; count = 0; break; case WRITE_RESOURCE_RECORD: last_resource = buff.tell(); if (packet[section].length == count) { switch (section) { case 'answer': section = 'authority'; state = WRITE_RESOURCE_RECORD; break; case 'authority': section = 'additional'; state = WRITE_RESOURCE_RECORD; break; case 'additional': state = WRITE_END; break; } count = 0; } else { state = WRITE_RESOURCE_WRITE; } break; case WRITE_RESOURCE_WRITE: rdata = {} val = packet[section][count]; state = writeResource(buff, val, label_index, rdata); break; case WRITE_RESOURCE_DONE: count += 1; state = writeResourceDone(buff, rdata); break; case WRITE_A: case WRITE_AAAA: state = writeIp(buff, val); break; case WRITE_NS: case WRITE_CNAME: case WRITE_PTR: state = writeCname(buff, val, label_index); break; case WRITE_SPF: case WRITE_TXT: state = writeTxt(buff, val); break; case WRITE_MX: state = writeMx(buff, val, label_index); break; case WRITE_SRV: state = writeSrv(buff, val, label_index); break; case WRITE_SOA: state = writeSoa(buff, val, label_index); break; case WRITE_OPT: state = writeOpt(buff, packet); break; case WRITE_NAPTR: state = writeNaptr(buff, val); break; case WRITE_END: return buff.tell(); break; default: throw new Error('WTF No State While Writing'); break; } } catch (e) { if (e instanceof BufferCursorOverflow) { state = WRITE_TRUNCATE; } else { throw e; } } } };