git ssb

0+

ansuz / modern-dnsd



Tree: 012e32d5ba7c8cc7ea4d15234b2715ffc1f0e154

Files: 012e32d5ba7c8cc7ea4d15234b2715ffc1f0e154 / message.js

9791 bytesRaw
1// Copyright 2012 Iris Couch, all rights reserved.
2//
3// Test displaying DNS records
4
5var util = require('util')
6
7var parse = require('./parse')
8var encode = require('./encode')
9var constants = require('./constants')
10
11module.exports = DNSMessage
12
13var SECTIONS = ['question', 'answer', 'authority', 'additional']
14
15// A DNS message. This is an easy-to-understand object representation of
16// standard DNS queries and responses.
17//
18// Attributes:
19// * id - a number representing the unique query ID
20// * type - "request" or "response"
21// * response - Number (server response code)
22// * opcode - "query", "iquery", "status", "unassigned", "notify", "update"
23// * authoritative - Boolean
24// * truncated - Boolean
25// * recursion_desired - Boolean
26// * recursion_available - Boolean
27// * authenticated - Boolean
28// * checking_disabled - Boolean
29//
30// Optional attributes:
31// * question (optional) - Array of the question section
32// * answer (optional) - Array of the answer section
33// * authority (optional) - Array of the authority section
34// * additional (optional) - Array of the additional section
35//
36// Methods:
37// * toString() - return a human-readable representation of this message
38// * toJSON() - Return a JSON-friendly represenation of this message
39// * toBinary() - Return a buffer of the encoded message
40function DNSMessage (body) {
41 var self = this
42
43 this.id = null
44 this.type = null
45 this.responseCode = null
46 this.opcode = null
47 this.authoritative = null
48 this.truncated = null
49 this.recursion_desired = null
50 this.recursion_available = null
51 this.authenticated = null
52 this.checking_disabled = null
53
54 if(Buffer.isBuffer(body))
55 this.parse(body)
56 else if(typeof body != 'object')
57 throw new Error('Must provide a buffer or object argument with message contents')
58 else {
59 Object.keys(body).forEach(function(key) { self[key] = body[key] })
60 SECTIONS.forEach(function(section) {
61 if(self[section])
62 self[section].forEach(function(record, i) {
63 self[section][i] = new DNSRecord(record)
64 })
65 })
66 }
67
68 // EDNS processing. For now, just remove those records.
69 SECTIONS.forEach(function(section) {
70 if(self[section]) {
71 self[section] = self[section].filter(function(record) { return ! record.edns })
72 if(self[section].length == 0)
73 delete self[section]
74 }
75 })
76}
77
78DNSMessage.prototype.parse = function(body) {
79 var self = this
80
81 self.id = parse.id(body)
82
83 var qr = parse.qr(body)
84 self.type = (qr == 0) ? 'request' : 'response'
85
86 self.responseCode = parse.rcode(body)
87
88 var opcode_names = ['query', 'iquery', 'status', null, 'notify', 'update']
89 var opcode = parse.opcode(body)
90 self.opcode = opcode_names[opcode] || null
91
92 self.authoritative = !! parse.aa(body)
93 self.truncated = !! parse.tc(body)
94 self.recursion_desired = !! parse.rd(body)
95 self.recursion_available = !! parse.ra(body)
96 self.authenticated = !! parse.ad(body)
97 self.checking_disabled = !! parse.cd(body)
98
99 var sections_cache = parse.sections(body)
100
101 SECTIONS.forEach(function(section) {
102 var count = parse.record_count(body, section)
103 if(count) {
104 self[section] = []
105 for(var i = 0; i < count; i++)
106 self[section].push(new DNSRecord(body, section, i, sections_cache))
107 }
108 })
109}
110
111DNSMessage.prototype.toBinary = function() {
112 // The encoder is picky, so make sure it gets a valid message.
113 var msg = JSON.parse(JSON.stringify(this))
114
115 SECTIONS.forEach(function(section) {
116 if(section == 'question')
117 return
118
119 msg[section] = msg[section] || []
120 msg[section].forEach(function(record) {
121 if(record.class != 'IN')
122 return
123
124 // Make sure records promising data have data.
125 if(record.class == 'IN' && record.type == 'A')
126 record.data = record.data || '0.0.0.0'
127
128 // Convert SOA email addresses back to the dotted notation.
129 if(record.class == 'IN' && record.type == 'SOA')
130 record.data.rname = record.data.rname.replace(/@/g, '.')
131
132 // Normalize TXT records.
133 if(record.type == 'TXT' && typeof record.data == 'string')
134 record.data = [record.data]
135 })
136 })
137
138 var state = new encode.State
139 state.message(msg)
140 return state.toBinary()
141}
142
143DNSMessage.prototype.toString = function() {
144 var self = this
145
146 var info = [ util.format('ID : %d', self.id)
147 , util.format("Type : %s", self.type)
148 , util.format("Opcode : %s", self.opcode)
149 , util.format("Authoritative : %s", self.authoritative)
150 , util.format("Truncated : %s", self.truncated)
151 , util.format("Recursion Desired : %s", self.recursion_desired)
152 , util.format("Recursion Available: %s", self.recursion_available)
153 , util.format("Response Code : %d", self.responseCode)
154 ]
155
156 SECTIONS.forEach(function(section) {
157 if(self[section]) {
158 info.push(util.format(';; %s SECTION:', section.toUpperCase()))
159 self[section].forEach(function(record) {
160 info.push(record.toString())
161 })
162 }
163 })
164
165 return info.join('\n')
166}
167
168
169// An individual record from a DNS message
170//
171// Attributes:
172// * name - Host name
173// * type - Query type ('A', 'NS', 'CNAME', etc. or 'Unknown')
174// * class - Network class ('IN', 'None' 'Unknown')
175// * ttl - Time to live for the data in the record
176// * data - The record data value, or null if not applicable
177function DNSRecord (body, section_name, record_num, sections_cache) {
178 var self = this
179
180 this.name = null
181 this.type = null
182 this.class = null
183
184 // Leave these undefined for more consice and clear JSON serialization.
185 //this.ttl = null
186 //this.data = null
187
188 if(Buffer.isBuffer(body))
189 this.parse(body, section_name, record_num, sections_cache || body)
190 else if(typeof body != 'object')
191 throw new Error('Must provide a buffer or object argument with message contents')
192 else
193 Object.keys(body).forEach(function(key) { self[key] = body[key] })
194}
195
196DNSRecord.prototype.parse = function(body, section_name, record_num, sections) {
197 var self = this
198
199 self.name = parse.record_name(sections, section_name, record_num)
200
201 var type = parse.record_type(sections, section_name, record_num)
202 self.type = constants.type_to_label(type)
203 if(! self.type)
204 throw new Error('Record '+record_num+' in section "'+section_name+'" has unknown type: ' + type)
205
206 if(section_name != 'additional' || self.type != 'OPT' || self.name != '') {
207 // Normal record
208 var clas = parse.record_class(sections, section_name, record_num)
209 self.class = constants.class_to_label(clas)
210 if(! self.class)
211 throw new Error('Record '+record_num+' in section "'+section_name+'" has unknown class: ' + type)
212
213 if(section_name == 'question')
214 return
215 else
216 self.ttl = parse.record_ttl(sections, section_name, record_num)
217 } else {
218 // EDNS record
219 self.edns = true
220 delete self.name
221 delete self.class
222 //self.edns = parse.record_edns(sections, section_name, record_num)
223 }
224
225 var rdata = parse.record_data(sections, section_name, record_num)
226 switch (self.kind()) {
227 case 'IN A':
228 if(rdata.length != 4)
229 throw new Error('Bad IN A data: ' + JSON.stringify(self))
230 self.data = inet_ntoa(rdata)
231 break
232 case 'IN AAAA':
233 if(rdata.length != 16)
234 throw new Error('Bad IN AAAA data: ' + JSON.stringify(self))
235 self.data = inet_ntoa6(rdata)
236 break
237 case 'IN NS':
238 case 'IN CNAME':
239 case 'IN PTR':
240 self.data = parse.uncompress(body, rdata)
241 break
242 case 'IN TXT':
243 self.data = parse.txt(body, rdata)
244 if(self.data.length === 0)
245 self.data = ''
246 else if(self.data.length === 1)
247 self.data = self.data[0]
248 break
249 case 'IN MX':
250 self.data = parse.mx(body, rdata)
251 break
252 case 'IN SRV':
253 self.data = parse.srv(body, rdata)
254 break
255 case 'IN SOA':
256 self.data = parse.soa(body, rdata)
257 self.data.rname = self.data.rname.replace(/\./, '@')
258 break
259 case 'IN DS':
260 self.data = { 'key_tag' : rdata[0] << 8 | rdata[1]
261 , 'algorithm' : rdata[2]
262 , 'digest_type': rdata[3]
263 , 'digest' : rdata.slice(4).toJSON() // Convert to a list of numbers.
264 }
265 break
266 case 'IN SSHFP':
267 self.data = parse.sshfp(body, rdata)
268 break
269 case 'NONE A':
270 self.data = []
271 break
272 case 'EDNS':
273 self.data = rdata
274 break
275 default:
276 throw new Error('Unknown record '+self.kind()+': ' + JSON.stringify(self))
277 }
278}
279
280DNSRecord.prototype.kind = function() {
281 return this.edns
282 ? 'EDNS'
283 : this.class + ' ' + this.type
284}
285
286DNSRecord.prototype.toString = function() {
287 var self = this
288 return [ width(23, self.name)
289 , width( 7, self.ttl || '')
290 , width( 7, self.class)
291 , width( 7, self.type)
292 , self.type == 'MX' && self.data
293 ? (width(3, self.data[0]) + ' ' + self.data[1])
294 : Buffer.isBuffer(self.data)
295 ? self.data.toString('hex')
296 : self.data || ''
297 ].join(' ')
298}
299
300//
301// Utilities
302//
303
304function width(str_len, str) {
305 str = '' + str
306 do {
307 var needed = str_len - str.length
308 if(needed > 0)
309 str = ' ' + str
310 } while(needed > 0)
311
312 return str
313}
314
315function inet_ntoa(buf) {
316 return buf[0] + '.' + buf[1] + '.' + buf[2] + '.' + buf[3]
317}
318
319function inet_ntoa6(buf) {
320 var result = []
321 for(var i = 0; i < 16; i += 2)
322 result.push(buf.slice(i, i+2).toString('hex'))
323 return result.join(':')
324}
325

Built with git-ssb-web