UNPKG

17 kBJavaScriptView Raw
1// Copyright 2011 Timothy J Fontaine <tjfontaine@gmail.com>
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the 'Software'), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE
20
21'use strict';
22
23var consts = require('./consts'),
24 BufferCursor = require('buffercursor'),
25 BufferCursorOverflow = BufferCursor.BufferCursorOverflow,
26 ipaddr = require('ipaddr.js'),
27 util = require('util');
28
29var Packet = module.exports = function(socket) {
30 this.header = {
31 id: 0,
32 qr: 0,
33 opcode: 0,
34 aa: 0,
35 tc: 0,
36 rd: 1,
37 ra: 0,
38 res1: 0,
39 res2: 0,
40 res3: 0,
41 rcode: 0
42 };
43 this.question = [];
44 this.answer = [];
45 this.authority = [];
46 this.additional = [];
47 this.edns_options = [];
48 this.payload = undefined;
49 this.address = undefined;
50
51 this._socket = socket;
52};
53
54Packet.prototype.send = function() {
55 var buff, len, size;
56
57 if (typeof(this.edns_version) !== 'undefined') {
58 size = 4096;
59 }
60
61 this.payload = size = size || this._socket.base_size;
62
63 buff = this._socket.buffer(size);
64 len = Packet.write(buff, this);
65 this._socket.send(len);
66};
67
68var LABEL_POINTER = 0xC0;
69
70var isPointer = function(len) {
71 return (len & LABEL_POINTER) === LABEL_POINTER;
72};
73
74var name_unpack = function(buff, index) {
75 var parts, len, start, pos, i, part, combine = [];
76
77 start = buff.tell();
78
79 parts = [];
80 len = buff.readUInt8();
81
82 while (len !== 0) {
83 if (isPointer(len)) {
84 len -= LABEL_POINTER;
85 len = len << 8;
86 pos = len + buff.readUInt8();
87 parts.push({
88 pos: pos,
89 value: index[pos]
90 });
91 len = 0;
92 } else {
93 parts.push({
94 pos: buff.tell() - 1,
95 value: buff.toString('ascii', len)
96 });
97 len = buff.readUInt8();
98 }
99 }
100
101 for (i = parts.length - 1; i >= 0; i--) {
102 part = parts[i];
103 combine.splice(0, 0, part.value);
104 index[part.pos] = combine.join('.');
105 }
106
107 return combine.join('.');
108};
109
110var name_pack = function(str, buff, index) {
111 var offset, dot, part;
112
113 while (str) {
114 if (index[str]) {
115 offset = (LABEL_POINTER << 8) + index[str];
116 buff.writeUInt16BE(offset);
117 break;
118 } else {
119 index[str] = buff.tell();
120 dot = str.indexOf('.');
121 if (dot > -1) {
122 part = str.slice(0, dot);
123 str = str.slice(dot + 1);
124 } else {
125 part = str;
126 str = undefined;
127 }
128 buff.writeUInt8(part.length);
129 buff.write(part, part.length, 'ascii');
130 }
131 }
132
133 if (!str) {
134 buff.writeUInt8(0);
135 }
136};
137
138Packet.write = function(buff, packet) {
139 var state,
140 next,
141 name,
142 val,
143 section,
144 count,
145 pos,
146 rdata_pos,
147 last_resource,
148 label_index = {};
149
150 buff = BufferCursor(buff);
151
152 if (typeof(packet.edns_version) !== 'undefined') {
153 state = 'EDNS';
154 } else {
155 state = 'HEADER';
156 }
157
158 while (true) {
159 try {
160 switch (state) {
161 case 'EDNS':
162 val = {
163 name: '',
164 type: consts.NAME_TO_QTYPE.OPT,
165 class: packet.payload
166 };
167 pos = packet.header.rcode;
168 val.ttl = packet.header.rcode >> 4;
169 packet.header.rcode = pos - (val.ttl << 4);
170 val.ttl = (val.ttl << 8) + packet.edns_version;
171 val.ttl = (val.ttl << 16) + (packet.do << 15) & 0x8000;
172 packet.additional.splice(0, 0, val);
173 state = 'HEADER';
174 break;
175 case 'HEADER':
176 buff.writeUInt16BE(packet.header.id);
177 val = 0;
178 val += (packet.header.qr << 15) & 0x8000;
179 val += (packet.header.opcode << 11) & 0x7800;
180 val += (packet.header.aa << 10) & 0x400;
181 val += (packet.header.tc << 9) & 0x200;
182 val += (packet.header.rd << 8) & 0x100;
183 val += (packet.header.ra << 7) & 0x80;
184 val += (packet.header.res1 << 6) & 0x40;
185 val += (packet.header.res1 << 5) & 0x20;
186 val += (packet.header.res1 << 4) & 0x10;
187 val += packet.header.rcode & 0xF;
188 buff.writeUInt16BE(val);
189 // TODO assert on question.length > 1, in practice multiple questions
190 // aren't used
191 buff.writeUInt16BE(1);
192 // answer offset 6
193 buff.writeUInt16BE(packet.answer.length);
194 // authority offset 8
195 buff.writeUInt16BE(packet.authority.length);
196 // additional offset 10
197 buff.writeUInt16BE(packet.additional.length);
198 state = 'QUESTION';
199 break;
200 case 'TRUNCATE':
201 buff.seek(2);
202 val = buff.readUInt16BE();
203 val |= (1 << 9) & 0x200;
204 buff.seek(2);
205 buff.writeUInt16BE(val);
206 switch (section) {
207 case 'answer':
208 pos = 6;
209 // seek to authority and clear it and additional out
210 buff.seek(8);
211 buff.writeUInt16BE(0);
212 buff.writeUInt16BE(0);
213 break;
214 case 'authority':
215 pos = 8;
216 // seek to additional and clear it out
217 buff.seek(10);
218 buff.writeUInt16BE(0);
219 break;
220 case 'additional':
221 pos = 10;
222 break;
223 }
224 buff.seek(pos);
225 buff.writeUInt16BE(count - 1);
226 buff.seek(last_resource);
227 state = 'END';
228 break;
229 case 'NAME_PACK':
230 name_pack(name, buff, label_index);
231 state = next;
232 break;
233 case 'QUESTION':
234 val = packet.question[0];
235 name = val.name;
236 state = 'NAME_PACK';
237 next = 'QUESTION_NEXT';
238 break;
239 case 'QUESTION_NEXT':
240 buff.writeUInt16BE(val.type);
241 buff.writeUInt16BE(val.class);
242 state = 'RESOURCE_RECORD';
243 section = 'answer';
244 count = 0;
245 break;
246 case 'RESOURCE_RECORD':
247 last_resource = buff.tell();
248 if (packet[section].length == count) {
249 switch (section) {
250 case 'answer':
251 section = 'authority';
252 state = 'RESOURCE_RECORD';
253 break;
254 case 'authority':
255 section = 'additional';
256 state = 'RESOURCE_RECORD';
257 break;
258 case 'additional':
259 state = 'END';
260 break;
261 }
262 count = 0;
263 } else {
264 state = 'RESOURCE_WRITE';
265 }
266 break;
267 case 'RESOURCE_WRITE':
268 val = packet[section][count];
269 name = val.name;
270 state = 'NAME_PACK';
271 next = 'RESOURCE_WRITE_NEXT';
272 break;
273 case 'RESOURCE_WRITE_NEXT':
274 buff.writeUInt16BE(val.type);
275 buff.writeUInt16BE(val.class);
276 buff.writeUInt32BE(val.ttl);
277
278 // where the rdata length goes
279 rdata_pos = buff.tell();
280 buff.writeUInt16BE(0);
281
282 state = consts.QTYPE_TO_NAME[val.type];
283 break;
284 case 'RESOURCE_DONE':
285 pos = buff.tell();
286 buff.seek(rdata_pos);
287 buff.writeUInt16BE(pos - rdata_pos - 2);
288 buff.seek(pos);
289 count += 1;
290 state = 'RESOURCE_RECORD';
291 break;
292 case 'A':
293 case 'AAAA':
294 //TODO XXX FIXME -- assert that address is of proper type
295 val = ipaddr.parse(val.address).toByteArray();
296 val.forEach(function(b) {
297 buff.writeUInt8(b);
298 });
299 state = 'RESOURCE_DONE';
300 break;
301 case 'NS':
302 case 'CNAME':
303 case 'PTR':
304 name = val.data;
305 state = 'NAME_PACK';
306 next = 'RESOURCE_DONE';
307 break;
308 case 'TXT':
309 //TODO XXX FIXME -- split on max char string and loop
310 buff.writeUInt8(val.data.length);
311 buff.write(val.data, val.data.length, 'ascii');
312 state = 'RESOURCE_DONE';
313 break;
314 case 'MX':
315 buff.writeUInt16BE(val.priority);
316 name = val.exchange;
317 state = 'NAME_PACK';
318 next = 'RESOURCE_DONE';
319 break;
320 case 'SRV':
321 buff.writeUInt16BE(val.priority);
322 buff.writeUInt16BE(val.weight);
323 buff.writeUInt16BE(val.port);
324 name = val.target;
325 state = 'NAME_PACK';
326 next = 'RESOURCE_DONE';
327 break;
328 case 'SOA':
329 name = val.primary;
330 state = 'NAME_PACK';
331 next = 'SOA_ADMIN';
332 break;
333 case 'SOA_ADMIN':
334 name = val.admin;
335 state = 'NAME_PACK';
336 next = 'SOA_NEXT';
337 break;
338 case 'SOA_NEXT':
339 buff.writeUInt32BE(val.serial);
340 buff.writeInt32BE(val.refresh);
341 buff.writeInt32BE(val.retry);
342 buff.writeInt32BE(val.expiration);
343 buff.writeInt32BE(val.minimum);
344 state = 'RESOURCE_DONE';
345 break;
346 case 'OPT':
347 while (packet.edns_options.length) {
348 val = packet.edns_options.pop();
349 buff.writeUInt16BE(val.code);
350 buff.writeUInt16BE(val.data.length);
351 for (pos = 0; pos < val.data.length; pos++) {
352 buff.writeUInt8(val.data.readUInt8(pos));
353 }
354 }
355 state = 'RESOURCE_DONE';
356 break;
357 case 'NAPTR':
358 buff.writeUInt16BE(val.order);
359 buff.writeUInt16BE(val.preference);
360 buff.writeUInt8(val.flags.length);
361 buff.write(val.flags, val.flags.length, 'ascii');
362 buff.writeUInt8(val.service.length);
363 buff.write(val.service, val.service.length, 'ascii');
364 buff.writeUInt8(val.regexp.length);
365 buff.write(val.regexp, val.regexp.length, 'ascii');
366 buff.writeUInt8(val.replacement.length);
367 buff.write(val.replacement, val.replacement.length, 'ascii');
368 state = 'RESOURCE_DONE';
369 break;
370 case 'END':
371 return buff.tell();
372 break;
373 default:
374 throw new Error('WTF No State While Writing');
375 break;
376 }
377 } catch (e) {
378 if (e instanceof BufferCursorOverflow) {
379 state = 'TRUNCATE';
380 } else {
381 throw e;
382 }
383 }
384 }
385};
386
387Packet.parse = function(msg, socket) {
388 var state,
389 len,
390 pos,
391 val,
392 rdata_len,
393 rdata,
394 label_index = {},
395 counts = {},
396 section,
397 count;
398
399 var packet = new Packet(socket);
400
401 pos = 0;
402 state = 'HEADER';
403
404 msg = BufferCursor(msg);
405 len = msg.length;
406
407 while (true) {
408 switch (state) {
409 case 'HEADER':
410 packet.header.id = msg.readUInt16BE();
411 val = msg.readUInt16BE();
412 packet.header.qr = (val & 0x8000) >> 15;
413 packet.header.opcode = (val & 0x7800) >> 11;
414 packet.header.aa = (val & 0x400) >> 10;
415 packet.header.tc = (val & 0x200) >> 9;
416 packet.header.rd = (val & 0x100) >> 8;
417 packet.header.ra = (val & 0x80) >> 7;
418 packet.header.res1 = (val & 0x40) >> 6;
419 packet.header.res2 = (val & 0x20) >> 5;
420 packet.header.res3 = (val & 0x10) >> 4;
421 packet.header.rcode = (val & 0xF);
422 counts.qdcount = msg.readUInt16BE();
423 counts.ancount = msg.readUInt16BE();
424 counts.nscount = msg.readUInt16BE();
425 counts.arcount = msg.readUInt16BE();
426 state = 'QUESTION';
427 break;
428 case 'QUESTION':
429 val = {};
430 val.name = name_unpack(msg, label_index);
431 val.type = msg.readUInt16BE();
432 val.class = msg.readUInt16BE();
433 packet.question.push(val);
434 // TODO handle qdcount > 0 in practice no one sends this
435 state = 'RESOURCE_RECORD';
436 section = 'answer';
437 count = 'ancount';
438 break;
439 case 'RESOURCE_RECORD':
440 if (counts[count] === packet[section].length) {
441 switch (section) {
442 case 'answer':
443 section = 'authority';
444 count = 'nscount';
445 break;
446 case 'authority':
447 section = 'additional';
448 count = 'arcount';
449 break;
450 case 'additional':
451 state = 'END';
452 break;
453 }
454 } else {
455 state = 'RR_UNPACK';
456 }
457 break;
458 case 'RR_UNPACK':
459 val = {};
460 val.name = name_unpack(msg, label_index);
461 val.type = msg.readUInt16BE();
462 val.class = msg.readUInt16BE();
463 val.ttl = msg.readUInt32BE();
464 rdata_len = msg.readUInt16BE();
465 rdata = msg.slice(rdata_len);
466 state = consts.QTYPE_TO_NAME[val.type];
467 break;
468 case 'RESOURCE_DONE':
469 packet[section].push(val);
470 state = 'RESOURCE_RECORD';
471 break;
472 case 'A':
473 val.address = new ipaddr.IPv4(rdata.toByteArray());
474 val.address = val.address.toString();
475 state = 'RESOURCE_DONE';
476 break;
477 case 'AAAA':
478 val.address = new ipaddr.IPv6(rdata.toByteArray('readUInt16BE'));
479 val.address = val.address.toString();
480 state = 'RESOURCE_DONE';
481 break;
482 case 'NS':
483 case 'CNAME':
484 case 'PTR':
485 pos = msg.tell();
486 msg.seek(pos - rdata_len);
487 val.data = name_unpack(msg, label_index);
488 msg.seek(pos);
489 state = 'RESOURCE_DONE';
490 break;
491 case 'TXT':
492 val.data = '';
493 while (!rdata.eof()) {
494 val.data += rdata.toString('ascii', rdata.readUInt8());
495 }
496 state = 'RESOURCE_DONE';
497 break;
498 case 'MX':
499 val.priority = rdata.readUInt16BE();
500 pos = msg.tell();
501 msg.seek(pos - rdata_len + rdata.tell());
502 val.exchange = name_unpack(msg, label_index);
503 msg.seek(pos);
504 state = 'RESOURCE_DONE';
505 break;
506 case 'SRV':
507 val.priority = rdata.readUInt16BE();
508 val.weight = rdata.readUInt16BE();
509 val.port = rdata.readUInt16BE();
510 pos = msg.tell();
511 msg.seek(pos - rdata_len + rdata.tell());
512 val.target = name_unpack(msg, label_index);
513 msg.seek(pos);
514 state = 'RESOURCE_DONE';
515 break;
516 case 'SOA':
517 pos = msg.tell();
518 msg.seek(pos - rdata_len + rdata.tell());
519 val.primary = name_unpack(msg, label_index);
520 val.admin = name_unpack(msg, label_index);
521 rdata.seek(msg.tell() - (pos - rdata_len + rdata.tell()));
522 msg.seek(pos);
523 val.serial = rdata.readUInt32BE();
524 val.refresh = rdata.readInt32BE();
525 val.retry = rdata.readInt32BE();
526 val.expiration = rdata.readInt32BE();
527 val.minimum = rdata.readInt32BE();
528 state = 'RESOURCE_DONE';
529 break;
530 case 'OPT':
531 // assert first entry in additional
532 counts[count] -= 1;
533 packet.payload = val.class;
534 pos = msg.tell();
535 msg.seek(pos - 6);
536 packet.header.rcode = (msg.readUInt8() << 4) + packet.header.rcode;
537 packet.edns_version = msg.readUInt8();
538 val = msg.readUInt16BE();
539 msg.seek(pos);
540 packet.do = (val & 0x8000) << 15;
541 while (!rdata.eof()) {
542 packet.edns_options.push({
543 code: rdata.readUInt16BE(),
544 data: rdata.slice(rdata.readUInt16BE()).buffer
545 });
546 }
547 state = 'RESOURCE_RECORD';
548 break;
549 case 'NAPTR':
550 val.order = rdata.readUInt16BE();
551 val.preference = rdata.readUInt16BE();
552 pos = rdata.readUInt8();
553 val.flags = rdata.toString('ascii', pos);
554 pos = rdata.readUInt8();
555 val.service = rdata.toString('ascii', pos);
556 pos = rdata.readUInt8();
557 val.regexp = rdata.toString('ascii', pos);
558 pos = rdata.readUInt8();
559 val.replacement = rdata.toString('ascii', pos);
560 state = 'RESOURCE_DONE';
561 break;
562 case 'END':
563 return packet;
564 break;
565 default:
566 //console.log(state, val);
567 state = 'RESOURCE_DONE';
568 break;
569 }
570 }
571};