forked from iriscouch/dnsd
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdecode.js
More file actions
383 lines (306 loc) · 11.5 KB
/
Copy pathdecode.js
File metadata and controls
383 lines (306 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
// Copyright 2012 Iris Couch, all rights reserved.
//
// Parse DNS messages
"use strict";
const { SECTIONS, typeToLabel } = require( "./constants" );
/**
* Extracts ID from DNS message provided as sequence of octets in wire format.
*
* @param {Buffer} msg encoded DNS message
* @returns {number} extracted ID of DNS message
*/
exports.id = msg => msg.readUInt16BE( 0 );
exports.qr = msg => msg.readUInt8( 2 ) >> 7;
exports.opcode = msg => ( msg.readUInt8( 2 ) >> 3 ) & 0x0f;
exports.aa = msg => ( msg.readUInt8( 2 ) >> 2 ) & 0x01;
exports.tc = msg => ( msg.readUInt8( 2 ) >> 1 ) & 0x01;
exports.rd = msg => msg.readUInt8( 2 ) & 0x01;
exports.ra = msg => msg.readUInt8( 3 ) >> 7;
exports.ad = msg => ( msg.readUInt8( 3 ) >> 5 ) & 0x01;
exports.cd = msg => ( msg.readUInt8( 3 ) >> 4 ) & 0x01;
exports.rcode = msg => msg.readUInt8( 3 ) & 0x0f;
/**
* Extracts record selected by its containing section of message and its index
* inside that section from provided DNS message.
*
* @param {Buffer|ParsedSectionsData} msg sequence of octets describing full DNS message or previously extracted sections data
* @param {string} sectionName name of section to extract
* @param {number} offset index of record in selected section to extract
* @returns {object} selected record
*/
exports.getRecord = ( msg, sectionName, offset ) => {
if ( typeof offset !== "number" || isNaN( offset ) || offset < 0 )
throw new Error( "Offset must be a natural number" );
const sections = Buffer.isBuffer( msg ) ? exports.sections( msg ) : msg;
const records = sections[sectionName];
if ( !records )
throw new Error( `No such section: "${sectionName}"` );
const record = records[offset];
if ( !record )
throw new Error( `Bad offset for section "${sectionName}": ${offset}` );
return record;
};
exports.recordCount = ( msg, name ) => {
if ( name === "question" )
return msg.readUInt16BE( 4 );
if ( name === "answer" )
return msg.readUInt16BE( 6 );
if ( name === "authority" )
return msg.readUInt16BE( 8 );
if ( name === "additional" )
return msg.readUInt16BE( 10 );
throw new Error( "Unknown section name: " + name );
};
/**
* Extracts records per section from encoded DNS message.
*
* @param {Buffer} msg sequence of octets describing DNS message in wire format
* @returns {ParsedSectionsData} map of section names into list of records per section
*/
exports.sections = msg => {
if ( msg.hasOwnProperty( "__decoded" ) ) {
msg.__decoded++; // eslint-disable-line no-param-reassign
}
const records = {};
let position = 12; // first byte of the first section
for ( const sectionName of SECTIONS ) {
const numRecords = exports.recordCount( msg, sectionName );
const collector = records[sectionName] = new Array( numRecords );
for ( let i = 0; i < numRecords; i++ ) {
const record = collector[i] = {};
const { segments, length } = compileName( msg, null, position );
const name = segments.join( "." );
position += length;
const typeValue = record.type = msg.readUInt16BE( position );
const classValue = msg.readUInt16BE( position + 2 );
position += 4;
if ( sectionName !== "question" ) {
const ttl = msg.readUInt32BE( position );
const rdataLength = msg.readUInt16BE( position + 4 );
position += 6;
const data = msg.slice( position, position + rdataLength );
position += rdataLength;
if ( typeToLabel( typeValue ) === "OPT" ) {
if ( name !== "" )
throw new Error( `invalid EDNS: name of OPT pseudo-RR must be empty, but found "${record.name}"` );
record.edns = true;
record.udpSize = Math.max( 512, classValue || 0 );
record.extendedResult = ttl >> 24;
record.version = ( ttl >> 16 ) & 0xff;
record.flagDO = Boolean( ttl & 0x8000 );
record.flags = ttl & 0x7FFF;
record.options = extractEDNSOptions( data );
continue;
}
record.ttl = ttl;
record.data = data;
}
record.name = name;
record.class = classValue;
record.type = typeValue;
}
}
return records;
};
/**
* Extracts information from provided record data of a MX RR.
*
* @param {Buffer} msg sequence of octets describing full DNS message in wire format
* @param {Buffer} data part of `msg` describing RDATA of resource record in wire format
* @returns {MXData} custom data of MX resource record
*/
exports.mx = ( msg, data ) => ( {
weight: data.readUInt16BE( 0 ),
name: exports.uncompress( msg, data, 2 ),
} );
/**
* Extracts information from provided record data of a SRV RR.
*
* @param {Buffer} msg sequence of octets describing full DNS message in wire format
* @param {Buffer} data part of `msg` describing RDATA of resource record in wire format
* @returns {SRVData} custom data of SRV resource record
*/
exports.srv = ( msg, data ) => ( {
priority: data.readUInt16BE( 0 ),
weight: data.readUInt16BE( 2 ),
port: data.readUInt16BE( 4 ),
target: exports.uncompress( msg, data, 6 ),
} );
/**
* Extracts information from provided record data of a SOA RR.
*
* @param {Buffer} msg sequence of octets describing full DNS message in wire format
* @param {Buffer} data part of `msg` describing RDATA of resource record in wire format
* @returns {SOAData} custom data of SOA resource record
*/
exports.soa = ( msg, data ) => {
const mname = compileName( msg, data );
const rname = compileName( msg, data, mname.length );
const offset = mname.length + rname.length;
return {
mname: mname.segments.join( "." ),
rname: rname.segments.join( "." ),
serial: data.readUInt32BE( offset ),
refresh: data.readUInt32BE( offset + 4 ),
retry: data.readUInt32BE( offset + 8 ),
expire: data.readUInt32BE( offset + 12 ),
ttl: data.readUInt32BE( offset + 16 ),
};
};
/**
* Extracts data of TXT resource record.
*
* @param {Buffer} msg sequence of octets describing full DNS message in wire format
* @param {Buffer} data part of `msg` describing RDATA of resource record in wire format
* @returns {string[]} lists segments of ASCII text found in TXT record data
*/
exports.txt = ( msg, data ) => {
const parts = [];
for ( let iter = data; iter.length; ) {
const len = iter.readUInt8( 0 );
parts.push( iter.slice( 1, 1 + len ).toString( "ascii" ) );
iter = iter.slice( 1 + len );
}
return parts;
};
/**
* Extracts domain name from DNS message at given offset.
*
* @param {Buffer} msg sequence of octets describing full DNS message in wire format
* @param {Buffer} sub part of `msg` containing name to extract
* @param {int} offset 0-based index into `sub` (or `msg` if `sub` is omitted) addressing octet to start extraction at
* @returns {string} extracted domain name
*/
exports.uncompress = ( msg, sub = null, offset = 0 ) => compileName( msg, sub, offset ).segments.join( "." );
/**
* Extracts variable-length name from provided (slice of a) DNS message handling
* probably used pointers for compressing DNS message.
*
* @see https://tools.ietf.org/html/rfc1035#section-4.1.4
*
* @param {Buffer} fullMessage sequence of octets describing whole DNS message in wire format
* @param {Buffer} slice sequence of octets describing segment of that DNS message
* @param {int} offset 0-based index into slice (or fullMessage when omitted) name should be extracted from
* @returns {{segments: string[], length: number}} segments of extracted domain name, number of octets occupied by name up to first encountered pointer
*/
function compileName( fullMessage, slice = null, offset = 0 ) {
if ( typeof offset !== "number" || isNaN( offset ) || offset < 0 || offset > fullMessage.length )
throw new Error( "Bad offset: " + offset );
const segments = [];
const pointersCache = {};
let buffer = slice || fullMessage;
let cursor = offset;
let labelLength = 1;
let numOctets = 1;
let metPointer = false;
while ( labelLength > 0 ) {
const byte = buffer.readUInt8( cursor++ );
switch ( byte & 0xc0 ) {
case 0xc0 : {
// pointer
const pointer = ( ( byte & 0x3f ) << 8 ) + buffer.readUInt8( cursor++ );
if ( pointer >= fullMessage.length ) {
throw new TypeError( "invalid out-of-bounds pointer" );
}
if ( pointersCache[pointer] ) {
throw new TypeError( "circular pointer references discovered" );
}
pointersCache[pointer] = true;
if ( !metPointer ) {
numOctets = cursor - offset;
}
metPointer = true;
buffer = fullMessage;
cursor = pointer;
break;
}
case 0x00 :
// label
labelLength = byte & 0x3f;
if ( labelLength > 0 ) {
segments.push( buffer.toString( "ascii", cursor, cursor + labelLength ) );
cursor += labelLength;
}
break;
default :
throw new Error( `unexpected type of label, maybe data is corrupted` );
}
}
if ( !metPointer ) {
numOctets = cursor - offset;
}
return { segments, length: numOctets };
}
/**
* Extracts EDNS-compliant options from record data of OPT RR.
*
* @param {Buffer} buffer raw record data of OPT RR
* @returns {EDNSOption[]} extracted list of EDNS options
*/
function extractEDNSOptions( buffer ) {
const options = [];
let offset = 0;
while ( offset < buffer.length ) {
const code = buffer.readUInt16BE( offset );
offset += 2;
const length = buffer.readUInt16BE( offset );
offset += 2;
const data = buffer.slice( offset, offset + length );
offset += length;
options.push( { code, data } );
}
return options;
}
/**
* @typedef {object} MXData
* @property {number} weight weight of MX service in comparison to other MX services defined
* @property {string} name domain name of host providing MX service
*/
/**
* @typedef {object} SRVData
* @property {number} priority service priority
* @property {number} weight service weight
* @property {number} port IP port service is listening on
* @property {string} target domain name of host providing described service
*/
/**
* @typedef {object} SOAData
* @property {string} mname domain name of zone
* @property {string} rname encoded mail address of zone administrator
* @property {number} serial serial number indicating latest change of zone
* @property {number} refresh number of seconds for a slave to wait before checking for update of zone again
* @property {number} retry number of seconds for a slave to wait before trying again after failed transfer of zone
* @property {number} expire number of seconds for a slave to wait before considering a zone gone while transfers keep failing
* @property {number} ttl negative TTL for resolvers to wait before querying again in case of general errors in context of zone
*/
/**
* @typedef {object} RawRegularResourceRecord
* @property {string} name name of resource
* @property {number} class class identifier
* @property {number} type type identifier of resource
* @property {number} ttl time to live
* @property {Buffer} data record's binary encoded data (payload)
*/
/**
* Represents information of pseudo-RR of type OPT as defined in RFC 6891.
*
* @typedef {object} RawEDNSResourceRecord
* @property {boolean} edns set true to indicate current record being OPT RR of EDNS
* @property {number} udpSize UDP message size accepted by peer
* @property {number} extendedResult extended result code
* @property {number} version applicable version of EDNS
* @property {boolean} flagDO true if DO flag is set
* @property {number} flags bit set containing additional flags
* @property {EDNSOption[]} options options included with EDNS record
*/
/**
* @typedef {object} EDNSOption
* @property {number} code option code
* @property {Buffer} data raw option data
*/
/**
* @typedef {RawRegularResourceRecord|RawEDNSResourceRecord} RawResourceRecord
*/
/**
* @typedef {object<string,RawResourceRecord[]>} ParsedSectionsData
*/