git ssb

0+

ansuz / modern-dnsd



Tree: 012e32d5ba7c8cc7ea4d15234b2715ffc1f0e154

Files: 012e32d5ba7c8cc7ea4d15234b2715ffc1f0e154 / encode.js

8298 bytesRaw
1// Copyright 2012 Iris Couch, all rights reserved.
2//
3// Encode DNS messages
4
5var util = require('util')
6
7var constants = require('./constants')
8
9module.exports = { 'State': State
10 }
11
12var SECTIONS = ['question', 'answer', 'authority', 'additional']
13
14function State () {
15 var self = this
16
17 self.header = new Buffer(12)
18 self.position = 0
19
20 self.question = []
21 self.answer = []
22 self.authority = []
23 self.additional = []
24
25 self.domains = {} // The compression lookup table
26}
27
28State.prototype.toBinary = function() {
29 var self = this
30
31 var bufs = [self.header]
32 self.question .forEach(function(buf) { bufs.push(buf) })
33 self.answer .forEach(function(buf) { bufs.push(buf) })
34 self.authority .forEach(function(buf) { bufs.push(buf) })
35 self.additional.forEach(function(buf) { bufs.push(buf) })
36
37 return Buffer.concat(bufs)
38}
39
40State.prototype.message = function(msg) {
41 var self = this
42
43 // ID
44 self.header.writeUInt16BE(msg.id, 0)
45
46 // QR, opcode, AA, TC, RD
47 var byte = 0
48 byte |= msg.type == 'response' ? 0x80 : 0x00
49 byte |= msg.authoritative ? 0x04 : 0x00
50 byte |= msg.truncated ? 0x02 : 0x00
51 byte |= msg.recursion_desired ? 0x01 : 0x00
52
53 var opcode_names = ['query', 'iquery', 'status', null, 'notify', 'update']
54 , opcode = opcode_names.indexOf(msg.opcode)
55
56 if(opcode == -1 || typeof msg.opcode != 'string')
57 throw new Error('Unknown opcode: ' + msg.opcode)
58 else
59 byte |= (opcode << 3)
60
61 self.header.writeUInt8(byte, 2)
62
63 // RA, Z, AD, CD, Rcode
64 byte = 0
65 byte |= msg.recursion_available ? 0x80 : 0x00
66 byte |= msg.authenticated ? 0x20 : 0x00
67 byte |= msg.checking_disabled ? 0x10 : 0x00
68 byte |= (msg.responseCode & 0x0f)
69
70 self.header.writeUInt8(byte, 3)
71
72 self.position = 12 // the beginning of the sections
73 SECTIONS.forEach(function(section) {
74 var records = msg[section] || []
75 records.forEach(function(rec) {
76 self.record(section, rec)
77 })
78 })
79
80 // Write the section counts.
81 self.header.writeUInt16BE(self.question.length , 4)
82 self.header.writeUInt16BE(self.answer.length , 6)
83 self.header.writeUInt16BE(self.authority.length , 8)
84 self.header.writeUInt16BE(self.additional.length , 10)
85}
86
87State.prototype.record = function(section_name, record) {
88 var self = this
89
90 var body = []
91 , buf
92
93 // Write the record name.
94 buf = self.encode(record.name)
95 body.push(buf)
96 self.position += buf.length
97
98 var type = constants.type_to_number(record.type)
99 , clas = constants.class_to_number(record.class)
100
101 // Write the type.
102 buf = new Buffer(2)
103 buf.writeUInt16BE(type, 0)
104 body.push(buf)
105 self.position += 2
106
107 // Write the class.
108 buf = new Buffer(2)
109 buf.writeUInt16BE(clas, 0)
110 body.push(buf)
111 self.position += 2
112
113 if(section_name != 'question') {
114 // Write the TTL.
115 buf = new Buffer(4)
116 buf.writeUInt32BE(record.ttl || 0, 0)
117 body.push(buf)
118 self.position += 4
119
120 // Write the rdata. Update the position now (the rdata length value) in case self.encode() runs.
121 var match, rdata
122 switch (record.class + ' ' + record.type) {
123 case 'IN A':
124 rdata = record.data || ''
125 match = rdata.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/)
126 if(!match)
127 throw new Error('Bad '+record.type+' record data: ' + JSON.stringify(record))
128 rdata = [ +match[1], +match[2], +match[3], +match[4] ]
129 break
130 case 'IN AAAA':
131 rdata = (record.data || '').split(/:/)
132 if(rdata.length != 8)
133 throw new Error('Bad '+record.type+' record data: ' + JSON.stringify(record))
134 rdata = rdata.map(pair_to_buf)
135 break
136 case 'IN MX':
137 var host = record.data[1]
138 rdata = [ buf16(record.data[0])
139 , self.encode(host, 2 + 2) // Adjust for the rdata length + preference values.
140 ]
141 break
142 case 'IN SOA':
143 var mname = self.encode(record.data.mname, 2) // Adust for rdata length
144 , rname = self.encode(record.data.rname, 2 + mname.length)
145 rdata = [ mname
146 , rname
147 , buf32(record.data.serial)
148 , buf32(record.data.refresh)
149 , buf32(record.data.retry)
150 , buf32(record.data.expire)
151 , buf32(record.data.ttl)
152 ]
153 break
154 case 'IN NS':
155 case 'IN PTR':
156 case 'IN CNAME':
157 rdata = self.encode(record.data, 2) // Adjust for the rdata length
158 break
159 case 'IN TXT':
160 rdata = record.data.map(function(part) {
161 part = new Buffer(part)
162 return [part.length, part]
163 })
164 break
165 case 'IN SRV':
166 rdata = [ buf16(record.data.priority)
167 , buf16(record.data.weight)
168 , buf16(record.data.port)
169 , self.encode(record.data.target, 2 + 6, 'nocompress') // Offset for rdata length + priority, weight, and port.
170 ]
171 break
172 case 'IN DS':
173 rdata = [ buf16(record.data.key_tag)
174 , new Buffer([record.data.algorithm])
175 , new Buffer([record.data.digest_type])
176 , new Buffer(record.data.digest)
177 ]
178 break
179 case 'IN SSHFP':
180 rdata = [ new Buffer([record.algorithm])
181 , new Buffer([record.fp_type])
182 , new Buffer(record.fingerprint)
183 ]
184 break
185 case 'NONE A':
186 // I think this is no data, from RFC 2136 S. 2.4.3.
187 rdata = []
188 break
189 default:
190 throw new Error('Unsupported record type: ' + JSON.stringify(record))
191 }
192
193 // Write the rdata length. (The position was already updated.)
194 rdata = flat(rdata)
195 buf = new Buffer(2)
196 buf.writeUInt16BE(rdata.length, 0)
197 body.push(buf)
198 self.position += 2
199
200 // Write the rdata.
201 self.position += rdata.length
202 if(rdata.length > 0)
203 body.push(new Buffer(rdata))
204 }
205
206 self[section_name].push(Buffer.concat(body))
207}
208
209State.prototype.encode = function(full_domain, position_offset, option) {
210 var self = this
211
212 var domain = full_domain
213 domain = domain.replace(/\.$/, '') // Strip the trailing dot.
214 position = self.position + (position_offset || 0)
215
216 var body = []
217 , bytes
218
219 var i = 0
220 var max_iterations = 127 // Theoretical upper limit for number of labels in a DNS name; see https://en.wikipedia.org/wiki/Subdomain#Overview
221
222 while(++i < max_iterations) {
223 if(domain == '') {
224 // Encode the root domain and be done.
225 body.push(new Buffer([0]))
226 return Buffer.concat(body)
227 }
228
229 /* FIXME
230 else if(self.domains[domain] && option !== 'nocompress') {
231 // Encode a pointer and be done.
232 body.push(new Buffer([0xc0, self.domains[domain]]))
233 return Buffer.concat(body)
234 }
235 */
236
237 else {
238 // Encode the next part of the domain, saving its position in the lookup table for later.
239 self.domains[domain] = position
240
241 var parts = domain.split(/\./)
242 , car = parts[0]
243 domain = parts.slice(1).join('.')
244
245 // Write the first part of the domain, with a length prefix.
246 //var part = parts[0]
247 var buf = new Buffer(car.length + 1)
248 buf.write(car, 1, car.length, 'ascii')
249 buf.writeUInt8(car.length, 0)
250 body.push(buf)
251 position += buf.length
252 //bytes.unshift(bytes.length)
253 }
254 }
255
256 throw new Error('Too many iterations encoding domain: ' + full_domain)
257}
258
259
260//
261// Utilities
262//
263
264function buf32(value) {
265 var buf = new Buffer(4)
266 buf.writeUInt32BE(value, 0)
267 return buf
268}
269
270function buf16(value) {
271 var buf = new Buffer(2)
272 buf.writeUInt16BE(value, 0)
273 return buf
274}
275
276function flat(data) {
277 return Buffer.isBuffer(data)
278 ? Array.prototype.slice.call(data)
279 : Array.isArray(data)
280 ? data.reduce(flatten, [])
281 : [data]
282}
283
284function flatten(state, element) {
285 return (Buffer.isBuffer(element) || Array.isArray(element))
286 ? state.concat(flat(element))
287 : state.concat([element])
288}
289
290function pair_to_buf(pair) {
291 // Convert a string of two hex bytes, e.g. "89ab" to a buffer.
292 if(! pair.match(/^[0-9a-fA-F]{4}$/))
293 throw new Error('Bad '+record.type+' record data: ' + JSON.stringify(record))
294 return new Buffer(pair, 'hex')
295}
296

Built with git-ssb-web