| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d9273ed commit 0418a70
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,290 @@ | |||
| 1 | + /* eslint-disable required-modules */ | ||
| 2 | + 'use strict'; | ||
| 3 | + | ||
| 4 | + // Naïve DNS parser/serializer. | ||
| 5 | + | ||
| 6 | + const assert = require('assert'); | ||
| 7 | + const os = require('os'); | ||
| 8 | + | ||
| 9 | + const types = { | ||
| 10 | + A: 1, | ||
| 11 | + AAAA: 28, | ||
| 12 | + NS: 2, | ||
| 13 | + CNAME: 5, | ||
| 14 | + SOA: 6, | ||
| 15 | + PTR: 12, | ||
| 16 | + MX: 15, | ||
| 17 | + TXT: 16, | ||
| 18 | + ANY: 255 | ||
| 19 | + }; | ||
| 20 | + | ||
| 21 | + const classes = { | ||
| 22 | + IN: 1 | ||
| 23 | + }; | ||
| 24 | + | ||
| 25 | + function readDomainFromPacket(buffer, offset) { | ||
| 26 | + assert.ok(offset < buffer.length); | ||
| 27 | + const length = buffer[offset]; | ||
| 28 | + if (length === 0) { | ||
| 29 | + return { nread: 1, domain: '' }; | ||
| 30 | + } else if ((length & 0xC0) === 0) { | ||
| 31 | + offset += 1; | ||
| 32 | + const chunk = buffer.toString('ascii', offset, offset + length); | ||
| 33 | + // Read the rest of the domain. | ||
| 34 | + const { nread, domain } = readDomainFromPacket(buffer, offset + length); | ||
| 35 | + return { | ||
| 36 | + nread: 1 + length + nread, | ||
| 37 | + domain: domain ? `${chunk}.${domain}` : chunk | ||
| 38 | + }; | ||
| 39 | + } else { | ||
| 40 | + // Pointer to another part of the packet. | ||
| 41 | + assert.strictEqual(length & 0xC0, 0xC0); | ||
| 42 | + // eslint-disable-next-line | ||
| 43 | + const pointeeOffset = buffer.readUInt16BE(offset) &~ 0xC000; | ||
| 44 | + return { | ||
| 45 | + nread: 2, | ||
| 46 | + domain: readDomainFromPacket(buffer, pointeeOffset) | ||
| 47 | + }; | ||
| 48 | + } | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + function parseDNSPacket(buffer) { | ||
| 52 | + assert.ok(buffer.length > 12); | ||
| 53 | + | ||
| 54 | + const parsed = { | ||
| 55 | + id: buffer.readUInt16BE(0), | ||
| 56 | + flags: buffer.readUInt16BE(2), | ||
| 57 | + }; | ||
| 58 | + | ||
| 59 | + const counts = [ | ||
| 60 | + ['questions', buffer.readUInt16BE(4)], | ||
| 61 | + ['answers', buffer.readUInt16BE(6)], | ||
| 62 | + ['authorityAnswers', buffer.readUInt16BE(8)], | ||
| 63 | + ['additionalRecords', buffer.readUInt16BE(10)] | ||
| 64 | + ]; | ||
| 65 | + | ||
| 66 | + let offset = 12; | ||
| 67 | + for (const [ sectionName, count ] of counts) { | ||
| 68 | + parsed[sectionName] = []; | ||
| 69 | + for (let i = 0; i < count; ++i) { | ||
| 70 | + const { nread, domain } = readDomainFromPacket(buffer, offset); | ||
| 71 | + offset += nread; | ||
| 72 | + | ||
| 73 | + const type = buffer.readUInt16BE(offset); | ||
| 74 | + | ||
| 75 | + const rr = { | ||
| 76 | + domain, | ||
| 77 | + cls: buffer.readUInt16BE(offset + 2), | ||
| 78 | + }; | ||
| 79 | + offset += 4; | ||
| 80 | + | ||
| 81 | + for (const name in types) { | ||
| 82 | + if (types[name] === type) | ||
| 83 | + rr.type = name; | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + if (sectionName !== 'questions') { | ||
| 87 | + rr.ttl = buffer.readInt32BE(offset); | ||
| 88 | + const dataLength = buffer.readUInt16BE(offset); | ||
| 89 | + offset += 6; | ||
| 90 | + | ||
| 91 | + switch (type) { | ||
| 92 | + case types.A: | ||
| 93 | + assert.strictEqual(dataLength, 4); | ||
| 94 | + rr.address = `${buffer[offset + 0]}.${buffer[offset + 1]}.` + | ||
| 95 | + `${buffer[offset + 2]}.${buffer[offset + 3]}`; | ||
| 96 | + break; | ||
| 97 | + case types.AAAA: | ||
| 98 | + assert.strictEqual(dataLength, 16); | ||
| 99 | + rr.address = buffer.toString('hex', offset, offset + 16) | ||
| 100 | + .replace(/(.{4}(?!$))/g, '$1:'); | ||
| 101 | + break; | ||
| 102 | + case types.TXT: | ||
| 103 | + { | ||
| 104 | + let position = offset; | ||
| 105 | + rr.entries = []; | ||
| 106 | + while (position < offset + dataLength) { | ||
| 107 | + const txtLength = buffer[offset]; | ||
| 108 | + rr.entries.push(buffer.toString('utf8', | ||
| 109 | + position + 1, | ||
| 110 | + position + 1 + txtLength)); | ||
| 111 | + position += 1 + txtLength; | ||
| 112 | + } | ||
| 113 | + assert.strictEqual(position, offset + dataLength); | ||
| 114 | + break; | ||
| 115 | + } | ||
| 116 | + case types.MX: | ||
| 117 | + { | ||
| 118 | + rr.priority = buffer.readInt16BE(buffer, offset); | ||
| 119 | + offset += 2; | ||
| 120 | + const { nread, domain } = readDomainFromPacket(buffer, offset); | ||
| 121 | + rr.exchange = domain; | ||
| 122 | + assert.strictEqual(nread, dataLength); | ||
| 123 | + break; | ||
| 124 | + } | ||
| 125 | + case types.NS: | ||
| 126 | + case types.CNAME: | ||
| 127 | + case types.PTR: | ||
| 128 | + { | ||
| 129 | + const { nread, domain } = readDomainFromPacket(buffer, offset); | ||
| 130 | + rr.value = domain; | ||
| 131 | + assert.strictEqual(nread, dataLength); | ||
| 132 | + break; | ||
| 133 | + } | ||
| 134 | + case types.SOA: | ||
| 135 | + { | ||
| 136 | + const mname = readDomainFromPacket(buffer, offset); | ||
| 137 | + const rname = readDomainFromPacket(buffer, offset + mname.nread); | ||
| 138 | + rr.nsname = mname.domain; | ||
| 139 | + rr.hostmaster = rname.domain; | ||
| 140 | + const trailerOffset = offset + mname.nread + rname.nread; | ||
| 141 | + rr.serial = buffer.readUInt32BE(trailerOffset); | ||
| 142 | + rr.refresh = buffer.readUInt32BE(trailerOffset + 4); | ||
| 143 | + rr.retry = buffer.readUInt32BE(trailerOffset + 8); | ||
| 144 | + rr.expire = buffer.readUInt32BE(trailerOffset + 12); | ||
| 145 | + rr.minttl = buffer.readUInt32BE(trailerOffset + 16); | ||
| 146 | + | ||
| 147 | + assert.strictEqual(trailerOffset + 20, dataLength); | ||
| 148 | + break; | ||
| 149 | + } | ||
| 150 | + default: | ||
| 151 | + throw new Error(`Unknown RR type ${rr.type}`); | ||
| 152 | + } | ||
| 153 | + offset += dataLength; | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + parsed[sectionName].push(rr); | ||
| 157 | + | ||
| 158 | + assert.ok(offset <= buffer.length); | ||
| 159 | + } | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | + assert.strictEqual(offset, buffer.length); | ||
| 163 | + return parsed; | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + function writeIPv6(ip) { | ||
| 167 | + const parts = ip.replace(/^:|:$/g, '').split(':'); | ||
| 168 | + const buf = Buffer.alloc(16); | ||
| 169 | + | ||
| 170 | + let offset = 0; | ||
| 171 | + for (const part of parts) { | ||
| 172 | + if (part === '') { | ||
| 173 | + offset += 16 - 2 * (parts.length - 1); | ||
| 174 | + } else { | ||
| 175 | + buf.writeUInt16BE(parseInt(part, 16), offset); | ||
| 176 | + offset += 2; | ||
| 177 | + } | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + return buf; | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + function writeDomainName(domain) { | ||
| 184 | + return Buffer.concat(domain.split('.').map((label) => { | ||
| 185 | + assert(label.length < 64); | ||
| 186 | + return Buffer.concat([ | ||
| 187 | + Buffer.from([label.length]), | ||
| 188 | + Buffer.from(label, 'ascii') | ||
| 189 | + ]); | ||
| 190 | + }).concat([Buffer.alloc(1)])); | ||
| 191 | + } | ||
| 192 | + | ||
| 193 | + function writeDNSPacket(parsed) { | ||
| 194 | + const buffers = []; | ||
| 195 | + const kStandardResponseFlags = 0x8180; | ||
| 196 | + | ||
| 197 | + buffers.push(new Uint16Array([ | ||
| 198 | + parsed.id, | ||
| 199 | + parsed.flags === undefined ? kStandardResponseFlags : parsed.flags, | ||
| 200 | + parsed.questions && parsed.questions.length, | ||
| 201 | + parsed.answers && parsed.answers.length, | ||
| 202 | + parsed.authorityAnswers && parsed.authorityAnswers.length, | ||
| 203 | + parsed.additionalRecords && parsed.additionalRecords.length, | ||
| 204 | + ])); | ||
| 205 | + | ||
| 206 | + for (const q of parsed.questions) { | ||
| 207 | + assert(types[q.type]); | ||
| 208 | + buffers.push(writeDomainName(q.domain)); | ||
| 209 | + buffers.push(new Uint16Array([ | ||
| 210 | + types[q.type], | ||
| 211 | + q.cls === undefined ? classes.IN : q.cls | ||
| 212 | + ])); | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + for (const rr of [].concat(parsed.answers, | ||
| 216 | + parsed.authorityAnswers, | ||
| 217 | + parsed.additionalRecords)) { | ||
| 218 | + if (!rr) continue; | ||
| 219 | + | ||
| 220 | + assert(types[rr.type]); | ||
| 221 | + buffers.push(writeDomainName(rr.domain)); | ||
| 222 | + buffers.push(new Uint16Array([ | ||
| 223 | + types[rr.type], | ||
| 224 | + rr.cls === undefined ? classes.IN : rr.cls | ||
| 225 | + ])); | ||
| 226 | + buffers.push(new Int32Array([rr.ttl])); | ||
| 227 | + | ||
| 228 | + const rdLengthBuf = new Uint16Array(1); | ||
| 229 | + buffers.push(rdLengthBuf); | ||
| 230 | + | ||
| 231 | + switch (rr.type) { | ||
| 232 | + case 'A': | ||
| 233 | + rdLengthBuf[0] = 4; | ||
| 234 | + buffers.push(new Uint8Array(rr.address.split('.'))); | ||
| 235 | + break; | ||
| 236 | + case 'AAAA': | ||
| 237 | + rdLengthBuf[0] = 16; | ||
| 238 | + buffers.push(writeIPv6(rr.address)); | ||
| 239 | + break; | ||
| 240 | + case 'TXT': | ||
| 241 | + const total = rr.entries.map((s) => s.length).reduce((a, b) => a + b); | ||
| 242 | + // Total length of all strings + 1 byte each for their lengths. | ||
| 243 | + rdLengthBuf[0] = rr.entries.length + total; | ||
| 244 | + for (const txt of rr.entries) { | ||
| 245 | + buffers.push(new Uint8Array([Buffer.byteLength(txt)])); | ||
| 246 | + buffers.push(Buffer.from(txt)); | ||
| 247 | + } | ||
| 248 | + break; | ||
| 249 | + case 'MX': | ||
| 250 | + rdLengthBuf[0] = 2; | ||
| 251 | + buffers.push(new Uint16Array([rr.priority])); | ||
| 252 | + // fall through | ||
| 253 | + case 'NS': | ||
| 254 | + case 'CNAME': | ||
| 255 | + case 'PTR': | ||
| 256 | + { | ||
| 257 | + const domain = writeDomainName(rr.exchange || rr.value); | ||
| 258 | + rdLengthBuf[0] += domain.length; | ||
| 259 | + buffers.push(domain); | ||
| 260 | + break; | ||
| 261 | + } | ||
| 262 | + case 'SOA': | ||
| 263 | + { | ||
| 264 | + const mname = writeDomainName(rr.nsname); | ||
| 265 | + const rname = writeDomainName(rr.hostmaster); | ||
| 266 | + rdLengthBuf[0] = mname.length + rname.length + 20; | ||
| 267 | + buffers.push(mname, rname); | ||
| 268 | + buffers.push(new Uint32Array([ | ||
| 269 | + rr.serial, rr.refresh, rr.retry, rr.expire, rr.minttl | ||
| 270 | + ])); | ||
| 271 | + break; | ||
| 272 | + } | ||
| 273 | + default: | ||
| 274 | + throw new Error(`Unknown RR type ${rr.type}`); | ||
| 275 | + } | ||
| 276 | + } | ||
| 277 | + | ||
| 278 | + return Buffer.concat(buffers.map((typedArray) => { | ||
| 279 | + const buf = Buffer.from(typedArray.buffer, | ||
| 280 | + typedArray.byteOffset, | ||
| 281 | + typedArray.byteLength); | ||
| 282 | + if (os.endianness() === 'LE') { | ||
| 283 | + if (typedArray.BYTES_PER_ELEMENT === 2) buf.swap16(); | ||
| 284 | + if (typedArray.BYTES_PER_ELEMENT === 4) buf.swap32(); | ||
| 285 | + } | ||
| 286 | + return buf; | ||
| 287 | + })); | ||
| 288 | + } | ||
| 289 | + | ||
| 290 | + module.exports = { types, classes, writeDNSPacket, parseDNSPacket }; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,35 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const dnstools = require('../common/dns'); | ||
| 4 | + const dns = require('dns'); | ||
| 5 | + const assert = require('assert'); | ||
| 6 | + const dgram = require('dgram'); | ||
| 7 | + | ||
| 8 | + const server = dgram.createSocket('udp4'); | ||
| 9 | + | ||
| 10 | + server.on('message', common.mustCall((msg, { address, port }) => { | ||
| 11 | + const parsed = dnstools.parseDNSPacket(msg); | ||
| 12 | + const domain = parsed.questions[0].domain; | ||
| 13 | + assert.strictEqual(domain, 'example.org'); | ||
| 14 | + | ||
| 15 | + const buf = dnstools.writeDNSPacket({ | ||
| 16 | + id: parsed.id, | ||
| 17 | + questions: parsed.questions, | ||
| 18 | + answers: { type: 'A', address: '1.2.3.4', ttl: 123, domain }, | ||
| 19 | + }); | ||
| 20 | + // Overwrite the # of answers with 2, which is incorrect. | ||
| 21 | + buf.writeUInt16LE(2, 6); | ||
| 22 | + server.send(buf, port, address); | ||
| 23 | + })); | ||
| 24 | + | ||
| 25 | + server.bind(0, common.mustCall(() => { | ||
| 26 | + const address = server.address(); | ||
| 27 | + dns.setServers([`127.0.0.1:${address.port}`]); | ||
| 28 | + | ||
| 29 | + dns.resolveAny('example.org', common.mustCall((err) => { | ||
| 30 | + assert.strictEqual(err.code, 'EBADRESP'); | ||
| 31 | + assert.strictEqual(err.syscall, 'queryAny'); | ||
| 32 | + assert.strictEqual(err.hostname, 'example.org'); | ||
| 33 | + server.close(); | ||
| 34 | + })); | ||
| 35 | + })); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,53 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const dnstools = require('../common/dns'); | ||
| 4 | + const dns = require('dns'); | ||
| 5 | + const assert = require('assert'); | ||
| 6 | + const dgram = require('dgram'); | ||
| 7 | + | ||
| 8 | + const answers = [ | ||
| 9 | + { type: 'A', address: '1.2.3.4', ttl: 123 }, | ||
| 10 | + { type: 'AAAA', address: '::42', ttl: 123 }, | ||
| 11 | + { type: 'MX', priority: 42, exchange: 'foobar.com', ttl: 124 }, | ||
| 12 | + { type: 'NS', value: 'foobar.org', ttl: 457 }, | ||
| 13 | + { type: 'TXT', entries: [ 'v=spf1 ~all', 'xyz' ] }, | ||
| 14 | + { type: 'PTR', value: 'baz.org', ttl: 987 }, | ||
| 15 | + { | ||
| 16 | + type: 'SOA', | ||
| 17 | + nsname: 'ns1.example.com', | ||
| 18 | + hostmaster: 'admin.example.com', | ||
| 19 | + serial: 156696742, | ||
| 20 | + refresh: 900, | ||
| 21 | + retry: 900, | ||
| 22 | + expire: 1800, | ||
| 23 | + minttl: 60 | ||
| 24 | + }, | ||
| 25 | + ]; | ||
| 26 | + | ||
| 27 | + const server = dgram.createSocket('udp4'); | ||
| 28 | + | ||
| 29 | + server.on('message', common.mustCall((msg, { address, port }) => { | ||
| 30 | + const parsed = dnstools.parseDNSPacket(msg); | ||
| 31 | + const domain = parsed.questions[0].domain; | ||
| 32 | + assert.strictEqual(domain, 'example.org'); | ||
| 33 | + | ||
| 34 | + server.send(dnstools.writeDNSPacket({ | ||
| 35 | + id: parsed.id, | ||
| 36 | + questions: parsed.questions, | ||
| 37 | + answers: answers.map((answer) => Object.assign({ domain }, answer)), | ||
| 38 | + }), port, address); | ||
| 39 | + })); | ||
| 40 | + | ||
| 41 | + server.bind(0, common.mustCall(() => { | ||
| 42 | + const address = server.address(); | ||
| 43 | + dns.setServers([`127.0.0.1:${address.port}`]); | ||
| 44 | + | ||
| 45 | + dns.resolveAny('example.org', common.mustCall((err, res) => { | ||
| 46 | + assert.ifError(err); | ||
| 47 | + // Compare copies with ttl removed, c-ares fiddles with that value. | ||
| 48 | + assert.deepStrictEqual( | ||
| 49 | + res.map((r) => Object.assign({}, r, { ttl: null })), | ||
| 50 | + answers.map((r) => Object.assign({}, r, { ttl: null }))); | ||
| 51 | + server.close(); | ||
| 52 | + })); | ||
| 53 | + })); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments