test.js (17353B)
1 'use strict' 2 3 const tape = require('tape') 4 const packet = require('./') 5 const rcodes = require('./rcodes') 6 const opcodes = require('./opcodes') 7 const optioncodes = require('./optioncodes') 8 9 tape('unknown', function (t) { 10 testEncoder(t, packet.unknown, Buffer.from('hello world')) 11 t.end() 12 }) 13 14 tape('txt', function (t) { 15 testEncoder(t, packet.txt, []) 16 testEncoder(t, packet.txt, ['hello world']) 17 testEncoder(t, packet.txt, ['hello', 'world']) 18 testEncoder(t, packet.txt, [Buffer.from([0, 1, 2, 3, 4, 5])]) 19 testEncoder(t, packet.txt, ['a', 'b', Buffer.from([0, 1, 2, 3, 4, 5])]) 20 testEncoder(t, packet.txt, ['', Buffer.allocUnsafe(0)]) 21 t.end() 22 }) 23 24 tape('txt-scalar-string', function (t) { 25 const buf = packet.txt.encode('hi') 26 const val = packet.txt.decode(buf) 27 t.ok(val.length === 1, 'array length') 28 t.ok(val[0].toString() === 'hi', 'data') 29 t.end() 30 }) 31 32 tape('txt-scalar-buffer', function (t) { 33 const data = Buffer.from([0, 1, 2, 3, 4, 5]) 34 const buf = packet.txt.encode(data) 35 const val = packet.txt.decode(buf) 36 t.ok(val.length === 1, 'array length') 37 t.ok(val[0].equals(data), 'data') 38 t.end() 39 }) 40 41 tape('txt-invalid-data', function (t) { 42 t.throws(function () { packet.txt.encode(null) }, 'null') 43 t.throws(function () { packet.txt.encode(undefined) }, 'undefined') 44 t.throws(function () { packet.txt.encode(10) }, 'number') 45 t.end() 46 }) 47 48 tape('null', function (t) { 49 testEncoder(t, packet.null, Buffer.from([0, 1, 2, 3, 4, 5])) 50 t.end() 51 }) 52 53 tape('hinfo', function (t) { 54 testEncoder(t, packet.hinfo, { cpu: 'intel', os: 'best one' }) 55 t.end() 56 }) 57 58 tape('ptr', function (t) { 59 testEncoder(t, packet.ptr, 'hello.world.com') 60 t.end() 61 }) 62 63 tape('cname', function (t) { 64 testEncoder(t, packet.cname, 'hello.cname.world.com') 65 t.end() 66 }) 67 68 tape('dname', function (t) { 69 testEncoder(t, packet.dname, 'hello.dname.world.com') 70 t.end() 71 }) 72 73 tape('srv', function (t) { 74 testEncoder(t, packet.srv, { port: 9999, target: 'hello.world.com' }) 75 testEncoder(t, packet.srv, { port: 9999, target: 'hello.world.com', priority: 42, weight: 10 }) 76 t.end() 77 }) 78 79 tape('caa', function (t) { 80 testEncoder(t, packet.caa, { flags: 128, tag: 'issue', value: 'letsencrypt.org', issuerCritical: true }) 81 testEncoder(t, packet.caa, { tag: 'issue', value: 'letsencrypt.org', issuerCritical: true }) 82 testEncoder(t, packet.caa, { tag: 'issue', value: 'letsencrypt.org' }) 83 t.end() 84 }) 85 86 tape('mx', function (t) { 87 testEncoder(t, packet.mx, { preference: 10, exchange: 'mx.hello.world.com' }) 88 testEncoder(t, packet.mx, { exchange: 'mx.hello.world.com' }) 89 t.end() 90 }) 91 92 tape('ns', function (t) { 93 testEncoder(t, packet.ns, 'ns.world.com') 94 t.end() 95 }) 96 97 tape('soa', function (t) { 98 testEncoder(t, packet.soa, { 99 mname: 'hello.world.com', 100 rname: 'root.hello.world.com', 101 serial: 2018010400, 102 refresh: 14400, 103 retry: 3600, 104 expire: 604800, 105 minimum: 3600 106 }) 107 t.end() 108 }) 109 110 tape('a', function (t) { 111 testEncoder(t, packet.a, '127.0.0.1') 112 t.end() 113 }) 114 115 tape('aaaa', function (t) { 116 testEncoder(t, packet.aaaa, 'fe80::1') 117 t.end() 118 }) 119 120 tape('query', function (t) { 121 testEncoder(t, packet, { 122 type: 'query', 123 questions: [{ 124 type: 'A', 125 name: 'hello.a.com' 126 }, { 127 type: 'SRV', 128 name: 'hello.srv.com' 129 }] 130 }) 131 132 testEncoder(t, packet, { 133 type: 'query', 134 id: 42, 135 questions: [{ 136 type: 'A', 137 class: 'IN', 138 name: 'hello.a.com' 139 }, { 140 type: 'SRV', 141 name: 'hello.srv.com' 142 }] 143 }) 144 145 testEncoder(t, packet, { 146 type: 'query', 147 id: 42, 148 questions: [{ 149 type: 'A', 150 class: 'CH', 151 name: 'hello.a.com' 152 }, { 153 type: 'SRV', 154 name: 'hello.srv.com' 155 }] 156 }) 157 158 t.end() 159 }) 160 161 tape('response', function (t) { 162 testEncoder(t, packet, { 163 type: 'response', 164 answers: [{ 165 type: 'A', 166 class: 'IN', 167 flush: true, 168 name: 'hello.a.com', 169 data: '127.0.0.1' 170 }] 171 }) 172 173 testEncoder(t, packet, { 174 type: 'response', 175 flags: packet.TRUNCATED_RESPONSE, 176 answers: [{ 177 type: 'A', 178 class: 'IN', 179 name: 'hello.a.com', 180 data: '127.0.0.1' 181 }, { 182 type: 'SRV', 183 class: 'IN', 184 name: 'hello.srv.com', 185 data: { 186 port: 9090, 187 target: 'hello.target.com' 188 } 189 }, { 190 type: 'CNAME', 191 class: 'IN', 192 name: 'hello.cname.com', 193 data: 'hello.other.domain.com' 194 }] 195 }) 196 197 testEncoder(t, packet, { 198 type: 'response', 199 id: 100, 200 flags: 0, 201 additionals: [{ 202 type: 'AAAA', 203 name: 'hello.a.com', 204 data: 'fe80::1' 205 }, { 206 type: 'PTR', 207 name: 'hello.ptr.com', 208 data: 'hello.other.ptr.com' 209 }, { 210 type: 'SRV', 211 name: 'hello.srv.com', 212 ttl: 42, 213 data: { 214 port: 9090, 215 target: 'hello.target.com' 216 } 217 }], 218 answers: [{ 219 type: 'NULL', 220 name: 'hello.null.com', 221 data: Buffer.from([1, 2, 3, 4, 5]) 222 }] 223 }) 224 225 testEncoder(t, packet, { 226 type: 'response', 227 answers: [{ 228 type: 'TXT', 229 name: 'emptytxt.com', 230 data: '' 231 }] 232 }) 233 234 t.end() 235 }) 236 237 tape('rcode', function (t) { 238 const errors = ['NOERROR', 'FORMERR', 'SERVFAIL', 'NXDOMAIN', 'NOTIMP', 'REFUSED', 'YXDOMAIN', 'YXRRSET', 'NXRRSET', 'NOTAUTH', 'NOTZONE', 'RCODE_11', 'RCODE_12', 'RCODE_13', 'RCODE_14', 'RCODE_15'] 239 for (const i in errors) { 240 const code = rcodes.toRcode(errors[i]) 241 t.ok(errors[i] === rcodes.toString(code), 'rcode conversion from/to string matches: ' + rcodes.toString(code)) 242 } 243 244 const ops = ['QUERY', 'IQUERY', 'STATUS', 'OPCODE_3', 'NOTIFY', 'UPDATE', 'OPCODE_6', 'OPCODE_7', 'OPCODE_8', 'OPCODE_9', 'OPCODE_10', 'OPCODE_11', 'OPCODE_12', 'OPCODE_13', 'OPCODE_14', 'OPCODE_15'] 245 for (const j in ops) { 246 const ocode = opcodes.toOpcode(ops[j]) 247 t.ok(ops[j] === opcodes.toString(ocode), 'opcode conversion from/to string matches: ' + opcodes.toString(ocode)) 248 } 249 250 const buf = packet.encode({ 251 type: 'response', 252 id: 45632, 253 flags: 0x8480, 254 answers: [{ 255 type: 'A', 256 name: 'hello.example.net', 257 data: '127.0.0.1' 258 }] 259 }) 260 const val = packet.decode(buf) 261 t.ok(val.type === 'response', 'decode type') 262 t.ok(val.opcode === 'QUERY', 'decode opcode') 263 t.ok(val.flag_qr === true, 'decode flag_qr') 264 t.ok(val.flag_aa === true, 'decode flag_aa') 265 t.ok(val.flag_tc === false, 'decode flag_tc') 266 t.ok(val.flag_rd === false, 'decode flag_rd') 267 t.ok(val.flag_ra === true, 'decode flag_ra') 268 t.ok(val.flag_z === false, 'decode flag_z') 269 t.ok(val.flag_ad === false, 'decode flag_ad') 270 t.ok(val.flag_cd === false, 'decode flag_cd') 271 t.ok(val.rcode === 'NOERROR', 'decode rcode') 272 t.end() 273 }) 274 275 tape('name_encoding', function (t) { 276 let data = 'foo.example.com' 277 const buf = Buffer.allocUnsafe(255) 278 let offset = 0 279 packet.name.encode(data, buf, offset) 280 t.ok(packet.name.encode.bytes === 17, 'name encoding length matches') 281 let dd = packet.name.decode(buf, offset) 282 t.ok(data === dd, 'encode/decode matches') 283 offset += packet.name.encode.bytes 284 285 data = 'com' 286 packet.name.encode(data, buf, offset) 287 t.ok(packet.name.encode.bytes === 5, 'name encoding length matches') 288 dd = packet.name.decode(buf, offset) 289 t.ok(data === dd, 'encode/decode matches') 290 offset += packet.name.encode.bytes 291 292 data = 'example.com.' 293 packet.name.encode(data, buf, offset) 294 t.ok(packet.name.encode.bytes === 13, 'name encoding length matches') 295 dd = packet.name.decode(buf, offset) 296 t.ok(data.slice(0, -1) === dd, 'encode/decode matches') 297 offset += packet.name.encode.bytes 298 299 data = '.' 300 packet.name.encode(data, buf, offset) 301 t.ok(packet.name.encode.bytes === 1, 'name encoding length matches') 302 dd = packet.name.decode(buf, offset) 303 t.ok(data === dd, 'encode/decode matches') 304 t.end() 305 }) 306 307 tape('stream', function (t) { 308 const val = { 309 type: 'query', 310 id: 45632, 311 flags: 0x8480, 312 answers: [{ 313 type: 'A', 314 name: 'test2.example.net', 315 data: '198.51.100.1' 316 }] 317 } 318 const buf = packet.streamEncode(val) 319 const val2 = packet.streamDecode(buf) 320 321 t.same(buf.length, packet.streamEncode.bytes, 'streamEncode.bytes was set correctly') 322 t.ok(compare(t, val2.type, val.type), 'streamDecoded type match') 323 t.ok(compare(t, val2.id, val.id), 'streamDecoded id match') 324 t.ok(parseInt(val2.flags) === parseInt(val.flags & 0x7FFF), 'streamDecoded flags match') 325 const answer = val.answers[0] 326 const answer2 = val2.answers[0] 327 t.ok(compare(t, answer.type, answer2.type), 'streamDecoded RR type match') 328 t.ok(compare(t, answer.name, answer2.name), 'streamDecoded RR name match') 329 t.ok(compare(t, answer.data, answer2.data), 'streamDecoded RR rdata match') 330 t.end() 331 }) 332 333 tape('opt', function (t) { 334 const val = { 335 type: 'query', 336 questions: [{ 337 type: 'A', 338 name: 'hello.a.com' 339 }], 340 additionals: [{ 341 type: 'OPT', 342 name: '.', 343 udpPayloadSize: 1024 344 }] 345 } 346 testEncoder(t, packet, val) 347 let buf = packet.encode(val) 348 let val2 = packet.decode(buf) 349 const additional1 = val.additionals[0] 350 let additional2 = val2.additionals[0] 351 t.ok(compare(t, additional1.name, additional2.name), 'name matches') 352 t.ok(compare(t, additional1.udpPayloadSize, additional2.udpPayloadSize), 'udp payload size matches') 353 t.ok(compare(t, 0, additional2.flags), 'flags match') 354 additional1.flags = packet.DNSSEC_OK 355 additional1.extendedRcode = 0x80 356 additional1.options = [ { 357 code: 'CLIENT_SUBNET', // edns-client-subnet, see RFC 7871 358 ip: 'fe80::', 359 sourcePrefixLength: 64 360 }, { 361 code: 8, // still ECS 362 ip: '5.6.0.0', 363 sourcePrefixLength: 16, 364 scopePrefixLength: 16 365 }, { 366 code: 'padding', 367 length: 31 368 }, { 369 code: 'TCP_KEEPALIVE' 370 }, { 371 code: 'tcp_keepalive', 372 timeout: 150 373 }, { 374 code: 'KEY_TAG', 375 tags: [1, 82, 987] 376 }] 377 buf = packet.encode(val) 378 val2 = packet.decode(buf) 379 additional2 = val2.additionals[0] 380 t.ok(compare(t, 1 << 15, additional2.flags), 'DO bit set in flags') 381 t.ok(compare(t, true, additional2.flag_do), 'DO bit set') 382 t.ok(compare(t, additional1.extendedRcode, additional2.extendedRcode), 'extended rcode matches') 383 t.ok(compare(t, 8, additional2.options[0].code)) 384 t.ok(compare(t, 'fe80::', additional2.options[0].ip)) 385 t.ok(compare(t, 64, additional2.options[0].sourcePrefixLength)) 386 t.ok(compare(t, '5.6.0.0', additional2.options[1].ip)) 387 t.ok(compare(t, 16, additional2.options[1].sourcePrefixLength)) 388 t.ok(compare(t, 16, additional2.options[1].scopePrefixLength)) 389 t.ok(compare(t, additional1.options[2].length, additional2.options[2].data.length)) 390 t.ok(compare(t, additional1.options[3].timeout, undefined)) 391 t.ok(compare(t, additional1.options[4].timeout, additional2.options[4].timeout)) 392 t.ok(compare(t, additional1.options[5].tags, additional2.options[5].tags)) 393 t.end() 394 }) 395 396 tape('dnskey', function (t) { 397 testEncoder(t, packet.dnskey, { 398 flags: packet.dnskey.SECURE_ENTRYPOINT | packet.dnskey.ZONE_KEY, 399 algorithm: 1, 400 key: Buffer.from([0, 1, 2, 3, 4, 5]) 401 }) 402 t.end() 403 }) 404 405 tape('rrsig', function (t) { 406 const testRRSIG = { 407 typeCovered: 'A', 408 algorithm: 1, 409 labels: 2, 410 originalTTL: 3600, 411 expiration: 1234, 412 inception: 1233, 413 keyTag: 2345, 414 signersName: 'foo.com', 415 signature: Buffer.from([0, 1, 2, 3, 4, 5]) 416 } 417 testEncoder(t, packet.rrsig, testRRSIG) 418 419 // Check the signature length is correct with extra junk at the end 420 const buf = Buffer.allocUnsafe(packet.rrsig.encodingLength(testRRSIG) + 4) 421 packet.rrsig.encode(testRRSIG, buf) 422 const val2 = packet.rrsig.decode(buf) 423 t.ok(compare(t, testRRSIG, val2)) 424 425 t.end() 426 }) 427 428 tape('rrp', function (t) { 429 testEncoder(t, packet.rp, { 430 mbox: 'foo.bar.com', 431 txt: 'baz.bar.com' 432 }) 433 testEncoder(t, packet.rp, { 434 mbox: 'foo.bar.com' 435 }) 436 testEncoder(t, packet.rp, { 437 txt: 'baz.bar.com' 438 }) 439 testEncoder(t, packet.rp, {}) 440 t.end() 441 }) 442 443 tape('nsec', function (t) { 444 testEncoder(t, packet.nsec, { 445 nextDomain: 'foo.com', 446 rrtypes: ['A', 'DNSKEY', 'CAA', 'DLV'] 447 }) 448 testEncoder(t, packet.nsec, { 449 nextDomain: 'foo.com', 450 rrtypes: ['TXT'] // 16 451 }) 452 testEncoder(t, packet.nsec, { 453 nextDomain: 'foo.com', 454 rrtypes: ['TKEY'] // 249 455 }) 456 testEncoder(t, packet.nsec, { 457 nextDomain: 'foo.com', 458 rrtypes: ['RRSIG', 'NSEC'] 459 }) 460 testEncoder(t, packet.nsec, { 461 nextDomain: 'foo.com', 462 rrtypes: ['TXT', 'RRSIG'] 463 }) 464 testEncoder(t, packet.nsec, { 465 nextDomain: 'foo.com', 466 rrtypes: ['TXT', 'NSEC'] 467 }) 468 469 // Test with the sample NSEC from https://tools.ietf.org/html/rfc4034#section-4.3 470 var sampleNSEC = Buffer.from('003704686f7374076578616d706c6503636f6d00' + 471 '0006400100000003041b000000000000000000000000000000000000000000000' + 472 '000000020', 'hex') 473 var decoded = packet.nsec.decode(sampleNSEC) 474 t.ok(compare(t, decoded, { 475 nextDomain: 'host.example.com', 476 rrtypes: ['A', 'MX', 'RRSIG', 'NSEC', 'UNKNOWN_1234'] 477 })) 478 var reencoded = packet.nsec.encode(decoded) 479 t.same(sampleNSEC.length, reencoded.length) 480 t.same(sampleNSEC, reencoded) 481 t.end() 482 }) 483 484 tape('nsec3', function (t) { 485 testEncoder(t, packet.nsec3, { 486 algorithm: 1, 487 flags: 0, 488 iterations: 257, 489 salt: Buffer.from([42, 42, 42]), 490 nextDomain: Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]), 491 rrtypes: ['A', 'DNSKEY', 'CAA', 'DLV'] 492 }) 493 t.end() 494 }) 495 496 tape('ds', function (t) { 497 testEncoder(t, packet.ds, { 498 keyTag: 1234, 499 algorithm: 1, 500 digestType: 1, 501 digest: Buffer.from([0, 1, 2, 3, 4, 5]) 502 }) 503 t.end() 504 }) 505 506 tape('unpack', function (t) { 507 const buf = Buffer.from([ 508 0x00, 0x79, 509 0xde, 0xad, 0x85, 0x00, 0x00, 0x01, 0x00, 0x01, 510 0x00, 0x02, 0x00, 0x02, 0x02, 0x6f, 0x6a, 0x05, 511 0x62, 0x61, 0x6e, 0x67, 0x6a, 0x03, 0x63, 0x6f, 512 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 513 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 514 0x00, 0x04, 0x81, 0xfa, 0x0b, 0xaa, 0xc0, 0x0f, 515 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 516 0x00, 0x05, 0x02, 0x63, 0x6a, 0xc0, 0x0f, 0xc0, 517 0x0f, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x0e, 518 0x10, 0x00, 0x02, 0xc0, 0x0c, 0xc0, 0x3a, 0x00, 519 0x01, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00, 520 0x04, 0x45, 0x4d, 0x9b, 0x9c, 0xc0, 0x0c, 0x00, 521 0x1c, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00, 522 0x10, 0x20, 0x01, 0x04, 0x18, 0x00, 0x00, 0x50, 523 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf9 524 ]) 525 const val = packet.streamDecode(buf) 526 const answer = val.answers[0] 527 const authority = val.authorities[1] 528 t.ok(val.rcode === 'NOERROR', 'decode rcode') 529 t.ok(compare(t, answer.type, 'A'), 'streamDecoded RR type match') 530 t.ok(compare(t, answer.name, 'oj.bangj.com'), 'streamDecoded RR name match') 531 t.ok(compare(t, answer.data, '129.250.11.170'), 'streamDecoded RR rdata match') 532 t.ok(compare(t, authority.type, 'NS'), 'streamDecoded RR type match') 533 t.ok(compare(t, authority.name, 'bangj.com'), 'streamDecoded RR name match') 534 t.ok(compare(t, authority.data, 'oj.bangj.com'), 'streamDecoded RR rdata match') 535 t.end() 536 }) 537 538 tape('optioncodes', function (t) { 539 const opts = [ 540 [0, 'OPTION_0'], 541 [1, 'LLQ'], 542 [2, 'UL'], 543 [3, 'NSID'], 544 [4, 'OPTION_4'], 545 [5, 'DAU'], 546 [6, 'DHU'], 547 [7, 'N3U'], 548 [8, 'CLIENT_SUBNET'], 549 [9, 'EXPIRE'], 550 [10, 'COOKIE'], 551 [11, 'TCP_KEEPALIVE'], 552 [12, 'PADDING'], 553 [13, 'CHAIN'], 554 [14, 'KEY_TAG'], 555 [26946, 'DEVICEID'], 556 [65535, 'OPTION_65535'], 557 [64000, 'OPTION_64000'], 558 [65002, 'OPTION_65002'], 559 [-1, null] 560 ] 561 for (const [code, str] of opts) { 562 const s = optioncodes.toString(code) 563 t.ok(compare(t, s, str), `${code} => ${str}`) 564 t.ok(compare(t, optioncodes.toCode(s), code), `${str} => ${code}`) 565 } 566 t.ok(compare(t, optioncodes.toCode('INVALIDINVALID'), -1)) 567 t.end() 568 }) 569 570 function testEncoder (t, rpacket, val) { 571 const buf = rpacket.encode(val) 572 const val2 = rpacket.decode(buf) 573 574 t.same(buf.length, rpacket.encode.bytes, 'encode.bytes was set correctly') 575 t.same(buf.length, rpacket.encodingLength(val), 'encoding length matches') 576 t.ok(compare(t, val, val2), 'decoded object match') 577 578 const buf2 = rpacket.encode(val2) 579 const val3 = rpacket.decode(buf2) 580 581 t.same(buf2.length, rpacket.encode.bytes, 'encode.bytes was set correctly on re-encode') 582 t.same(buf2.length, rpacket.encodingLength(val), 'encoding length matches on re-encode') 583 584 t.ok(compare(t, val, val3), 'decoded object match on re-encode') 585 t.ok(compare(t, val2, val3), 're-encoded decoded object match on re-encode') 586 587 const bigger = Buffer.allocUnsafe(buf2.length + 10) 588 589 const buf3 = rpacket.encode(val, bigger, 10) 590 const val4 = rpacket.decode(buf3, 10) 591 592 t.ok(buf3 === bigger, 'echoes buffer on external buffer') 593 t.same(rpacket.encode.bytes, buf.length, 'encode.bytes is the same on external buffer') 594 t.ok(compare(t, val, val4), 'decoded object match on external buffer') 595 } 596 597 function compare (t, a, b) { 598 if (Buffer.isBuffer(a)) return a.toString('hex') === b.toString('hex') 599 if (typeof a === 'object' && a && b) { 600 const keys = Object.keys(a) 601 for (let i = 0; i < keys.length; i++) { 602 if (!compare(t, a[keys[i]], b[keys[i]])) { 603 return false 604 } 605 } 606 } else if (Array.isArray(b) && !Array.isArray(a)) { 607 // TXT always decode as array 608 return a.toString() === b[0].toString() 609 } else { 610 return a === b 611 } 612 return true 613 }