git ssb

0+

ansuz / modern-dnsd



Tree: 012e32d5ba7c8cc7ea4d15234b2715ffc1f0e154

Files: 012e32d5ba7c8cc7ea4d15234b2715ffc1f0e154 / parse.js

7806 bytesRaw
1// Copyright 2012 Iris Couch, all rights reserved.
2//
3// Parse DNS messages
4
5var util = require('util')
6
7var constants = require('./constants')
8
9module.exports = { 'id': id
10 , 'qr': qr
11 , 'aa': aa
12 , 'tc': tc
13 , 'rd': rd
14 , 'ra': ra
15 , 'ad': ad
16 , 'cd': cd
17 , 'rcode': rcode
18 , 'opcode': opcode
19 , 'record_count': record_count
20 , 'record_name' : record_name
21 , 'record_class': record_class
22 , 'record_ttl' : record_ttl
23 , 'record_type' : record_type
24 , 'record_data' : record_data
25 , 'uncompress' : uncompress
26 , 'sections' : sections
27 , 'mx': mx
28 , 'srv': srv
29 , 'soa': soa
30 , 'txt': txt
31 , 'sshfp': sshfp
32 }
33
34
35function id(msg) {
36 return msg.readUInt16BE(0)
37}
38
39function qr(msg) {
40 return msg.readUInt8(2) >> 7
41}
42
43function opcode(msg) {
44 return (msg.readUInt8(2) >> 3) & 0x0f
45}
46
47function aa(msg) {
48 return (msg.readUInt8(2) >> 2) & 0x01
49}
50
51function tc(msg) {
52 return (msg.readUInt8(2) >> 1) & 0x01
53}
54
55function rd(msg) {
56 return msg.readUInt8(2) & 0x01
57}
58
59function ra(msg) {
60 return msg.readUInt8(3) >> 7
61}
62
63function ad(msg) {
64 return msg.readUInt8(3) >> 5 & 0x01
65}
66
67function cd(msg) {
68 return msg.readUInt8(3) >> 4 & 0x01
69}
70
71function rcode(msg) {
72 return msg.readUInt8(3) & 0x0f
73}
74
75function record_count(msg, name) {
76 if(name == 'question')
77 return msg.readUInt16BE(4)
78 else if(name == 'answer')
79 return msg.readUInt16BE(6)
80 else if(name == 'authority')
81 return msg.readUInt16BE(8)
82 else if(name == 'additional')
83 return msg.readUInt16BE(10)
84 else
85 throw new Error('Unknown section name: ' + name)
86}
87
88function record_name(msg, section_name, offset) {
89 var rec = record(msg, section_name, offset)
90 return rec.name
91}
92
93function record_class(msg, section_name, offset) {
94 var rec = record(msg, section_name, offset)
95 return rec.class
96}
97
98function record_type(msg, section_name, offset) {
99 var rec = record(msg, section_name, offset)
100 return rec.type
101}
102
103function record_ttl(msg, section_name, offset) {
104 var rec = record(msg, section_name, offset)
105 return rec.ttl
106}
107
108function record_data(msg, section_name, offset) {
109 var rec = record(msg, section_name, offset)
110 return rec.data
111}
112
113function record_class(msg, section_name, offset) {
114 var rec = record(msg, section_name, offset)
115 return rec.class
116}
117
118function record(msg, section_name, offset) {
119 if(typeof offset != 'number' || isNaN(offset) || offset < 0)
120 throw new Error('Offset must be a natural number')
121
122 // Support msg being a previously-parsed sections object.
123 var sects = Buffer.isBuffer(msg)
124 ? sections(msg)
125 : msg
126
127 var records = sects[section_name]
128 if(!records)
129 throw new Error('No such section: "'+section_name+'"')
130
131 var rec = records[offset]
132 if(!rec)
133 throw new Error('Bad offset for section "'+section_name+'": ' + offset)
134
135 return rec
136}
137
138function sections(msg) {
139 // Count the times this message has been parsed, for debugging and testing purposes.
140 if('__parsed' in msg)
141 msg.__parsed += 1
142
143 var position = 12 // First byte of the first section
144 , result = {'question':[], 'answer':[], 'authority':[], 'additional':[]}
145 , need = { 'question' : record_count(msg, 'question')
146 , 'answer' : record_count(msg, 'answer')
147 , 'authority' : record_count(msg, 'authority')
148 , 'additional': record_count(msg, 'additional')
149 }
150
151 var states = ['question', 'answer', 'authority', 'additional', 'done']
152 , state = states.shift()
153
154 while(true) {
155 if(state == 'done')
156 return result
157 else if(result[state].length == need[state])
158 state = states.shift()
159 else if(!state)
160 throw new Error('Unknown parsing state at position '+position+': '+JSON.stringify(state))
161 else
162 add_record()
163 }
164
165 function add_record() {
166 var record = {}
167
168 var data = domain_parts(msg, position)
169 record.name = data.parts.join('.')
170 position += data.length
171
172 record.type = msg.readUInt16BE(position + 0)
173 record.class = msg.readUInt16BE(position + 2)
174 position += 4
175
176 if(state != 'question') {
177 record.ttl = msg.readUInt32BE(position + 0)
178 var rdata_len = msg.readUInt16BE(position + 4)
179
180 position += 6
181 record.data = msg.slice(position, position + rdata_len)
182
183 position += rdata_len
184
185 if(constants.type(record.type) === 'OPT') {
186 // EDNS
187 if(record.name !== '')
188 throw new Error('EDNS record option for non-root domain: ' + record.name)
189
190 record.udp_size = record.class
191 delete record.class
192
193 record.extended_rcode = (record.ttl >> 24)
194 record.edns_version = (record.ttl >> 16) & 0xff
195 record.zero = (record.ttl >> 8)
196 delete record.ttl
197
198 record.data = Array.prototype.slice.call(record.data)
199 }
200 }
201
202 result[state] = result[state] || []
203 result[state].push(record)
204 }
205}
206
207function mx(msg, data) {
208 return [ data.readUInt16BE(0)
209 , uncompress(msg, data.slice(2))
210 ]
211}
212
213function srv(msg, data) {
214 return { 'priority': data.readUInt16BE(0)
215 , 'weight' : data.readUInt16BE(2)
216 , 'port' : data.readUInt16BE(4)
217 , 'target' : uncompress(msg, data.slice(6)) // Techncially compression is not allowed in RFC 2782.
218 }
219}
220
221function soa(msg, data) {
222 var result = domain_parts(msg, data)
223 , offset = result.length
224 , mname = result.parts.join('.')
225
226 result = domain_parts(msg, data.slice(offset))
227 var rname = result.parts.join('.')
228 offset += result.length
229
230 return { 'mname' : mname
231 , 'rname' : rname //.replace(/\./, '@')
232 , 'serial' : data.readUInt32BE(offset + 0)
233 , 'refresh': data.readUInt32BE(offset + 4)
234 , 'retry' : data.readUInt32BE(offset + 8)
235 , 'expire' : data.readUInt32BE(offset + 12)
236 , 'ttl' : data.readUInt32BE(offset + 16)
237 }
238}
239
240function txt(msg, data) {
241 var parts = []
242 while(data.length) {
243 var len = data.readUInt8(0)
244 parts.push(data.slice(1, 1+len).toString('ascii'))
245 data = data.slice(1+len)
246 }
247
248 return parts
249}
250
251function sshfp(msg, data) {
252 return { 'algorithm': data.readUInt8(0)
253 , 'fp_type' : data.readUInt8(1)
254 , 'fingerprint' : data.slice(2)
255 }
256}
257
258function uncompress(msg, offset) {
259 var data = domain_parts(msg, offset)
260 return data.parts.join('.')
261}
262
263function domain_parts(msg, offset) {
264 if(Buffer.isBuffer(offset)) {
265 var full_message = msg
266 msg = offset
267 offset = 0
268 }
269
270 if(typeof offset != 'number' || isNaN(offset) || offset < 0 || offset > msg.length)
271 throw new Error('Bad offset: ' + offset)
272
273 var parts = []
274 , real_length = 0
275 , jumped = false
276
277 var i = 0
278 while(true) {
279 if(++i >= 100)
280 throw new Error('Too many iterations uncompressing name')
281
282 var byte = msg.readUInt8(offset)
283 , flags = byte >> 6
284 , len = byte & 0x3f // 0 - 63
285
286 offset += 1
287 add_length(1)
288
289 if(flags === 0x03) {
290 offset = (len << 8) + msg.readUInt8(offset)
291 add_length(1)
292 jumped = true
293
294 // If processing so far has just been on some given fragment, begin using the full message now.
295 msg = full_message || msg
296 }
297
298 else if(len == 0)
299 return {'parts':parts, 'length':real_length}
300
301 else {
302 parts.push(msg.toString('ascii', offset, offset + len))
303
304 offset += len
305 add_length(len)
306 }
307 }
308
309 function add_length(amount) {
310 if(! jumped)
311 real_length += amount
312 }
313}
314

Built with git-ssb-web