Bug 1540656 - Add dns-packet and node-ip modules r=dragana
☠☠ backed out by 9ce75ad03d72 ☠ ☠
authorValentin Gosu <valentin.gosu@gmail.com>
Thu, 04 Apr 2019 09:32:11 +0000
changeset 467926 70a90b960aef224ca2b85718e534e7a9b8c1e5f1
parent 467925 631a1aae6fb0038e9f1b4b56ebf7db0e99786f07
child 467927 625e9335159cefd680bfd517ac7c3310b80a9bf8
push id35813
push useraiakab@mozilla.com
push dateThu, 04 Apr 2019 16:07:30 +0000
treeherdermozilla-central@aa623df2ae8f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdragana
bugs1540656
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1540656 - Add dns-packet and node-ip modules r=dragana These modules are MIT licensed and they're to be used in xpcshell-tests for TRR. They allow us to make moz-http2.js act like a true DoH server - more specifically to answer DNS queries that have actually been asked, not just a dumb character buffer. Differential Revision: https://phabricator.services.mozilla.com/D25672
.eslintignore
testing/xpcshell/dns-packet/.editorconfig
testing/xpcshell/dns-packet/.eslintrc
testing/xpcshell/dns-packet/.gitignore
testing/xpcshell/dns-packet/.travis.yml
testing/xpcshell/dns-packet/CHANGELOG.md
testing/xpcshell/dns-packet/LICENSE
testing/xpcshell/dns-packet/README.md
testing/xpcshell/dns-packet/classes.js
testing/xpcshell/dns-packet/examples/doh.js
testing/xpcshell/dns-packet/examples/tcp.js
testing/xpcshell/dns-packet/examples/tls.js
testing/xpcshell/dns-packet/examples/udp.js
testing/xpcshell/dns-packet/index.js
testing/xpcshell/dns-packet/opcodes.js
testing/xpcshell/dns-packet/optioncodes.js
testing/xpcshell/dns-packet/package.json
testing/xpcshell/dns-packet/rcodes.js
testing/xpcshell/dns-packet/test.js
testing/xpcshell/dns-packet/types.js
testing/xpcshell/node-ip/.gitignore
testing/xpcshell/node-ip/.jscsrc
testing/xpcshell/node-ip/.jshintrc
testing/xpcshell/node-ip/.travis.yml
testing/xpcshell/node-ip/README.md
testing/xpcshell/node-ip/lib/ip.js
testing/xpcshell/node-ip/package.json
testing/xpcshell/node-ip/test/api-test.js
tools/rewriting/ThirdPartyPaths.txt
--- a/.eslintignore
+++ b/.eslintignore
@@ -327,16 +327,19 @@ testing/talos/talos/tests/kraken/**
 # Runing Talos may extract data here, see bug 1435677.
 testing/talos/talos/tests/tp5n/**
 # Raptor third party
 testing/raptor/raptor/playback/scripts/catapult/**
 
 testing/web-platform/**
 testing/xpcshell/moz-http2/**
 testing/xpcshell/node-http2/**
+testing/xpcshell/dns-packet/**
+testing/xpcshell/node-ip/**
+
 
 # Third party services
 services/common/kinto-http-client.js
 services/common/kinto-offline-client.js
 
 # toolkit/ exclusions
 
 # Intentionally invalid JS
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/.editorconfig
@@ -0,0 +1,10 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+tab_width = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/.eslintrc
@@ -0,0 +1,9 @@
+root: true
+
+parserOptions:
+  ecmaVersion: 2015
+
+env:
+  node: true
+
+extends: standard
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/.gitignore
@@ -0,0 +1,4 @@
+node_modules/
+.nyc_output/
+coverage/
+package-lock.json
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/.travis.yml
@@ -0,0 +1,11 @@
+language: node_js
+node_js:
+  - node
+  - lts/*
+install:
+- npm install
+- npm install coveralls
+script:
+- npm run coverage
+after_success:
+- npx nyc report --reporter=text-lcov | npx coveralls
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/CHANGELOG.md
@@ -0,0 +1,30 @@
+# Version 5.2.0 - 2019-02-21
+
+- Feature: Added support for de/encoding certain OPT options.
+
+# Version 5.1.0 - 2019-01-22
+
+- Feature: Added support for the RP record type.
+
+# Version 5.0.0 - 2018-06-01
+
+- Breaking: Node.js 6.0.0 or greater is now required.
+- Feature: Added support for DNSSEC record types.
+
+# Version 4.1.0 - 2018-02-11
+
+- Feature: Added support for the MX record type.
+
+# Version 4.0.0 - 2018-02-04
+
+- Feature: Added `streamEncode` and `streamDecode` methods for encoding TCP packets.
+- Breaking: Changed the decoded value of TXT records to an array of Buffers. This is to accomodate DNS-SD records which rely on the individual strings record being separated.
+- Breaking: Renamed the `flag_trunc` and `flag_auth` to `flag_tc` and `flag_aa` to match the names of these in the dns standards.
+
+# Version 3.0.0 - 2018-01-12
+
+- Breaking: The `class` option has been changed from integer to string.
+
+# Version 2.0.0 - 2018-01-11
+
+- Breaking: Converted module to ES2015, now requires Node.js 4.0 or greater
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Mathias Buus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/README.md
@@ -0,0 +1,365 @@
+# dns-packet
+[![](https://img.shields.io/npm/v/dns-packet.svg?style=flat)](https://www.npmjs.org/package/dns-packet) [![](https://img.shields.io/npm/dm/dns-packet.svg)](https://www.npmjs.org/package/dns-packet) [![](https://api.travis-ci.org/mafintosh/dns-packet.svg?style=flat)](https://travis-ci.org/mafintosh/dns-packet) [![Coverage Status](https://coveralls.io/repos/github/mafintosh/dns-packet/badge.svg?branch=master)](https://coveralls.io/github/mafintosh/dns-packet?branch=master)
+
+An [abstract-encoding](https://github.com/mafintosh/abstract-encoding) compliant module for encoding / decoding DNS packets. Lifted out of [multicast-dns](https://github.com/mafintosh/multicast-dns) as a separate module.
+
+```
+npm install dns-packet
+```
+
+## UDP Usage
+
+``` js
+const dnsPacket = require('dns-packet')
+const dgram = require('dgram')
+
+const socket = dgram.createSocket('udp4')
+
+const buf = dnsPacket.encode({
+  type: 'query',
+  id: 1,
+  flags: dnsPacket.RECURSION_DESIRED,
+  questions: [{
+    type: 'A',
+    name: 'google.com'
+  }]
+})
+
+socket.on('message', message => {
+  console.log(dnsPacket.decode(message)) // prints out a response from google dns
+})
+
+socket.send(buf, 0, buf.length, 53, '8.8.8.8')
+```
+
+Also see [the UDP example](examples/udp.js).
+
+## TCP, TLS, HTTPS
+
+While DNS has traditionally been used over a datagram transport, it is increasingly being carried over TCP for larger responses commonly including DNSSEC responses and TLS or HTTPS for enhanced security. See below examples on how to use `dns-packet` to wrap DNS packets in these protocols:
+
+- [TCP](examples/tcp.js)
+- [DNS over TLS](examples/tls.js)
+- [DNS over HTTPS](examples/doh.js)
+
+## API
+
+#### `var buf = packets.encode(packet, [buf], [offset])`
+
+Encodes a DNS packet into a buffer containing a UDP payload.
+
+#### `var packet = packets.decode(buf, [offset])`
+
+Decode a DNS packet from a buffer containing a UDP payload.
+
+#### `var buf = packets.streamEncode(packet, [buf], [offset])`
+
+Encodes a DNS packet into a buffer containing a TCP payload.
+
+#### `var packet = packets.streamDecode(buf, [offset])`
+
+Decode a DNS packet from a buffer containing a TCP payload.
+
+#### `var len = packets.encodingLength(packet)`
+
+Returns how many bytes are needed to encode the DNS packet
+
+## Packets
+
+Packets look like this
+
+``` js
+{
+  type: 'query|response',
+  id: optionalIdNumber,
+  flags: optionalBitFlags,
+  questions: [...],
+  answers: [...],
+  additionals: [...],
+  authorities: [...]
+}
+```
+
+The bit flags available are
+
+``` js
+packet.RECURSION_DESIRED
+packet.RECURSION_AVAILABLE
+packet.TRUNCATED_RESPONSE
+packet.AUTHORITATIVE_ANSWER
+packet.AUTHENTIC_DATA
+packet.CHECKING_DISABLED
+```
+
+To use more than one flag bitwise-or them together
+
+``` js
+var flags = packet.RECURSION_DESIRED | packet.RECURSION_AVAILABLE
+```
+
+And to check for a flag use bitwise-and
+
+``` js
+var isRecursive = message.flags & packet.RECURSION_DESIRED
+```
+
+A question looks like this
+
+``` js
+{
+  type: 'A', // or SRV, AAAA, etc
+  class: 'IN', // one of IN, CS, CH, HS, ANY. Default: IN
+  name: 'google.com' // which record are you looking for
+}
+```
+
+And an answer, additional, or authority looks like this
+
+``` js
+{
+  type: 'A', // or SRV, AAAA, etc
+  class: 'IN', // one of IN, CS, CH, HS
+  name: 'google.com', // which name is this record for
+  ttl: optionalTimeToLiveInSeconds,
+  (record specific data, see below)
+}
+```
+
+## Supported record types
+
+#### `A`
+
+``` js
+{
+  data: 'IPv4 address' // fx 127.0.0.1
+}
+```
+
+#### `AAAA`
+
+``` js
+{
+  data: 'IPv6 address' // fx fe80::1
+}
+```
+
+#### `CAA`
+
+``` js
+{
+  flags: 128, // octet
+  tag: 'issue|issuewild|iodef',
+  value: 'ca.example.net',
+  issuerCritical: false
+}
+```
+
+#### `CNAME`
+
+``` js
+{
+  data: 'cname.to.another.record'
+}
+```
+
+#### `DNAME`
+
+``` js
+{
+  data: 'dname.to.another.record'
+}
+```
+
+#### `DNSKEY`
+
+``` js
+{
+  flags: 257, // 16 bits
+  algorithm: 1, // octet
+  key: Buffer
+}
+```
+
+#### `DS`
+
+``` js
+{
+  keyTag: 12345,
+  algorithm: 8,
+  digestType: 1,
+  digest: Buffer
+}
+```
+
+#### `HINFO`
+
+``` js
+{
+  data: {
+    cpu: 'cpu info',
+    os: 'os info'
+  }
+}
+```
+
+#### `MX`
+
+``` js
+{
+  preference: 10,
+  exchange: 'mail.example.net'
+}
+```
+
+#### `NS`
+
+``` js
+{
+  data: nameServer
+}
+```
+
+#### `NSEC`
+
+``` js
+{
+  nextDomain: 'a.domain',
+  rrtypes: ['A', 'TXT', 'RRSIG']
+}
+```
+
+#### `NSEC3`
+
+``` js
+{
+  algorithm: 1,
+  flags: 0,
+  iterations: 2,
+  salt: Buffer,
+  nextDomain: Buffer, // Hashed per RFC5155
+  rrtypes: ['A', 'TXT', 'RRSIG']
+}
+```
+
+#### `NULL`
+
+``` js
+{
+  data: Buffer('any binary data')
+}
+```
+
+#### `OPT`
+
+[EDNS0](https://tools.ietf.org/html/rfc6891) options.
+
+``` js
+{
+  type: 'OPT',
+  name: '.',
+  udpPayloadSize: 4096,
+  flags: packet.DNSSEC_OK,
+  options: [{
+    // pass in any code/data for generic EDNS0 options
+    code: 12,
+    data: Buffer.alloc(31)
+  }, {
+    // Several EDNS0 options have enhanced support
+    code: 'PADDING',
+    length: 31,
+  }, {
+    code: 'CLIENT_SUBNET',
+    family: 2, // 1 for IPv4, 2 for IPv6
+    sourcePrefixLength: 64, // used to truncate IP address
+    scopePrefixLength: 0,
+    ip: 'fe80::',
+  }, {
+    code: 'TCP_KEEPALIVE',
+    timeout: 150 // increments of 100ms.  This means 15s.
+  }, {
+    code: 'KEY_TAG',
+    tags: [1, 2, 3],
+  }]
+}
+```
+
+The options `PADDING`, `CLIENT_SUBNET`, `TCP_KEEPALIVE` and `KEY_TAG` support enhanced de/encoding. See [optionscodes.js](https://github.com/mafintosh/dns-packet/blob/master/optioncodes.js) for all supported option codes. If the `data` property is present on a option, it takes precedence. On decoding, `data` will always be defined.
+
+#### `PTR`
+
+``` js
+{
+  data: 'points.to.another.record'
+}
+```
+
+#### `RP`
+
+``` js
+{
+  mbox: 'admin.example.com',
+  txt: 'txt.example.com'
+}
+```
+
+#### `RRSIG`
+
+``` js
+{
+  typeCovered: 'A',
+  algorithm: 8,
+  labels: 1,
+  originalTTL: 3600,
+  expiration: timestamp,
+  inception: timestamp,
+  keyTag: 12345,
+  signersName: 'a.name',
+  signature: Buffer
+}
+```
+
+#### `SOA`
+
+``` js
+{
+  data:
+    {
+      mname: domainName,
+      rname: mailbox,
+      serial: zoneSerial,
+      refresh: refreshInterval,
+      retry: retryInterval,
+      expire: expireInterval,
+      minimum: minimumTTL
+    }
+}
+```
+
+#### `SRV`
+
+``` js
+{
+  data: {
+    port: servicePort,
+    target: serviceHostName,
+    priority: optionalServicePriority,
+    weight: optionalServiceWeight
+  }
+}
+```
+
+#### `TXT`
+
+``` js
+{
+  data: 'text' || Buffer || [ Buffer || 'text' ]
+}
+```
+
+When encoding, scalar values are converted to an array and strings are converted to UTF-8 encoded Buffers. When decoding, the return value will always be an array of Buffer.
+
+If you need another record type, open an issue and we'll try to add it.
+
+## License
+
+MIT
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/classes.js
@@ -0,0 +1,23 @@
+'use strict'
+
+exports.toString = function (klass) {
+  switch (klass) {
+    case 1: return 'IN'
+    case 2: return 'CS'
+    case 3: return 'CH'
+    case 4: return 'HS'
+    case 255: return 'ANY'
+  }
+  return 'UNKNOWN_' + klass
+}
+
+exports.toClass = function (name) {
+  switch (name.toUpperCase()) {
+    case 'IN': return 1
+    case 'CS': return 2
+    case 'CH': return 3
+    case 'HS': return 4
+    case 'ANY': return 255
+  }
+  return 0
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/examples/doh.js
@@ -0,0 +1,52 @@
+
+'use strict'
+
+/*
+ * Sample code to make DNS over HTTPS request using POST
+ * AUTHOR: Tom Pusateri <pusateri@bangj.com>
+ * DATE: March 17, 2018
+ * LICENSE: MIT
+ */
+
+const dnsPacket = require('..')
+const https = require('https')
+
+function getRandomInt (min, max) {
+  return Math.floor(Math.random() * (max - min + 1)) + min
+}
+
+const buf = dnsPacket.encode({
+  type: 'query',
+  id: getRandomInt(1, 65534),
+  flags: dnsPacket.RECURSION_DESIRED,
+  questions: [{
+    type: 'A',
+    name: 'google.com'
+  }]
+})
+
+const options = {
+  hostname: 'dns.google.com',
+  port: 443,
+  path: '/experimental',
+  method: 'POST',
+  headers: {
+    'Content-Type': 'application/dns-udpwireformat',
+    'Content-Length': Buffer.byteLength(buf)
+  }
+}
+
+const request = https.request(options, (response) => {
+  console.log('statusCode:', response.statusCode)
+  console.log('headers:', response.headers)
+
+  response.on('data', (d) => {
+    console.log(dnsPacket.decode(d))
+  })
+})
+
+request.on('error', (e) => {
+  console.error(e)
+})
+request.write(buf)
+request.end()
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/examples/tcp.js
@@ -0,0 +1,52 @@
+'use strict'
+
+const dnsPacket = require('..')
+const net = require('net')
+
+var response = null
+var expectedLength = 0
+
+function getRandomInt (min, max) {
+  return Math.floor(Math.random() * (max - min + 1)) + min
+}
+
+const buf = dnsPacket.streamEncode({
+  type: 'query',
+  id: getRandomInt(1, 65534),
+  flags: dnsPacket.RECURSION_DESIRED,
+  questions: [{
+    type: 'A',
+    name: 'google.com'
+  }]
+})
+
+const client = new net.Socket()
+client.connect(53, '8.8.8.8', function () {
+  console.log('Connected')
+  client.write(buf)
+})
+
+client.on('data', function (data) {
+  console.log('Received response: %d bytes', data.byteLength)
+  if (response == null) {
+    if (data.byteLength > 1) {
+      const plen = data.readUInt16BE(0)
+      expectedLength = plen
+      if (plen < 12) {
+        throw new Error('below DNS minimum packet length')
+      }
+      response = Buffer.from(data)
+    }
+  } else {
+    response = Buffer.concat([response, data])
+  }
+
+  if (response.byteLength >= expectedLength) {
+    console.log(dnsPacket.streamDecode(response))
+    client.destroy()
+  }
+})
+
+client.on('close', function () {
+  console.log('Connection closed')
+})
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/examples/tls.js
@@ -0,0 +1,61 @@
+'use strict'
+
+const tls = require('tls')
+const dnsPacket = require('..')
+
+var response = null
+var expectedLength = 0
+
+function getRandomInt (min, max) {
+  return Math.floor(Math.random() * (max - min + 1)) + min
+}
+
+const buf = dnsPacket.streamEncode({
+  type: 'query',
+  id: getRandomInt(1, 65534),
+  flags: dnsPacket.RECURSION_DESIRED,
+  questions: [{
+    type: 'A',
+    name: 'google.com'
+  }]
+})
+
+const context = tls.createSecureContext({
+  secureProtocol: 'TLSv1_2_method'
+})
+
+const options = {
+  port: 853,
+  host: 'getdnsapi.net',
+  secureContext: context
+}
+
+const client = tls.connect(options, () => {
+  console.log('client connected')
+  client.write(buf)
+})
+
+client.on('data', function (data) {
+  console.log('Received response: %d bytes', data.byteLength)
+  if (response == null) {
+    if (data.byteLength > 1) {
+      const plen = data.readUInt16BE(0)
+      expectedLength = plen
+      if (plen < 12) {
+        throw new Error('below DNS minimum packet length')
+      }
+      response = Buffer.from(data)
+    }
+  } else {
+    response = Buffer.concat([response, data])
+  }
+
+  if (response.byteLength >= expectedLength) {
+    console.log(dnsPacket.streamDecode(response))
+    client.destroy()
+  }
+})
+
+client.on('end', () => {
+  console.log('Connection ended')
+})
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/examples/udp.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const dnsPacket = require('..')
+const dgram = require('dgram')
+
+const socket = dgram.createSocket('udp4')
+
+function getRandomInt (min, max) {
+  return Math.floor(Math.random() * (max - min + 1)) + min
+}
+
+const buf = dnsPacket.encode({
+  type: 'query',
+  id: getRandomInt(1, 65534),
+  flags: dnsPacket.RECURSION_DESIRED,
+  questions: [{
+    type: 'A',
+    name: 'google.com'
+  }]
+})
+
+socket.on('message', function (message, rinfo) {
+  console.log(rinfo)
+  console.log(dnsPacket.decode(message)) // prints out a response from google dns
+  socket.close()
+})
+
+socket.send(buf, 0, buf.length, 53, '8.8.8.8')
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/index.js
@@ -0,0 +1,1541 @@
+'use strict'
+
+const types = require('./types')
+const rcodes = require('./rcodes')
+const opcodes = require('./opcodes')
+const classes = require('./classes')
+const optioncodes = require('./optioncodes')
+const ip = require('../node-ip')
+
+const QUERY_FLAG = 0
+const RESPONSE_FLAG = 1 << 15
+const FLUSH_MASK = 1 << 15
+const NOT_FLUSH_MASK = ~FLUSH_MASK
+const QU_MASK = 1 << 15
+const NOT_QU_MASK = ~QU_MASK
+
+const name = exports.txt = exports.name = {}
+
+name.encode = function (str, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(name.encodingLength(str))
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  // strip leading and trailing .
+  const n = str.replace(/^\.|\.$/gm, '')
+  if (n.length) {
+    const list = n.split('.')
+
+    for (let i = 0; i < list.length; i++) {
+      const len = buf.write(list[i], offset + 1)
+      buf[offset] = len
+      offset += len + 1
+    }
+  }
+
+  buf[offset++] = 0
+
+  name.encode.bytes = offset - oldOffset
+  return buf
+}
+
+name.encode.bytes = 0
+
+name.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  const list = []
+  const oldOffset = offset
+  let len = buf[offset++]
+
+  if (len === 0) {
+    name.decode.bytes = 1
+    return '.'
+  }
+  if (len >= 0xc0) {
+    const res = name.decode(buf, buf.readUInt16BE(offset - 1) - 0xc000)
+    name.decode.bytes = 2
+    return res
+  }
+
+  while (len) {
+    if (len >= 0xc0) {
+      list.push(name.decode(buf, buf.readUInt16BE(offset - 1) - 0xc000))
+      offset++
+      break
+    }
+
+    list.push(buf.toString('utf-8', offset, offset + len))
+    offset += len
+    len = buf[offset++]
+  }
+
+  name.decode.bytes = offset - oldOffset
+  return list.join('.')
+}
+
+name.decode.bytes = 0
+
+name.encodingLength = function (n) {
+  if (n === '.') return 1
+  return Buffer.byteLength(n) + 2
+}
+
+const string = {}
+
+string.encode = function (s, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(string.encodingLength(s))
+  if (!offset) offset = 0
+
+  const len = buf.write(s, offset + 1)
+  buf[offset] = len
+  string.encode.bytes = len + 1
+  return buf
+}
+
+string.encode.bytes = 0
+
+string.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  const len = buf[offset]
+  const s = buf.toString('utf-8', offset + 1, offset + 1 + len)
+  string.decode.bytes = len + 1
+  return s
+}
+
+string.decode.bytes = 0
+
+string.encodingLength = function (s) {
+  return Buffer.byteLength(s) + 1
+}
+
+const header = {}
+
+header.encode = function (h, buf, offset) {
+  if (!buf) buf = header.encodingLength(h)
+  if (!offset) offset = 0
+
+  const flags = (h.flags || 0) & 32767
+  const type = h.type === 'response' ? RESPONSE_FLAG : QUERY_FLAG
+
+  buf.writeUInt16BE(h.id || 0, offset)
+  buf.writeUInt16BE(flags | type, offset + 2)
+  buf.writeUInt16BE(h.questions.length, offset + 4)
+  buf.writeUInt16BE(h.answers.length, offset + 6)
+  buf.writeUInt16BE(h.authorities.length, offset + 8)
+  buf.writeUInt16BE(h.additionals.length, offset + 10)
+
+  return buf
+}
+
+header.encode.bytes = 12
+
+header.decode = function (buf, offset) {
+  if (!offset) offset = 0
+  if (buf.length < 12) throw new Error('Header must be 12 bytes')
+  const flags = buf.readUInt16BE(offset + 2)
+
+  return {
+    id: buf.readUInt16BE(offset),
+    type: flags & RESPONSE_FLAG ? 'response' : 'query',
+    flags: flags & 32767,
+    flag_qr: ((flags >> 15) & 0x1) === 1,
+    opcode: opcodes.toString((flags >> 11) & 0xf),
+    flag_aa: ((flags >> 10) & 0x1) === 1,
+    flag_tc: ((flags >> 9) & 0x1) === 1,
+    flag_rd: ((flags >> 8) & 0x1) === 1,
+    flag_ra: ((flags >> 7) & 0x1) === 1,
+    flag_z: ((flags >> 6) & 0x1) === 1,
+    flag_ad: ((flags >> 5) & 0x1) === 1,
+    flag_cd: ((flags >> 4) & 0x1) === 1,
+    rcode: rcodes.toString(flags & 0xf),
+    questions: new Array(buf.readUInt16BE(offset + 4)),
+    answers: new Array(buf.readUInt16BE(offset + 6)),
+    authorities: new Array(buf.readUInt16BE(offset + 8)),
+    additionals: new Array(buf.readUInt16BE(offset + 10))
+  }
+}
+
+header.decode.bytes = 12
+
+header.encodingLength = function () {
+  return 12
+}
+
+const runknown = exports.unknown = {}
+
+runknown.encode = function (data, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(runknown.encodingLength(data))
+  if (!offset) offset = 0
+
+  buf.writeUInt16BE(data.length, offset)
+  data.copy(buf, offset + 2)
+
+  runknown.encode.bytes = data.length + 2
+  return buf
+}
+
+runknown.encode.bytes = 0
+
+runknown.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  const len = buf.readUInt16BE(offset)
+  const data = buf.slice(offset + 2, offset + 2 + len)
+  runknown.decode.bytes = len + 2
+  return data
+}
+
+runknown.decode.bytes = 0
+
+runknown.encodingLength = function (data) {
+  return data.length + 2
+}
+
+const rns = exports.ns = {}
+
+rns.encode = function (data, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(rns.encodingLength(data))
+  if (!offset) offset = 0
+
+  name.encode(data, buf, offset + 2)
+  buf.writeUInt16BE(name.encode.bytes, offset)
+  rns.encode.bytes = name.encode.bytes + 2
+  return buf
+}
+
+rns.encode.bytes = 0
+
+rns.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  const len = buf.readUInt16BE(offset)
+  const dd = name.decode(buf, offset + 2)
+
+  rns.decode.bytes = len + 2
+  return dd
+}
+
+rns.decode.bytes = 0
+
+rns.encodingLength = function (data) {
+  return name.encodingLength(data) + 2
+}
+
+const rsoa = exports.soa = {}
+
+rsoa.encode = function (data, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(rsoa.encodingLength(data))
+  if (!offset) offset = 0
+
+  const oldOffset = offset
+  offset += 2
+  name.encode(data.mname, buf, offset)
+  offset += name.encode.bytes
+  name.encode(data.rname, buf, offset)
+  offset += name.encode.bytes
+  buf.writeUInt32BE(data.serial || 0, offset)
+  offset += 4
+  buf.writeUInt32BE(data.refresh || 0, offset)
+  offset += 4
+  buf.writeUInt32BE(data.retry || 0, offset)
+  offset += 4
+  buf.writeUInt32BE(data.expire || 0, offset)
+  offset += 4
+  buf.writeUInt32BE(data.minimum || 0, offset)
+  offset += 4
+
+  buf.writeUInt16BE(offset - oldOffset - 2, oldOffset)
+  rsoa.encode.bytes = offset - oldOffset
+  return buf
+}
+
+rsoa.encode.bytes = 0
+
+rsoa.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  const oldOffset = offset
+
+  const data = {}
+  offset += 2
+  data.mname = name.decode(buf, offset)
+  offset += name.decode.bytes
+  data.rname = name.decode(buf, offset)
+  offset += name.decode.bytes
+  data.serial = buf.readUInt32BE(offset)
+  offset += 4
+  data.refresh = buf.readUInt32BE(offset)
+  offset += 4
+  data.retry = buf.readUInt32BE(offset)
+  offset += 4
+  data.expire = buf.readUInt32BE(offset)
+  offset += 4
+  data.minimum = buf.readUInt32BE(offset)
+  offset += 4
+
+  rsoa.decode.bytes = offset - oldOffset
+  return data
+}
+
+rsoa.decode.bytes = 0
+
+rsoa.encodingLength = function (data) {
+  return 22 + name.encodingLength(data.mname) + name.encodingLength(data.rname)
+}
+
+const rtxt = exports.txt = {}
+
+rtxt.encode = function (data, buf, offset) {
+  if (!Array.isArray(data)) data = [data]
+  for (let i = 0; i < data.length; i++) {
+    if (typeof data[i] === 'string') {
+      data[i] = Buffer.from(data[i])
+    }
+    if (!Buffer.isBuffer(data[i])) {
+      throw new Error('Must be a Buffer')
+    }
+  }
+
+  if (!buf) buf = Buffer.allocUnsafe(rtxt.encodingLength(data))
+  if (!offset) offset = 0
+
+  const oldOffset = offset
+  offset += 2
+
+  data.forEach(function (d) {
+    buf[offset++] = d.length
+    d.copy(buf, offset, 0, d.length)
+    offset += d.length
+  })
+
+  buf.writeUInt16BE(offset - oldOffset - 2, oldOffset)
+  rtxt.encode.bytes = offset - oldOffset
+  return buf
+}
+
+rtxt.encode.bytes = 0
+
+rtxt.decode = function (buf, offset) {
+  if (!offset) offset = 0
+  const oldOffset = offset
+  let remaining = buf.readUInt16BE(offset)
+  offset += 2
+
+  let data = []
+  while (remaining > 0) {
+    const len = buf[offset++]
+    --remaining
+    if (remaining < len) {
+      throw new Error('Buffer overflow')
+    }
+    data.push(buf.slice(offset, offset + len))
+    offset += len
+    remaining -= len
+  }
+
+  rtxt.decode.bytes = offset - oldOffset
+  return data
+}
+
+rtxt.decode.bytes = 0
+
+rtxt.encodingLength = function (data) {
+  if (!Array.isArray(data)) data = [data]
+  let length = 2
+  data.forEach(function (buf) {
+    if (typeof buf === 'string') {
+      length += Buffer.byteLength(buf) + 1
+    } else {
+      length += buf.length + 1
+    }
+  })
+  return length
+}
+
+const rnull = exports.null = {}
+
+rnull.encode = function (data, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(rnull.encodingLength(data))
+  if (!offset) offset = 0
+
+  if (typeof data === 'string') data = Buffer.from(data)
+  if (!data) data = Buffer.allocUnsafe(0)
+
+  const oldOffset = offset
+  offset += 2
+
+  const len = data.length
+  data.copy(buf, offset, 0, len)
+  offset += len
+
+  buf.writeUInt16BE(offset - oldOffset - 2, oldOffset)
+  rnull.encode.bytes = offset - oldOffset
+  return buf
+}
+
+rnull.encode.bytes = 0
+
+rnull.decode = function (buf, offset) {
+  if (!offset) offset = 0
+  const oldOffset = offset
+  const len = buf.readUInt16BE(offset)
+
+  offset += 2
+
+  const data = buf.slice(offset, offset + len)
+  offset += len
+
+  rnull.decode.bytes = offset - oldOffset
+  return data
+}
+
+rnull.decode.bytes = 0
+
+rnull.encodingLength = function (data) {
+  if (!data) return 2
+  return (Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data)) + 2
+}
+
+const rhinfo = exports.hinfo = {}
+
+rhinfo.encode = function (data, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(rhinfo.encodingLength(data))
+  if (!offset) offset = 0
+
+  const oldOffset = offset
+  offset += 2
+  string.encode(data.cpu, buf, offset)
+  offset += string.encode.bytes
+  string.encode(data.os, buf, offset)
+  offset += string.encode.bytes
+  buf.writeUInt16BE(offset - oldOffset - 2, oldOffset)
+  rhinfo.encode.bytes = offset - oldOffset
+  return buf
+}
+
+rhinfo.encode.bytes = 0
+
+rhinfo.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  const oldOffset = offset
+
+  const data = {}
+  offset += 2
+  data.cpu = string.decode(buf, offset)
+  offset += string.decode.bytes
+  data.os = string.decode(buf, offset)
+  offset += string.decode.bytes
+  rhinfo.decode.bytes = offset - oldOffset
+  return data
+}
+
+rhinfo.decode.bytes = 0
+
+rhinfo.encodingLength = function (data) {
+  return string.encodingLength(data.cpu) + string.encodingLength(data.os) + 2
+}
+
+const rptr = exports.ptr = {}
+const rcname = exports.cname = rptr
+const rdname = exports.dname = rptr
+
+rptr.encode = function (data, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(rptr.encodingLength(data))
+  if (!offset) offset = 0
+
+  name.encode(data, buf, offset + 2)
+  buf.writeUInt16BE(name.encode.bytes, offset)
+  rptr.encode.bytes = name.encode.bytes + 2
+  return buf
+}
+
+rptr.encode.bytes = 0
+
+rptr.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  const data = name.decode(buf, offset + 2)
+  rptr.decode.bytes = name.decode.bytes + 2
+  return data
+}
+
+rptr.decode.bytes = 0
+
+rptr.encodingLength = function (data) {
+  return name.encodingLength(data) + 2
+}
+
+const rsrv = exports.srv = {}
+
+rsrv.encode = function (data, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(rsrv.encodingLength(data))
+  if (!offset) offset = 0
+
+  buf.writeUInt16BE(data.priority || 0, offset + 2)
+  buf.writeUInt16BE(data.weight || 0, offset + 4)
+  buf.writeUInt16BE(data.port || 0, offset + 6)
+  name.encode(data.target, buf, offset + 8)
+
+  const len = name.encode.bytes + 6
+  buf.writeUInt16BE(len, offset)
+
+  rsrv.encode.bytes = len + 2
+  return buf
+}
+
+rsrv.encode.bytes = 0
+
+rsrv.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  const len = buf.readUInt16BE(offset)
+
+  const data = {}
+  data.priority = buf.readUInt16BE(offset + 2)
+  data.weight = buf.readUInt16BE(offset + 4)
+  data.port = buf.readUInt16BE(offset + 6)
+  data.target = name.decode(buf, offset + 8)
+
+  rsrv.decode.bytes = len + 2
+  return data
+}
+
+rsrv.decode.bytes = 0
+
+rsrv.encodingLength = function (data) {
+  return 8 + name.encodingLength(data.target)
+}
+
+const rcaa = exports.caa = {}
+
+rcaa.ISSUER_CRITICAL = 1 << 7
+
+rcaa.encode = function (data, buf, offset) {
+  const len = rcaa.encodingLength(data)
+
+  if (!buf) buf = Buffer.allocUnsafe(rcaa.encodingLength(data))
+  if (!offset) offset = 0
+
+  if (data.issuerCritical) {
+    data.flags = rcaa.ISSUER_CRITICAL
+  }
+
+  buf.writeUInt16BE(len - 2, offset)
+  offset += 2
+  buf.writeUInt8(data.flags || 0, offset)
+  offset += 1
+  string.encode(data.tag, buf, offset)
+  offset += string.encode.bytes
+  buf.write(data.value, offset)
+  offset += Buffer.byteLength(data.value)
+
+  rcaa.encode.bytes = len
+  return buf
+}
+
+rcaa.encode.bytes = 0
+
+rcaa.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  const len = buf.readUInt16BE(offset)
+  offset += 2
+
+  const oldOffset = offset
+  const data = {}
+  data.flags = buf.readUInt8(offset)
+  offset += 1
+  data.tag = string.decode(buf, offset)
+  offset += string.decode.bytes
+  data.value = buf.toString('utf-8', offset, oldOffset + len)
+
+  data.issuerCritical = !!(data.flags & rcaa.ISSUER_CRITICAL)
+
+  rcaa.decode.bytes = len + 2
+
+  return data
+}
+
+rcaa.decode.bytes = 0
+
+rcaa.encodingLength = function (data) {
+  return string.encodingLength(data.tag) + string.encodingLength(data.value) + 2
+}
+
+const rmx = exports.mx = {}
+
+rmx.encode = function (data, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(rmx.encodingLength(data))
+  if (!offset) offset = 0
+
+  const oldOffset = offset
+  offset += 2
+  buf.writeUInt16BE(data.preference || 0, offset)
+  offset += 2
+  name.encode(data.exchange, buf, offset)
+  offset += name.encode.bytes
+
+  buf.writeUInt16BE(offset - oldOffset - 2, oldOffset)
+  rmx.encode.bytes = offset - oldOffset
+  return buf
+}
+
+rmx.encode.bytes = 0
+
+rmx.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  const oldOffset = offset
+
+  const data = {}
+  offset += 2
+  data.preference = buf.readUInt16BE(offset)
+  offset += 2
+  data.exchange = name.decode(buf, offset)
+  offset += name.decode.bytes
+
+  rmx.decode.bytes = offset - oldOffset
+  return data
+}
+
+rmx.encodingLength = function (data) {
+  return 4 + name.encodingLength(data.exchange)
+}
+
+const ra = exports.a = {}
+
+ra.encode = function (host, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(ra.encodingLength(host))
+  if (!offset) offset = 0
+
+  buf.writeUInt16BE(4, offset)
+  offset += 2
+  ip.toBuffer(host, buf, offset)
+  ra.encode.bytes = 6
+  return buf
+}
+
+ra.encode.bytes = 0
+
+ra.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  offset += 2
+  const host = ip.toString(buf, offset, 4)
+  ra.decode.bytes = 6
+  return host
+}
+
+ra.decode.bytes = 0
+
+ra.encodingLength = function () {
+  return 6
+}
+
+const raaaa = exports.aaaa = {}
+
+raaaa.encode = function (host, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(raaaa.encodingLength(host))
+  if (!offset) offset = 0
+
+  buf.writeUInt16BE(16, offset)
+  offset += 2
+  ip.toBuffer(host, buf, offset)
+  raaaa.encode.bytes = 18
+  return buf
+}
+
+raaaa.encode.bytes = 0
+
+raaaa.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  offset += 2
+  const host = ip.toString(buf, offset, 16)
+  raaaa.decode.bytes = 18
+  return host
+}
+
+raaaa.decode.bytes = 0
+
+raaaa.encodingLength = function () {
+  return 18
+}
+
+const roption = exports.option = {}
+
+roption.encode = function (option, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(roption.encodingLength(option))
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  const code = optioncodes.toCode(option.code)
+  buf.writeUInt16BE(code, offset)
+  offset += 2
+  if (option.data) {
+    buf.writeUInt16BE(option.data.length, offset)
+    offset += 2
+    option.data.copy(buf, offset)
+    offset += option.data.length
+  } else {
+    switch (code) {
+      // case 3: NSID.  No encode makes sense.
+      // case 5,6,7: Not implementable
+      case 8: // ECS
+        // note: do IP math before calling
+        const spl = option.sourcePrefixLength || 0
+        const fam = option.family || (ip.isV4Format(option.ip) ? 1 : 2)
+        const ipBuf = ip.toBuffer(option.ip)
+        const ipLen = Math.ceil(spl / 8)
+        buf.writeUInt16BE(ipLen + 4, offset)
+        offset += 2
+        buf.writeUInt16BE(fam, offset)
+        offset += 2
+        buf.writeUInt8(spl, offset++)
+        buf.writeUInt8(option.scopePrefixLength || 0, offset++)
+
+        ipBuf.copy(buf, offset, 0, ipLen)
+        offset += ipLen
+        break
+      // case 9: EXPIRE (experimental)
+      // case 10: COOKIE.  No encode makes sense.
+      case 11: // KEEP-ALIVE
+        if (option.timeout) {
+          buf.writeUInt16BE(2, offset)
+          offset += 2
+          buf.writeUInt16BE(option.timeout, offset)
+          offset += 2
+        } else {
+          buf.writeUInt16BE(0, offset)
+          offset += 2
+        }
+        break
+      case 12: // PADDING
+        const len = option.length || 0
+        buf.writeUInt16BE(len, offset)
+        offset += 2
+        buf.fill(0, offset, offset + len)
+        offset += len
+        break
+      // case 13:  CHAIN.  Experimental.
+      case 14: // KEY-TAG
+        const tagsLen = option.tags.length * 2
+        buf.writeUInt16BE(tagsLen, offset)
+        offset += 2
+        for (const tag of option.tags) {
+          buf.writeUInt16BE(tag, offset)
+          offset += 2
+        }
+        break
+      default:
+        throw new Error(`Unknown roption code: ${option.code}`)
+    }
+  }
+
+  roption.encode.bytes = offset - oldOffset
+  return buf
+}
+
+roption.encode.bytes = 0
+
+roption.decode = function (buf, offset) {
+  if (!offset) offset = 0
+  const option = {}
+  option.code = buf.readUInt16BE(offset)
+  option.type = optioncodes.toString(option.code)
+  offset += 2
+  const len = buf.readUInt16BE(offset)
+  offset += 2
+  option.data = buf.slice(offset, offset + len)
+  switch (option.code) {
+    // case 3: NSID.  No decode makes sense.
+    case 8: // ECS
+      option.family = buf.readUInt16BE(offset)
+      offset += 2
+      option.sourcePrefixLength = buf.readUInt8(offset++)
+      option.scopePrefixLength = buf.readUInt8(offset++)
+      const padded = Buffer.alloc((option.family === 1) ? 4 : 16)
+      buf.copy(padded, 0, offset, offset + len - 4)
+      option.ip = ip.toString(padded)
+      break
+    // case 12: Padding.  No decode makes sense.
+    case 11: // KEEP-ALIVE
+      if (len > 0) {
+        option.timeout = buf.readUInt16BE(offset)
+        offset += 2
+      }
+      break
+    case 14:
+      option.tags = []
+      for (let i = 0; i < len; i += 2) {
+        option.tags.push(buf.readUInt16BE(offset))
+        offset += 2
+      }
+    // don't worry about default.  caller will use data if desired
+  }
+
+  roption.decode.bytes = len + 4
+  return option
+}
+
+roption.decode.bytes = 0
+
+roption.encodingLength = function (option) {
+  if (option.data) {
+    return option.data.length + 4
+  }
+  const code = optioncodes.toCode(option.code)
+  switch (code) {
+    case 8: // ECS
+      const spl = option.sourcePrefixLength || 0
+      return Math.ceil(spl / 8) + 8
+    case 11: // KEEP-ALIVE
+      return (typeof option.timeout === 'number') ? 6 : 4
+    case 12: // PADDING
+      return option.length + 4
+    case 14: // KEY-TAG
+      return 4 + (option.tags.length * 2)
+  }
+  throw new Error(`Unknown roption code: ${option.code}`)
+}
+
+const ropt = exports.opt = {}
+
+ropt.encode = function (options, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(ropt.encodingLength(options))
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  const rdlen = encodingLengthList(options, roption)
+  buf.writeUInt16BE(rdlen, offset)
+  offset = encodeList(options, roption, buf, offset + 2)
+
+  ropt.encode.bytes = offset - oldOffset
+  return buf
+}
+
+ropt.encode.bytes = 0
+
+ropt.decode = function (buf, offset) {
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  const options = []
+  let rdlen = buf.readUInt16BE(offset)
+  offset += 2
+  let o = 0
+  while (rdlen > 0) {
+    options[o++] = roption.decode(buf, offset)
+    offset += roption.decode.bytes
+    rdlen -= roption.decode.bytes
+  }
+  ropt.decode.bytes = offset - oldOffset
+  return options
+}
+
+ropt.decode.bytes = 0
+
+ropt.encodingLength = function (options) {
+  return 2 + encodingLengthList(options || [], roption)
+}
+
+const rdnskey = exports.dnskey = {}
+
+rdnskey.PROTOCOL_DNSSEC = 3
+rdnskey.ZONE_KEY = 0x80
+rdnskey.SECURE_ENTRYPOINT = 0x8000
+
+rdnskey.encode = function (key, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(rdnskey.encodingLength(key))
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  const keydata = key.key
+  if (!Buffer.isBuffer(keydata)) {
+    throw new Error('Key must be a Buffer')
+  }
+
+  offset += 2 // Leave space for length
+  buf.writeUInt16BE(key.flags, offset)
+  offset += 2
+  buf.writeUInt8(rdnskey.PROTOCOL_DNSSEC, offset)
+  offset += 1
+  buf.writeUInt8(key.algorithm, offset)
+  offset += 1
+  keydata.copy(buf, offset, 0, keydata.length)
+  offset += keydata.length
+
+  rdnskey.encode.bytes = offset - oldOffset
+  buf.writeUInt16BE(rdnskey.encode.bytes - 2, oldOffset)
+  return buf
+}
+
+rdnskey.encode.bytes = 0
+
+rdnskey.decode = function (buf, offset) {
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  var key = {}
+  var length = buf.readUInt16BE(offset)
+  offset += 2
+  key.flags = buf.readUInt16BE(offset)
+  offset += 2
+  if (buf.readUInt8(offset) !== rdnskey.PROTOCOL_DNSSEC) {
+    throw new Error('Protocol must be 3')
+  }
+  offset += 1
+  key.algorithm = buf.readUInt8(offset)
+  offset += 1
+  key.key = buf.slice(offset, oldOffset + length + 2)
+  offset += key.key.length
+  rdnskey.decode.bytes = offset - oldOffset
+  return key
+}
+
+rdnskey.decode.bytes = 0
+
+rdnskey.encodingLength = function (key) {
+  return 6 + Buffer.byteLength(key.key)
+}
+
+const rrrsig = exports.rrsig = {}
+
+rrrsig.encode = function (sig, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(rrrsig.encodingLength(sig))
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  const signature = sig.signature
+  if (!Buffer.isBuffer(signature)) {
+    throw new Error('Signature must be a Buffer')
+  }
+
+  offset += 2 // Leave space for length
+  buf.writeUInt16BE(types.toType(sig.typeCovered), offset)
+  offset += 2
+  buf.writeUInt8(sig.algorithm, offset)
+  offset += 1
+  buf.writeUInt8(sig.labels, offset)
+  offset += 1
+  buf.writeUInt32BE(sig.originalTTL, offset)
+  offset += 4
+  buf.writeUInt32BE(sig.expiration, offset)
+  offset += 4
+  buf.writeUInt32BE(sig.inception, offset)
+  offset += 4
+  buf.writeUInt16BE(sig.keyTag, offset)
+  offset += 2
+  name.encode(sig.signersName, buf, offset)
+  offset += name.encode.bytes
+  signature.copy(buf, offset, 0, signature.length)
+  offset += signature.length
+
+  rrrsig.encode.bytes = offset - oldOffset
+  buf.writeUInt16BE(rrrsig.encode.bytes - 2, oldOffset)
+  return buf
+}
+
+rrrsig.encode.bytes = 0
+
+rrrsig.decode = function (buf, offset) {
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  var sig = {}
+  var length = buf.readUInt16BE(offset)
+  offset += 2
+  sig.typeCovered = types.toString(buf.readUInt16BE(offset))
+  offset += 2
+  sig.algorithm = buf.readUInt8(offset)
+  offset += 1
+  sig.labels = buf.readUInt8(offset)
+  offset += 1
+  sig.originalTTL = buf.readUInt32BE(offset)
+  offset += 4
+  sig.expiration = buf.readUInt32BE(offset)
+  offset += 4
+  sig.inception = buf.readUInt32BE(offset)
+  offset += 4
+  sig.keyTag = buf.readUInt16BE(offset)
+  offset += 2
+  sig.signersName = name.decode(buf, offset)
+  offset += name.decode.bytes
+  sig.signature = buf.slice(offset, oldOffset + length + 2)
+  offset += sig.signature.length
+  rrrsig.decode.bytes = offset - oldOffset
+  return sig
+}
+
+rrrsig.decode.bytes = 0
+
+rrrsig.encodingLength = function (sig) {
+  return 20 +
+    name.encodingLength(sig.signersName) +
+    Buffer.byteLength(sig.signature)
+}
+
+const rrp = exports.rp = {}
+
+rrp.encode = function (data, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(rrp.encodingLength(data))
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  offset += 2 // Leave space for length
+  name.encode(data.mbox || '.', buf, offset)
+  offset += name.encode.bytes
+  name.encode(data.txt || '.', buf, offset)
+  offset += name.encode.bytes
+  rrp.encode.bytes = offset - oldOffset
+  buf.writeUInt16BE(rrp.encode.bytes - 2, oldOffset)
+  return buf
+}
+
+rrp.encode.bytes = 0
+
+rrp.decode = function (buf, offset) {
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  const data = {}
+  offset += 2
+  data.mbox = name.decode(buf, offset) || '.'
+  offset += name.decode.bytes
+  data.txt = name.decode(buf, offset) || '.'
+  offset += name.decode.bytes
+  rrp.decode.bytes = offset - oldOffset
+  return data
+}
+
+rrp.decode.bytes = 0
+
+rrp.encodingLength = function (data) {
+  return 2 + name.encodingLength(data.mbox || '.') + name.encodingLength(data.txt || '.')
+}
+
+const typebitmap = {}
+
+typebitmap.encode = function (typelist, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(typebitmap.encodingLength(typelist))
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  var typesByWindow = []
+  for (var i = 0; i < typelist.length; i++) {
+    var typeid = types.toType(typelist[i])
+    if (typesByWindow[typeid >> 8] === undefined) {
+      typesByWindow[typeid >> 8] = []
+    }
+    typesByWindow[typeid >> 8][(typeid >> 3) & 0x1F] |= 1 << (7 - (typeid & 0x7))
+  }
+
+  for (i = 0; i < typesByWindow.length; i++) {
+    if (typesByWindow[i] !== undefined) {
+      var windowBuf = Buffer.from(typesByWindow[i])
+      buf.writeUInt8(i, offset)
+      offset += 1
+      buf.writeUInt8(windowBuf.length, offset)
+      offset += 1
+      windowBuf.copy(buf, offset)
+      offset += windowBuf.length
+    }
+  }
+
+  typebitmap.encode.bytes = offset - oldOffset
+  return buf
+}
+
+typebitmap.encode.bytes = 0
+
+typebitmap.decode = function (buf, offset, length) {
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  var typelist = []
+  while (offset - oldOffset < length) {
+    var window = buf.readUInt8(offset)
+    offset += 1
+    var windowLength = buf.readUInt8(offset)
+    offset += 1
+    for (var i = 0; i < windowLength; i++) {
+      var b = buf.readUInt8(offset + i)
+      for (var j = 0; j < 8; j++) {
+        if (b & (1 << (7 - j))) {
+          var typeid = types.toString((window << 8) | (i << 3) | j)
+          typelist.push(typeid)
+        }
+      }
+    }
+    offset += windowLength
+  }
+
+  typebitmap.decode.bytes = offset - oldOffset
+  return typelist
+}
+
+typebitmap.decode.bytes = 0
+
+typebitmap.encodingLength = function (typelist) {
+  var extents = []
+  for (var i = 0; i < typelist.length; i++) {
+    var typeid = types.toType(typelist[i])
+    extents[typeid >> 8] = Math.max(extents[typeid >> 8] || 0, typeid & 0xFF)
+  }
+
+  var len = 0
+  for (i = 0; i < extents.length; i++) {
+    if (extents[i] !== undefined) {
+      len += 2 + Math.ceil((extents[i] + 1) / 8)
+    }
+  }
+
+  return len
+}
+
+const rnsec = exports.nsec = {}
+
+rnsec.encode = function (record, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(rnsec.encodingLength(record))
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  offset += 2 // Leave space for length
+  name.encode(record.nextDomain, buf, offset)
+  offset += name.encode.bytes
+  typebitmap.encode(record.rrtypes, buf, offset)
+  offset += typebitmap.encode.bytes
+
+  rnsec.encode.bytes = offset - oldOffset
+  buf.writeUInt16BE(rnsec.encode.bytes - 2, oldOffset)
+  return buf
+}
+
+rnsec.encode.bytes = 0
+
+rnsec.decode = function (buf, offset) {
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  var record = {}
+  var length = buf.readUInt16BE(offset)
+  offset += 2
+  record.nextDomain = name.decode(buf, offset)
+  offset += name.decode.bytes
+  record.rrtypes = typebitmap.decode(buf, offset, length - (offset - oldOffset))
+  offset += typebitmap.decode.bytes
+
+  rnsec.decode.bytes = offset - oldOffset
+  return record
+}
+
+rnsec.decode.bytes = 0
+
+rnsec.encodingLength = function (record) {
+  return 2 +
+    name.encodingLength(record.nextDomain) +
+    typebitmap.encodingLength(record.rrtypes)
+}
+
+const rnsec3 = exports.nsec3 = {}
+
+rnsec3.encode = function (record, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(rnsec3.encodingLength(record))
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  const salt = record.salt
+  if (!Buffer.isBuffer(salt)) {
+    throw new Error('salt must be a Buffer')
+  }
+
+  const nextDomain = record.nextDomain
+  if (!Buffer.isBuffer(nextDomain)) {
+    throw new Error('nextDomain must be a Buffer')
+  }
+
+  offset += 2 // Leave space for length
+  buf.writeUInt8(record.algorithm, offset)
+  offset += 1
+  buf.writeUInt8(record.flags, offset)
+  offset += 1
+  buf.writeUInt16BE(record.iterations, offset)
+  offset += 2
+  buf.writeUInt8(salt.length, offset)
+  offset += 1
+  salt.copy(buf, offset, 0, salt.length)
+  offset += salt.length
+  buf.writeUInt8(nextDomain.length, offset)
+  offset += 1
+  nextDomain.copy(buf, offset, 0, nextDomain.length)
+  offset += nextDomain.length
+  typebitmap.encode(record.rrtypes, buf, offset)
+  offset += typebitmap.encode.bytes
+
+  rnsec3.encode.bytes = offset - oldOffset
+  buf.writeUInt16BE(rnsec3.encode.bytes - 2, oldOffset)
+  return buf
+}
+
+rnsec3.encode.bytes = 0
+
+rnsec3.decode = function (buf, offset) {
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  var record = {}
+  var length = buf.readUInt16BE(offset)
+  offset += 2
+  record.algorithm = buf.readUInt8(offset)
+  offset += 1
+  record.flags = buf.readUInt8(offset)
+  offset += 1
+  record.iterations = buf.readUInt16BE(offset)
+  offset += 2
+  const saltLength = buf.readUInt8(offset)
+  offset += 1
+  record.salt = buf.slice(offset, offset + saltLength)
+  offset += saltLength
+  const hashLength = buf.readUInt8(offset)
+  offset += 1
+  record.nextDomain = buf.slice(offset, offset + hashLength)
+  offset += hashLength
+  record.rrtypes = typebitmap.decode(buf, offset, length - (offset - oldOffset))
+  offset += typebitmap.decode.bytes
+
+  rnsec3.decode.bytes = offset - oldOffset
+  return record
+}
+
+rnsec3.decode.bytes = 0
+
+rnsec3.encodingLength = function (record) {
+  return 8 +
+    record.salt.length +
+    record.nextDomain.length +
+    typebitmap.encodingLength(record.rrtypes)
+}
+
+const rds = exports.ds = {}
+
+rds.encode = function (digest, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(rds.encodingLength(digest))
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  const digestdata = digest.digest
+  if (!Buffer.isBuffer(digestdata)) {
+    throw new Error('Digest must be a Buffer')
+  }
+
+  offset += 2 // Leave space for length
+  buf.writeUInt16BE(digest.keyTag, offset)
+  offset += 2
+  buf.writeUInt8(digest.algorithm, offset)
+  offset += 1
+  buf.writeUInt8(digest.digestType, offset)
+  offset += 1
+  digestdata.copy(buf, offset, 0, digestdata.length)
+  offset += digestdata.length
+
+  rds.encode.bytes = offset - oldOffset
+  buf.writeUInt16BE(rds.encode.bytes - 2, oldOffset)
+  return buf
+}
+
+rds.encode.bytes = 0
+
+rds.decode = function (buf, offset) {
+  if (!offset) offset = 0
+  const oldOffset = offset
+
+  var digest = {}
+  var length = buf.readUInt16BE(offset)
+  offset += 2
+  digest.keyTag = buf.readUInt16BE(offset)
+  offset += 2
+  digest.algorithm = buf.readUInt8(offset)
+  offset += 1
+  digest.digestType = buf.readUInt8(offset)
+  offset += 1
+  digest.digest = buf.slice(offset, oldOffset + length + 2)
+  offset += digest.digest.length
+  rds.decode.bytes = offset - oldOffset
+  return digest
+}
+
+rds.decode.bytes = 0
+
+rds.encodingLength = function (digest) {
+  return 6 + Buffer.byteLength(digest.digest)
+}
+
+const renc = exports.record = function (type) {
+  switch (type.toUpperCase()) {
+    case 'A': return ra
+    case 'PTR': return rptr
+    case 'CNAME': return rcname
+    case 'DNAME': return rdname
+    case 'TXT': return rtxt
+    case 'NULL': return rnull
+    case 'AAAA': return raaaa
+    case 'SRV': return rsrv
+    case 'HINFO': return rhinfo
+    case 'CAA': return rcaa
+    case 'NS': return rns
+    case 'SOA': return rsoa
+    case 'MX': return rmx
+    case 'OPT': return ropt
+    case 'DNSKEY': return rdnskey
+    case 'RRSIG': return rrrsig
+    case 'RP': return rrp
+    case 'NSEC': return rnsec
+    case 'NSEC3': return rnsec3
+    case 'DS': return rds
+  }
+  return runknown
+}
+
+const answer = exports.answer = {}
+
+answer.encode = function (a, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(answer.encodingLength(a))
+  if (!offset) offset = 0
+
+  const oldOffset = offset
+
+  name.encode(a.name, buf, offset)
+  offset += name.encode.bytes
+
+  buf.writeUInt16BE(types.toType(a.type), offset)
+
+  if (a.type.toUpperCase() === 'OPT') {
+    if (a.name !== '.') {
+      throw new Error('OPT name must be root.')
+    }
+    buf.writeUInt16BE(a.udpPayloadSize || 4096, offset + 2)
+    buf.writeUInt8(a.extendedRcode || 0, offset + 4)
+    buf.writeUInt8(a.ednsVersion || 0, offset + 5)
+    buf.writeUInt16BE(a.flags || 0, offset + 6)
+
+    offset += 8
+    ropt.encode(a.options || [], buf, offset)
+    offset += ropt.encode.bytes
+  } else {
+    let klass = classes.toClass(a.class === undefined ? 'IN' : a.class)
+    if (a.flush) klass |= FLUSH_MASK // the 1st bit of the class is the flush bit
+    buf.writeUInt16BE(klass, offset + 2)
+    buf.writeUInt32BE(a.ttl || 0, offset + 4)
+
+    offset += 8
+    const enc = renc(a.type)
+    enc.encode(a.data, buf, offset)
+    offset += enc.encode.bytes
+  }
+
+  answer.encode.bytes = offset - oldOffset
+  return buf
+}
+
+answer.encode.bytes = 0
+
+answer.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  const a = {}
+  const oldOffset = offset
+
+  a.name = name.decode(buf, offset)
+  offset += name.decode.bytes
+  a.type = types.toString(buf.readUInt16BE(offset))
+  if (a.type === 'OPT') {
+    a.udpPayloadSize = buf.readUInt16BE(offset + 2)
+    a.extendedRcode = buf.readUInt8(offset + 4)
+    a.ednsVersion = buf.readUInt8(offset + 5)
+    a.flags = buf.readUInt16BE(offset + 6)
+    a.flag_do = ((a.flags >> 15) & 0x1) === 1
+    a.options = ropt.decode(buf, offset + 8)
+    offset += 8 + ropt.decode.bytes
+  } else {
+    const klass = buf.readUInt16BE(offset + 2)
+    a.ttl = buf.readUInt32BE(offset + 4)
+
+    a.class = classes.toString(klass & NOT_FLUSH_MASK)
+    a.flush = !!(klass & FLUSH_MASK)
+
+    const enc = renc(a.type)
+    a.data = enc.decode(buf, offset + 8)
+    offset += 8 + enc.decode.bytes
+  }
+
+  answer.decode.bytes = offset - oldOffset
+  return a
+}
+
+answer.decode.bytes = 0
+
+answer.encodingLength = function (a) {
+  const data = (a.data !== null && a.data !== undefined) ? a.data : a.options
+  return name.encodingLength(a.name) + 8 + renc(a.type).encodingLength(data)
+}
+
+const question = exports.question = {}
+
+question.encode = function (q, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(question.encodingLength(q))
+  if (!offset) offset = 0
+
+  const oldOffset = offset
+
+  name.encode(q.name, buf, offset)
+  offset += name.encode.bytes
+
+  buf.writeUInt16BE(types.toType(q.type), offset)
+  offset += 2
+
+  buf.writeUInt16BE(classes.toClass(q.class === undefined ? 'IN' : q.class), offset)
+  offset += 2
+
+  question.encode.bytes = offset - oldOffset
+  return q
+}
+
+question.encode.bytes = 0
+
+question.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  const oldOffset = offset
+  const q = {}
+
+  q.name = name.decode(buf, offset)
+  offset += name.decode.bytes
+
+  q.type = types.toString(buf.readUInt16BE(offset))
+  offset += 2
+
+  q.class = classes.toString(buf.readUInt16BE(offset))
+  offset += 2
+
+  const qu = !!(q.class & QU_MASK)
+  if (qu) q.class &= NOT_QU_MASK
+
+  question.decode.bytes = offset - oldOffset
+  return q
+}
+
+question.decode.bytes = 0
+
+question.encodingLength = function (q) {
+  return name.encodingLength(q.name) + 4
+}
+
+exports.AUTHORITATIVE_ANSWER = 1 << 10
+exports.TRUNCATED_RESPONSE = 1 << 9
+exports.RECURSION_DESIRED = 1 << 8
+exports.RECURSION_AVAILABLE = 1 << 7
+exports.AUTHENTIC_DATA = 1 << 5
+exports.CHECKING_DISABLED = 1 << 4
+exports.DNSSEC_OK = 1 << 15
+
+exports.encode = function (result, buf, offset) {
+  if (!buf) buf = Buffer.allocUnsafe(exports.encodingLength(result))
+  if (!offset) offset = 0
+
+  const oldOffset = offset
+
+  if (!result.questions) result.questions = []
+  if (!result.answers) result.answers = []
+  if (!result.authorities) result.authorities = []
+  if (!result.additionals) result.additionals = []
+
+  header.encode(result, buf, offset)
+  offset += header.encode.bytes
+
+  offset = encodeList(result.questions, question, buf, offset)
+  offset = encodeList(result.answers, answer, buf, offset)
+  offset = encodeList(result.authorities, answer, buf, offset)
+  offset = encodeList(result.additionals, answer, buf, offset)
+
+  exports.encode.bytes = offset - oldOffset
+
+  return buf
+}
+
+exports.encode.bytes = 0
+
+exports.decode = function (buf, offset) {
+  if (!offset) offset = 0
+
+  const oldOffset = offset
+  const result = header.decode(buf, offset)
+  offset += header.decode.bytes
+
+  offset = decodeList(result.questions, question, buf, offset)
+  offset = decodeList(result.answers, answer, buf, offset)
+  offset = decodeList(result.authorities, answer, buf, offset)
+  offset = decodeList(result.additionals, answer, buf, offset)
+
+  exports.decode.bytes = offset - oldOffset
+
+  return result
+}
+
+exports.decode.bytes = 0
+
+exports.encodingLength = function (result) {
+  return header.encodingLength(result) +
+    encodingLengthList(result.questions || [], question) +
+    encodingLengthList(result.answers || [], answer) +
+    encodingLengthList(result.authorities || [], answer) +
+    encodingLengthList(result.additionals || [], answer)
+}
+
+exports.streamEncode = function (result) {
+  const buf = exports.encode(result)
+  const sbuf = Buffer.allocUnsafe(2)
+  sbuf.writeUInt16BE(buf.byteLength)
+  const combine = Buffer.concat([sbuf, buf])
+  exports.streamEncode.bytes = combine.byteLength
+  return combine
+}
+
+exports.streamEncode.bytes = 0
+
+exports.streamDecode = function (sbuf) {
+  const len = sbuf.readUInt16BE(0)
+  if (sbuf.byteLength < len + 2) {
+    // not enough data
+    return null
+  }
+  const result = exports.decode(sbuf.slice(2))
+  exports.streamDecode.bytes = exports.decode.bytes
+  return result
+}
+
+exports.streamDecode.bytes = 0
+
+function encodingLengthList (list, enc) {
+  let len = 0
+  for (let i = 0; i < list.length; i++) len += enc.encodingLength(list[i])
+  return len
+}
+
+function encodeList (list, enc, buf, offset) {
+  for (let i = 0; i < list.length; i++) {
+    enc.encode(list[i], buf, offset)
+    offset += enc.encode.bytes
+  }
+  return offset
+}
+
+function decodeList (list, enc, buf, offset) {
+  for (let i = 0; i < list.length; i++) {
+    list[i] = enc.decode(buf, offset)
+    offset += enc.decode.bytes
+  }
+  return offset
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/opcodes.js
@@ -0,0 +1,50 @@
+'use strict'
+
+/*
+ * Traditional DNS header OPCODEs (4-bits) defined by IANA in
+ * https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5
+ */
+
+exports.toString = function (opcode) {
+  switch (opcode) {
+    case 0: return 'QUERY'
+    case 1: return 'IQUERY'
+    case 2: return 'STATUS'
+    case 3: return 'OPCODE_3'
+    case 4: return 'NOTIFY'
+    case 5: return 'UPDATE'
+    case 6: return 'OPCODE_6'
+    case 7: return 'OPCODE_7'
+    case 8: return 'OPCODE_8'
+    case 9: return 'OPCODE_9'
+    case 10: return 'OPCODE_10'
+    case 11: return 'OPCODE_11'
+    case 12: return 'OPCODE_12'
+    case 13: return 'OPCODE_13'
+    case 14: return 'OPCODE_14'
+    case 15: return 'OPCODE_15'
+  }
+  return 'OPCODE_' + opcode
+}
+
+exports.toOpcode = function (code) {
+  switch (code.toUpperCase()) {
+    case 'QUERY': return 0
+    case 'IQUERY': return 1
+    case 'STATUS': return 2
+    case 'OPCODE_3': return 3
+    case 'NOTIFY': return 4
+    case 'UPDATE': return 5
+    case 'OPCODE_6': return 6
+    case 'OPCODE_7': return 7
+    case 'OPCODE_8': return 8
+    case 'OPCODE_9': return 9
+    case 'OPCODE_10': return 10
+    case 'OPCODE_11': return 11
+    case 'OPCODE_12': return 12
+    case 'OPCODE_13': return 13
+    case 'OPCODE_14': return 14
+    case 'OPCODE_15': return 15
+  }
+  return 0
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/optioncodes.js
@@ -0,0 +1,59 @@
+'use strict'
+
+exports.toString = function (type) {
+  switch (type) {
+    // list at
+    // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11
+    case 1: return 'LLQ'
+    case 2: return 'UL'
+    case 3: return 'NSID'
+    case 5: return 'DAU'
+    case 6: return 'DHU'
+    case 7: return 'N3U'
+    case 8: return 'CLIENT_SUBNET'
+    case 9: return 'EXPIRE'
+    case 10: return 'COOKIE'
+    case 11: return 'TCP_KEEPALIVE'
+    case 12: return 'PADDING'
+    case 13: return 'CHAIN'
+    case 14: return 'KEY_TAG'
+    case 26946: return 'DEVICEID'
+  }
+  if (type < 0) {
+    return null
+  }
+  return `OPTION_${type}`
+}
+
+exports.toCode = function (name) {
+  if (typeof name === 'number') {
+    return name
+  }
+  if (!name) {
+    return -1
+  }
+  switch (name.toUpperCase()) {
+    case 'OPTION_0': return 0
+    case 'LLQ': return 1
+    case 'UL': return 2
+    case 'NSID': return 3
+    case 'OPTION_4': return 4
+    case 'DAU': return 5
+    case 'DHU': return 6
+    case 'N3U': return 7
+    case 'CLIENT_SUBNET': return 8
+    case 'EXPIRE': return 9
+    case 'COOKIE': return 10
+    case 'TCP_KEEPALIVE': return 11
+    case 'PADDING': return 12
+    case 'CHAIN': return 13
+    case 'KEY_TAG': return 14
+    case 'DEVICEID': return 26946
+    case 'OPTION_65535': return 65535
+  }
+  const m = name.match(/_(\d+)$/)
+  if (m) {
+    return parseInt(m[1], 10)
+  }
+  return -1
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/package.json
@@ -0,0 +1,48 @@
+{
+  "name": "dns-packet",
+  "version": "5.2.1",
+  "description": "An abstract-encoding compliant module for encoding / decoding DNS packets",
+  "author": "Mathias Buus",
+  "license": "MIT",
+  "repository": "mafintosh/dns-packet",
+  "homepage": "https://github.com/mafintosh/dns-packet",
+  "engines": {
+    "node": ">=6"
+  },
+  "scripts": {
+    "clean": "rm -rf coverage .nyc_output/",
+    "lint": "eslint --color *.js examples/*.js",
+    "pretest": "npm run lint",
+    "test": "tape test.js",
+    "coverage": "nyc -r html npm test"
+  },
+  "dependencies": {
+    "ip": "^1.1.5"
+  },
+  "devDependencies": {
+    "eslint": "^5.14.1",
+    "eslint-config-standard": "^12.0.0",
+    "eslint-plugin-import": "^2.16.0",
+    "eslint-plugin-node": "^8.0.1",
+    "eslint-plugin-promise": "^4.0.1",
+    "eslint-plugin-standard": "^4.0.0",
+    "nyc": "^13.3.0",
+    "tape": "^4.10.1"
+  },
+  "keywords": [
+    "dns",
+    "packet",
+    "encodings",
+    "encoding",
+    "encoder",
+    "abstract-encoding"
+  ],
+  "files": [
+    "index.js",
+    "types.js",
+    "rcodes.js",
+    "opcodes.js",
+    "classes.js",
+    "optioncodes.js"
+  ]
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/rcodes.js
@@ -0,0 +1,50 @@
+'use strict'
+
+/*
+ * Traditional DNS header RCODEs (4-bits) defined by IANA in
+ * https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
+ */
+
+exports.toString = function (rcode) {
+  switch (rcode) {
+    case 0: return 'NOERROR'
+    case 1: return 'FORMERR'
+    case 2: return 'SERVFAIL'
+    case 3: return 'NXDOMAIN'
+    case 4: return 'NOTIMP'
+    case 5: return 'REFUSED'
+    case 6: return 'YXDOMAIN'
+    case 7: return 'YXRRSET'
+    case 8: return 'NXRRSET'
+    case 9: return 'NOTAUTH'
+    case 10: return 'NOTZONE'
+    case 11: return 'RCODE_11'
+    case 12: return 'RCODE_12'
+    case 13: return 'RCODE_13'
+    case 14: return 'RCODE_14'
+    case 15: return 'RCODE_15'
+  }
+  return 'RCODE_' + rcode
+}
+
+exports.toRcode = function (code) {
+  switch (code.toUpperCase()) {
+    case 'NOERROR': return 0
+    case 'FORMERR': return 1
+    case 'SERVFAIL': return 2
+    case 'NXDOMAIN': return 3
+    case 'NOTIMP': return 4
+    case 'REFUSED': return 5
+    case 'YXDOMAIN': return 6
+    case 'YXRRSET': return 7
+    case 'NXRRSET': return 8
+    case 'NOTAUTH': return 9
+    case 'NOTZONE': return 10
+    case 'RCODE_11': return 11
+    case 'RCODE_12': return 12
+    case 'RCODE_13': return 13
+    case 'RCODE_14': return 14
+    case 'RCODE_15': return 15
+  }
+  return 0
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/test.js
@@ -0,0 +1,613 @@
+'use strict'
+
+const tape = require('tape')
+const packet = require('./')
+const rcodes = require('./rcodes')
+const opcodes = require('./opcodes')
+const optioncodes = require('./optioncodes')
+
+tape('unknown', function (t) {
+  testEncoder(t, packet.unknown, Buffer.from('hello world'))
+  t.end()
+})
+
+tape('txt', function (t) {
+  testEncoder(t, packet.txt, [])
+  testEncoder(t, packet.txt, ['hello world'])
+  testEncoder(t, packet.txt, ['hello', 'world'])
+  testEncoder(t, packet.txt, [Buffer.from([0, 1, 2, 3, 4, 5])])
+  testEncoder(t, packet.txt, ['a', 'b', Buffer.from([0, 1, 2, 3, 4, 5])])
+  testEncoder(t, packet.txt, ['', Buffer.allocUnsafe(0)])
+  t.end()
+})
+
+tape('txt-scalar-string', function (t) {
+  const buf = packet.txt.encode('hi')
+  const val = packet.txt.decode(buf)
+  t.ok(val.length === 1, 'array length')
+  t.ok(val[0].toString() === 'hi', 'data')
+  t.end()
+})
+
+tape('txt-scalar-buffer', function (t) {
+  const data = Buffer.from([0, 1, 2, 3, 4, 5])
+  const buf = packet.txt.encode(data)
+  const val = packet.txt.decode(buf)
+  t.ok(val.length === 1, 'array length')
+  t.ok(val[0].equals(data), 'data')
+  t.end()
+})
+
+tape('txt-invalid-data', function (t) {
+  t.throws(function () { packet.txt.encode(null) }, 'null')
+  t.throws(function () { packet.txt.encode(undefined) }, 'undefined')
+  t.throws(function () { packet.txt.encode(10) }, 'number')
+  t.end()
+})
+
+tape('null', function (t) {
+  testEncoder(t, packet.null, Buffer.from([0, 1, 2, 3, 4, 5]))
+  t.end()
+})
+
+tape('hinfo', function (t) {
+  testEncoder(t, packet.hinfo, { cpu: 'intel', os: 'best one' })
+  t.end()
+})
+
+tape('ptr', function (t) {
+  testEncoder(t, packet.ptr, 'hello.world.com')
+  t.end()
+})
+
+tape('cname', function (t) {
+  testEncoder(t, packet.cname, 'hello.cname.world.com')
+  t.end()
+})
+
+tape('dname', function (t) {
+  testEncoder(t, packet.dname, 'hello.dname.world.com')
+  t.end()
+})
+
+tape('srv', function (t) {
+  testEncoder(t, packet.srv, { port: 9999, target: 'hello.world.com' })
+  testEncoder(t, packet.srv, { port: 9999, target: 'hello.world.com', priority: 42, weight: 10 })
+  t.end()
+})
+
+tape('caa', function (t) {
+  testEncoder(t, packet.caa, { flags: 128, tag: 'issue', value: 'letsencrypt.org', issuerCritical: true })
+  testEncoder(t, packet.caa, { tag: 'issue', value: 'letsencrypt.org', issuerCritical: true })
+  testEncoder(t, packet.caa, { tag: 'issue', value: 'letsencrypt.org' })
+  t.end()
+})
+
+tape('mx', function (t) {
+  testEncoder(t, packet.mx, { preference: 10, exchange: 'mx.hello.world.com' })
+  testEncoder(t, packet.mx, { exchange: 'mx.hello.world.com' })
+  t.end()
+})
+
+tape('ns', function (t) {
+  testEncoder(t, packet.ns, 'ns.world.com')
+  t.end()
+})
+
+tape('soa', function (t) {
+  testEncoder(t, packet.soa, {
+    mname: 'hello.world.com',
+    rname: 'root.hello.world.com',
+    serial: 2018010400,
+    refresh: 14400,
+    retry: 3600,
+    expire: 604800,
+    minimum: 3600
+  })
+  t.end()
+})
+
+tape('a', function (t) {
+  testEncoder(t, packet.a, '127.0.0.1')
+  t.end()
+})
+
+tape('aaaa', function (t) {
+  testEncoder(t, packet.aaaa, 'fe80::1')
+  t.end()
+})
+
+tape('query', function (t) {
+  testEncoder(t, packet, {
+    type: 'query',
+    questions: [{
+      type: 'A',
+      name: 'hello.a.com'
+    }, {
+      type: 'SRV',
+      name: 'hello.srv.com'
+    }]
+  })
+
+  testEncoder(t, packet, {
+    type: 'query',
+    id: 42,
+    questions: [{
+      type: 'A',
+      class: 'IN',
+      name: 'hello.a.com'
+    }, {
+      type: 'SRV',
+      name: 'hello.srv.com'
+    }]
+  })
+
+  testEncoder(t, packet, {
+    type: 'query',
+    id: 42,
+    questions: [{
+      type: 'A',
+      class: 'CH',
+      name: 'hello.a.com'
+    }, {
+      type: 'SRV',
+      name: 'hello.srv.com'
+    }]
+  })
+
+  t.end()
+})
+
+tape('response', function (t) {
+  testEncoder(t, packet, {
+    type: 'response',
+    answers: [{
+      type: 'A',
+      class: 'IN',
+      flush: true,
+      name: 'hello.a.com',
+      data: '127.0.0.1'
+    }]
+  })
+
+  testEncoder(t, packet, {
+    type: 'response',
+    flags: packet.TRUNCATED_RESPONSE,
+    answers: [{
+      type: 'A',
+      class: 'IN',
+      name: 'hello.a.com',
+      data: '127.0.0.1'
+    }, {
+      type: 'SRV',
+      class: 'IN',
+      name: 'hello.srv.com',
+      data: {
+        port: 9090,
+        target: 'hello.target.com'
+      }
+    }, {
+      type: 'CNAME',
+      class: 'IN',
+      name: 'hello.cname.com',
+      data: 'hello.other.domain.com'
+    }]
+  })
+
+  testEncoder(t, packet, {
+    type: 'response',
+    id: 100,
+    flags: 0,
+    additionals: [{
+      type: 'AAAA',
+      name: 'hello.a.com',
+      data: 'fe80::1'
+    }, {
+      type: 'PTR',
+      name: 'hello.ptr.com',
+      data: 'hello.other.ptr.com'
+    }, {
+      type: 'SRV',
+      name: 'hello.srv.com',
+      ttl: 42,
+      data: {
+        port: 9090,
+        target: 'hello.target.com'
+      }
+    }],
+    answers: [{
+      type: 'NULL',
+      name: 'hello.null.com',
+      data: Buffer.from([1, 2, 3, 4, 5])
+    }]
+  })
+
+  testEncoder(t, packet, {
+    type: 'response',
+    answers: [{
+      type: 'TXT',
+      name: 'emptytxt.com',
+      data: ''
+    }]
+  })
+
+  t.end()
+})
+
+tape('rcode', function (t) {
+  const errors = ['NOERROR', 'FORMERR', 'SERVFAIL', 'NXDOMAIN', 'NOTIMP', 'REFUSED', 'YXDOMAIN', 'YXRRSET', 'NXRRSET', 'NOTAUTH', 'NOTZONE', 'RCODE_11', 'RCODE_12', 'RCODE_13', 'RCODE_14', 'RCODE_15']
+  for (const i in errors) {
+    const code = rcodes.toRcode(errors[i])
+    t.ok(errors[i] === rcodes.toString(code), 'rcode conversion from/to string matches: ' + rcodes.toString(code))
+  }
+
+  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']
+  for (const j in ops) {
+    const ocode = opcodes.toOpcode(ops[j])
+    t.ok(ops[j] === opcodes.toString(ocode), 'opcode conversion from/to string matches: ' + opcodes.toString(ocode))
+  }
+
+  const buf = packet.encode({
+    type: 'response',
+    id: 45632,
+    flags: 0x8480,
+    answers: [{
+      type: 'A',
+      name: 'hello.example.net',
+      data: '127.0.0.1'
+    }]
+  })
+  const val = packet.decode(buf)
+  t.ok(val.type === 'response', 'decode type')
+  t.ok(val.opcode === 'QUERY', 'decode opcode')
+  t.ok(val.flag_qr === true, 'decode flag_qr')
+  t.ok(val.flag_aa === true, 'decode flag_aa')
+  t.ok(val.flag_tc === false, 'decode flag_tc')
+  t.ok(val.flag_rd === false, 'decode flag_rd')
+  t.ok(val.flag_ra === true, 'decode flag_ra')
+  t.ok(val.flag_z === false, 'decode flag_z')
+  t.ok(val.flag_ad === false, 'decode flag_ad')
+  t.ok(val.flag_cd === false, 'decode flag_cd')
+  t.ok(val.rcode === 'NOERROR', 'decode rcode')
+  t.end()
+})
+
+tape('name_encoding', function (t) {
+  let data = 'foo.example.com'
+  const buf = Buffer.allocUnsafe(255)
+  let offset = 0
+  packet.name.encode(data, buf, offset)
+  t.ok(packet.name.encode.bytes === 17, 'name encoding length matches')
+  let dd = packet.name.decode(buf, offset)
+  t.ok(data === dd, 'encode/decode matches')
+  offset += packet.name.encode.bytes
+
+  data = 'com'
+  packet.name.encode(data, buf, offset)
+  t.ok(packet.name.encode.bytes === 5, 'name encoding length matches')
+  dd = packet.name.decode(buf, offset)
+  t.ok(data === dd, 'encode/decode matches')
+  offset += packet.name.encode.bytes
+
+  data = 'example.com.'
+  packet.name.encode(data, buf, offset)
+  t.ok(packet.name.encode.bytes === 13, 'name encoding length matches')
+  dd = packet.name.decode(buf, offset)
+  t.ok(data.slice(0, -1) === dd, 'encode/decode matches')
+  offset += packet.name.encode.bytes
+
+  data = '.'
+  packet.name.encode(data, buf, offset)
+  t.ok(packet.name.encode.bytes === 1, 'name encoding length matches')
+  dd = packet.name.decode(buf, offset)
+  t.ok(data === dd, 'encode/decode matches')
+  t.end()
+})
+
+tape('stream', function (t) {
+  const val = {
+    type: 'query',
+    id: 45632,
+    flags: 0x8480,
+    answers: [{
+      type: 'A',
+      name: 'test2.example.net',
+      data: '198.51.100.1'
+    }]
+  }
+  const buf = packet.streamEncode(val)
+  const val2 = packet.streamDecode(buf)
+
+  t.same(buf.length, packet.streamEncode.bytes, 'streamEncode.bytes was set correctly')
+  t.ok(compare(t, val2.type, val.type), 'streamDecoded type match')
+  t.ok(compare(t, val2.id, val.id), 'streamDecoded id match')
+  t.ok(parseInt(val2.flags) === parseInt(val.flags & 0x7FFF), 'streamDecoded flags match')
+  const answer = val.answers[0]
+  const answer2 = val2.answers[0]
+  t.ok(compare(t, answer.type, answer2.type), 'streamDecoded RR type match')
+  t.ok(compare(t, answer.name, answer2.name), 'streamDecoded RR name match')
+  t.ok(compare(t, answer.data, answer2.data), 'streamDecoded RR rdata match')
+  t.end()
+})
+
+tape('opt', function (t) {
+  const val = {
+    type: 'query',
+    questions: [{
+      type: 'A',
+      name: 'hello.a.com'
+    }],
+    additionals: [{
+      type: 'OPT',
+      name: '.',
+      udpPayloadSize: 1024
+    }]
+  }
+  testEncoder(t, packet, val)
+  let buf = packet.encode(val)
+  let val2 = packet.decode(buf)
+  const additional1 = val.additionals[0]
+  let additional2 = val2.additionals[0]
+  t.ok(compare(t, additional1.name, additional2.name), 'name matches')
+  t.ok(compare(t, additional1.udpPayloadSize, additional2.udpPayloadSize), 'udp payload size matches')
+  t.ok(compare(t, 0, additional2.flags), 'flags match')
+  additional1.flags = packet.DNSSEC_OK
+  additional1.extendedRcode = 0x80
+  additional1.options = [ {
+    code: 'CLIENT_SUBNET', // edns-client-subnet, see RFC 7871
+    ip: 'fe80::',
+    sourcePrefixLength: 64
+  }, {
+    code: 8, // still ECS
+    ip: '5.6.0.0',
+    sourcePrefixLength: 16,
+    scopePrefixLength: 16
+  }, {
+    code: 'padding',
+    length: 31
+  }, {
+    code: 'TCP_KEEPALIVE'
+  }, {
+    code: 'tcp_keepalive',
+    timeout: 150
+  }, {
+    code: 'KEY_TAG',
+    tags: [1, 82, 987]
+  }]
+  buf = packet.encode(val)
+  val2 = packet.decode(buf)
+  additional2 = val2.additionals[0]
+  t.ok(compare(t, 1 << 15, additional2.flags), 'DO bit set in flags')
+  t.ok(compare(t, true, additional2.flag_do), 'DO bit set')
+  t.ok(compare(t, additional1.extendedRcode, additional2.extendedRcode), 'extended rcode matches')
+  t.ok(compare(t, 8, additional2.options[0].code))
+  t.ok(compare(t, 'fe80::', additional2.options[0].ip))
+  t.ok(compare(t, 64, additional2.options[0].sourcePrefixLength))
+  t.ok(compare(t, '5.6.0.0', additional2.options[1].ip))
+  t.ok(compare(t, 16, additional2.options[1].sourcePrefixLength))
+  t.ok(compare(t, 16, additional2.options[1].scopePrefixLength))
+  t.ok(compare(t, additional1.options[2].length, additional2.options[2].data.length))
+  t.ok(compare(t, additional1.options[3].timeout, undefined))
+  t.ok(compare(t, additional1.options[4].timeout, additional2.options[4].timeout))
+  t.ok(compare(t, additional1.options[5].tags, additional2.options[5].tags))
+  t.end()
+})
+
+tape('dnskey', function (t) {
+  testEncoder(t, packet.dnskey, {
+    flags: packet.dnskey.SECURE_ENTRYPOINT | packet.dnskey.ZONE_KEY,
+    algorithm: 1,
+    key: Buffer.from([0, 1, 2, 3, 4, 5])
+  })
+  t.end()
+})
+
+tape('rrsig', function (t) {
+  const testRRSIG = {
+    typeCovered: 'A',
+    algorithm: 1,
+    labels: 2,
+    originalTTL: 3600,
+    expiration: 1234,
+    inception: 1233,
+    keyTag: 2345,
+    signersName: 'foo.com',
+    signature: Buffer.from([0, 1, 2, 3, 4, 5])
+  }
+  testEncoder(t, packet.rrsig, testRRSIG)
+
+  // Check the signature length is correct with extra junk at the end
+  const buf = Buffer.allocUnsafe(packet.rrsig.encodingLength(testRRSIG) + 4)
+  packet.rrsig.encode(testRRSIG, buf)
+  const val2 = packet.rrsig.decode(buf)
+  t.ok(compare(t, testRRSIG, val2))
+
+  t.end()
+})
+
+tape('rrp', function (t) {
+  testEncoder(t, packet.rp, {
+    mbox: 'foo.bar.com',
+    txt: 'baz.bar.com'
+  })
+  testEncoder(t, packet.rp, {
+    mbox: 'foo.bar.com'
+  })
+  testEncoder(t, packet.rp, {
+    txt: 'baz.bar.com'
+  })
+  testEncoder(t, packet.rp, {})
+  t.end()
+})
+
+tape('nsec', function (t) {
+  testEncoder(t, packet.nsec, {
+    nextDomain: 'foo.com',
+    rrtypes: ['A', 'DNSKEY', 'CAA', 'DLV']
+  })
+  testEncoder(t, packet.nsec, {
+    nextDomain: 'foo.com',
+    rrtypes: ['TXT'] // 16
+  })
+  testEncoder(t, packet.nsec, {
+    nextDomain: 'foo.com',
+    rrtypes: ['TKEY'] // 249
+  })
+  testEncoder(t, packet.nsec, {
+    nextDomain: 'foo.com',
+    rrtypes: ['RRSIG', 'NSEC']
+  })
+  testEncoder(t, packet.nsec, {
+    nextDomain: 'foo.com',
+    rrtypes: ['TXT', 'RRSIG']
+  })
+  testEncoder(t, packet.nsec, {
+    nextDomain: 'foo.com',
+    rrtypes: ['TXT', 'NSEC']
+  })
+
+  // Test with the sample NSEC from https://tools.ietf.org/html/rfc4034#section-4.3
+  var sampleNSEC = Buffer.from('003704686f7374076578616d706c6503636f6d00' +
+      '0006400100000003041b000000000000000000000000000000000000000000000' +
+      '000000020', 'hex')
+  var decoded = packet.nsec.decode(sampleNSEC)
+  t.ok(compare(t, decoded, {
+    nextDomain: 'host.example.com',
+    rrtypes: ['A', 'MX', 'RRSIG', 'NSEC', 'UNKNOWN_1234']
+  }))
+  var reencoded = packet.nsec.encode(decoded)
+  t.same(sampleNSEC.length, reencoded.length)
+  t.same(sampleNSEC, reencoded)
+  t.end()
+})
+
+tape('nsec3', function (t) {
+  testEncoder(t, packet.nsec3, {
+    algorithm: 1,
+    flags: 0,
+    iterations: 257,
+    salt: Buffer.from([42, 42, 42]),
+    nextDomain: Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
+    rrtypes: ['A', 'DNSKEY', 'CAA', 'DLV']
+  })
+  t.end()
+})
+
+tape('ds', function (t) {
+  testEncoder(t, packet.ds, {
+    keyTag: 1234,
+    algorithm: 1,
+    digestType: 1,
+    digest: Buffer.from([0, 1, 2, 3, 4, 5])
+  })
+  t.end()
+})
+
+tape('unpack', function (t) {
+  const buf = Buffer.from([
+    0x00, 0x79,
+    0xde, 0xad, 0x85, 0x00, 0x00, 0x01, 0x00, 0x01,
+    0x00, 0x02, 0x00, 0x02, 0x02, 0x6f, 0x6a, 0x05,
+    0x62, 0x61, 0x6e, 0x67, 0x6a, 0x03, 0x63, 0x6f,
+    0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c,
+    0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10,
+    0x00, 0x04, 0x81, 0xfa, 0x0b, 0xaa, 0xc0, 0x0f,
+    0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10,
+    0x00, 0x05, 0x02, 0x63, 0x6a, 0xc0, 0x0f, 0xc0,
+    0x0f, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x0e,
+    0x10, 0x00, 0x02, 0xc0, 0x0c, 0xc0, 0x3a, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00,
+    0x04, 0x45, 0x4d, 0x9b, 0x9c, 0xc0, 0x0c, 0x00,
+    0x1c, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00,
+    0x10, 0x20, 0x01, 0x04, 0x18, 0x00, 0x00, 0x50,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf9
+  ])
+  const val = packet.streamDecode(buf)
+  const answer = val.answers[0]
+  const authority = val.authorities[1]
+  t.ok(val.rcode === 'NOERROR', 'decode rcode')
+  t.ok(compare(t, answer.type, 'A'), 'streamDecoded RR type match')
+  t.ok(compare(t, answer.name, 'oj.bangj.com'), 'streamDecoded RR name match')
+  t.ok(compare(t, answer.data, '129.250.11.170'), 'streamDecoded RR rdata match')
+  t.ok(compare(t, authority.type, 'NS'), 'streamDecoded RR type match')
+  t.ok(compare(t, authority.name, 'bangj.com'), 'streamDecoded RR name match')
+  t.ok(compare(t, authority.data, 'oj.bangj.com'), 'streamDecoded RR rdata match')
+  t.end()
+})
+
+tape('optioncodes', function (t) {
+  const opts = [
+    [0, 'OPTION_0'],
+    [1, 'LLQ'],
+    [2, 'UL'],
+    [3, 'NSID'],
+    [4, 'OPTION_4'],
+    [5, 'DAU'],
+    [6, 'DHU'],
+    [7, 'N3U'],
+    [8, 'CLIENT_SUBNET'],
+    [9, 'EXPIRE'],
+    [10, 'COOKIE'],
+    [11, 'TCP_KEEPALIVE'],
+    [12, 'PADDING'],
+    [13, 'CHAIN'],
+    [14, 'KEY_TAG'],
+    [26946, 'DEVICEID'],
+    [65535, 'OPTION_65535'],
+    [64000, 'OPTION_64000'],
+    [65002, 'OPTION_65002'],
+    [-1, null]
+  ]
+  for (const [code, str] of opts) {
+    const s = optioncodes.toString(code)
+    t.ok(compare(t, s, str), `${code} => ${str}`)
+    t.ok(compare(t, optioncodes.toCode(s), code), `${str} => ${code}`)
+  }
+  t.ok(compare(t, optioncodes.toCode('INVALIDINVALID'), -1))
+  t.end()
+})
+
+function testEncoder (t, rpacket, val) {
+  const buf = rpacket.encode(val)
+  const val2 = rpacket.decode(buf)
+
+  t.same(buf.length, rpacket.encode.bytes, 'encode.bytes was set correctly')
+  t.same(buf.length, rpacket.encodingLength(val), 'encoding length matches')
+  t.ok(compare(t, val, val2), 'decoded object match')
+
+  const buf2 = rpacket.encode(val2)
+  const val3 = rpacket.decode(buf2)
+
+  t.same(buf2.length, rpacket.encode.bytes, 'encode.bytes was set correctly on re-encode')
+  t.same(buf2.length, rpacket.encodingLength(val), 'encoding length matches on re-encode')
+
+  t.ok(compare(t, val, val3), 'decoded object match on re-encode')
+  t.ok(compare(t, val2, val3), 're-encoded decoded object match on re-encode')
+
+  const bigger = Buffer.allocUnsafe(buf2.length + 10)
+
+  const buf3 = rpacket.encode(val, bigger, 10)
+  const val4 = rpacket.decode(buf3, 10)
+
+  t.ok(buf3 === bigger, 'echoes buffer on external buffer')
+  t.same(rpacket.encode.bytes, buf.length, 'encode.bytes is the same on external buffer')
+  t.ok(compare(t, val, val4), 'decoded object match on external buffer')
+}
+
+function compare (t, a, b) {
+  if (Buffer.isBuffer(a)) return a.toString('hex') === b.toString('hex')
+  if (typeof a === 'object' && a && b) {
+    const keys = Object.keys(a)
+    for (let i = 0; i < keys.length; i++) {
+      if (!compare(t, a[keys[i]], b[keys[i]])) {
+        return false
+      }
+    }
+  } else if (Array.isArray(b) && !Array.isArray(a)) {
+    // TXT always decode as array
+    return a.toString() === b[0].toString()
+  } else {
+    return a === b
+  }
+  return true
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/dns-packet/types.js
@@ -0,0 +1,103 @@
+'use strict'
+
+exports.toString = function (type) {
+  switch (type) {
+    case 1: return 'A'
+    case 10: return 'NULL'
+    case 28: return 'AAAA'
+    case 18: return 'AFSDB'
+    case 42: return 'APL'
+    case 257: return 'CAA'
+    case 60: return 'CDNSKEY'
+    case 59: return 'CDS'
+    case 37: return 'CERT'
+    case 5: return 'CNAME'
+    case 49: return 'DHCID'
+    case 32769: return 'DLV'
+    case 39: return 'DNAME'
+    case 48: return 'DNSKEY'
+    case 43: return 'DS'
+    case 55: return 'HIP'
+    case 13: return 'HINFO'
+    case 45: return 'IPSECKEY'
+    case 25: return 'KEY'
+    case 36: return 'KX'
+    case 29: return 'LOC'
+    case 15: return 'MX'
+    case 35: return 'NAPTR'
+    case 2: return 'NS'
+    case 47: return 'NSEC'
+    case 50: return 'NSEC3'
+    case 51: return 'NSEC3PARAM'
+    case 12: return 'PTR'
+    case 46: return 'RRSIG'
+    case 17: return 'RP'
+    case 24: return 'SIG'
+    case 6: return 'SOA'
+    case 99: return 'SPF'
+    case 33: return 'SRV'
+    case 44: return 'SSHFP'
+    case 32768: return 'TA'
+    case 249: return 'TKEY'
+    case 52: return 'TLSA'
+    case 250: return 'TSIG'
+    case 16: return 'TXT'
+    case 252: return 'AXFR'
+    case 251: return 'IXFR'
+    case 41: return 'OPT'
+    case 255: return 'ANY'
+  }
+  return 'UNKNOWN_' + type
+}
+
+exports.toType = function (name) {
+  switch (name.toUpperCase()) {
+    case 'A': return 1
+    case 'NULL': return 10
+    case 'AAAA': return 28
+    case 'AFSDB': return 18
+    case 'APL': return 42
+    case 'CAA': return 257
+    case 'CDNSKEY': return 60
+    case 'CDS': return 59
+    case 'CERT': return 37
+    case 'CNAME': return 5
+    case 'DHCID': return 49
+    case 'DLV': return 32769
+    case 'DNAME': return 39
+    case 'DNSKEY': return 48
+    case 'DS': return 43
+    case 'HIP': return 55
+    case 'HINFO': return 13
+    case 'IPSECKEY': return 45
+    case 'KEY': return 25
+    case 'KX': return 36
+    case 'LOC': return 29
+    case 'MX': return 15
+    case 'NAPTR': return 35
+    case 'NS': return 2
+    case 'NSEC': return 47
+    case 'NSEC3': return 50
+    case 'NSEC3PARAM': return 51
+    case 'PTR': return 12
+    case 'RRSIG': return 46
+    case 'RP': return 17
+    case 'SIG': return 24
+    case 'SOA': return 6
+    case 'SPF': return 99
+    case 'SRV': return 33
+    case 'SSHFP': return 44
+    case 'TA': return 32768
+    case 'TKEY': return 249
+    case 'TLSA': return 52
+    case 'TSIG': return 250
+    case 'TXT': return 16
+    case 'AXFR': return 252
+    case 'IXFR': return 251
+    case 'OPT': return 41
+    case 'ANY': return 255
+    case '*': return 255
+  }
+  if (name.toUpperCase().startsWith('UNKNOWN_')) return parseInt(name.slice(8))
+  return 0
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-ip/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+npm-debug.log
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-ip/.jscsrc
@@ -0,0 +1,46 @@
+{
+  "disallowKeywordsOnNewLine": [ "else" ],
+  "disallowMixedSpacesAndTabs": true,
+  "disallowMultipleLineStrings": true,
+  "disallowMultipleVarDecl": true,
+  "disallowNewlineBeforeBlockStatements": true,
+  "disallowQuotedKeysInObjects": true,
+  "disallowSpaceAfterObjectKeys": true,
+  "disallowSpaceAfterPrefixUnaryOperators": true,
+  "disallowSpaceBeforePostfixUnaryOperators": true,
+  "disallowSpacesInCallExpression": true,
+  "disallowTrailingComma": true,
+  "disallowTrailingWhitespace": true,
+  "disallowYodaConditions": true,
+
+  "requireCommaBeforeLineBreak": true,
+  "requireOperatorBeforeLineBreak": true,
+  "requireSpaceAfterBinaryOperators": true,
+  "requireSpaceAfterKeywords": [ "if", "for", "while", "else", "try", "catch" ],
+  "requireSpaceAfterLineComment": true,
+  "requireSpaceBeforeBinaryOperators": true,
+  "requireSpaceBeforeBlockStatements": true,
+  "requireSpaceBeforeKeywords": [ "else", "catch" ],
+  "requireSpaceBeforeObjectValues": true,
+  "requireSpaceBetweenArguments": true,
+  "requireSpacesInAnonymousFunctionExpression": {
+    "beforeOpeningCurlyBrace": true
+  },
+  "requireSpacesInFunctionDeclaration": {
+    "beforeOpeningCurlyBrace": true
+  },
+  "requireSpacesInFunctionExpression": {
+    "beforeOpeningCurlyBrace": true
+  },
+  "requireSpacesInConditionalExpression": true,
+  "requireSpacesInForStatement": true,
+  "requireSpacesInsideArrayBrackets": "all",
+  "requireSpacesInsideObjectBrackets": "all",
+  "requireDotNotation": true,
+
+  "maximumLineLength": 80,
+  "validateIndentation": 2,
+  "validateLineBreaks": "LF",
+  "validateParameterSeparator": ", ",
+  "validateQuoteMarks": "'"
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-ip/.jshintrc
@@ -0,0 +1,89 @@
+{
+    // JSHint Default Configuration File (as on JSHint website)
+    // See http://jshint.com/docs/ for more details
+
+    "maxerr"        : 50,       // {int} Maximum error before stopping
+
+    // Enforcing
+    "bitwise"       : false,     // true: Prohibit bitwise operators (&, |, ^, etc.)
+    "camelcase"     : false,     // true: Identifiers must be in camelCase
+    "curly"         : false,     // true: Require {} for every new block or scope
+    "eqeqeq"        : true,     // true: Require triple equals (===) for comparison
+    "forin"         : true,     // true: Require filtering for..in loops with obj.hasOwnProperty()
+    "freeze"        : true,     // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
+    "immed"         : false,    // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
+    "indent"        : 2,        // {int} Number of spaces to use for indentation
+    "latedef"       : true,     // true: Require variables/functions to be defined before being used
+    "newcap"        : true,     // true: Require capitalization of all constructor functions e.g. `new F()`
+    "noarg"         : true,     // true: Prohibit use of `arguments.caller` and `arguments.callee`
+    "noempty"       : false,    // true: Prohibit use of empty blocks
+    "nonbsp"        : true,     // true: Prohibit "non-breaking whitespace" characters.
+    "nonew"         : false,    // true: Prohibit use of constructors for side-effects (without assignment)
+    "plusplus"      : false,    // true: Prohibit use of `++` & `--`
+    "quotmark"      : "single", // Quotation mark consistency:
+                                //   false    : do nothing (default)
+                                //   true     : ensure whatever is used is consistent
+                                //   "single" : require single quotes
+                                //   "double" : require double quotes
+    "undef"         : true,     // true: Require all non-global variables to be declared (prevents global leaks)
+    "unused"        : true,     // true: Require all defined variables be used
+    "strict"        : true,     // true: Requires all functions run in ES5 Strict Mode
+    "maxparams"     : false,    // {int} Max number of formal params allowed per function
+    "maxdepth"      : 3,        // {int} Max depth of nested blocks (within functions)
+    "maxstatements" : false,    // {int} Max number statements per function
+    "maxcomplexity" : false,    // {int} Max cyclomatic complexity per function
+    "maxlen"        : false,    // {int} Max number of characters per line
+
+    // Relaxing
+    "asi"           : false,     // true: Tolerate Automatic Semicolon Insertion (no semicolons)
+    "boss"          : false,     // true: Tolerate assignments where comparisons would be expected
+    "debug"         : false,     // true: Allow debugger statements e.g. browser breakpoints.
+    "eqnull"        : false,     // true: Tolerate use of `== null`
+    "es5"           : false,     // true: Allow ES5 syntax (ex: getters and setters)
+    "esnext"        : false,     // true: Allow ES.next (ES6) syntax (ex: `const`)
+    "moz"           : false,     // true: Allow Mozilla specific syntax (extends and overrides esnext features)
+                                 // (ex: `for each`, multiple try/catch, function expression…)
+    "evil"          : false,     // true: Tolerate use of `eval` and `new Function()`
+    "expr"          : false,     // true: Tolerate `ExpressionStatement` as Programs
+    "funcscope"     : false,     // true: Tolerate defining variables inside control statements
+    "globalstrict"  : false,     // true: Allow global "use strict" (also enables 'strict')
+    "iterator"      : false,     // true: Tolerate using the `__iterator__` property
+    "lastsemic"     : false,     // true: Tolerate omitting a semicolon for the last statement of a 1-line block
+    "laxbreak"      : false,     // true: Tolerate possibly unsafe line breakings
+    "laxcomma"      : false,     // true: Tolerate comma-first style coding
+    "loopfunc"      : false,     // true: Tolerate functions being defined in loops
+    "multistr"      : false,     // true: Tolerate multi-line strings
+    "noyield"       : false,     // true: Tolerate generator functions with no yield statement in them.
+    "notypeof"      : false,     // true: Tolerate invalid typeof operator values
+    "proto"         : false,     // true: Tolerate using the `__proto__` property
+    "scripturl"     : false,     // true: Tolerate script-targeted URLs
+    "shadow"        : true,      // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
+    "sub"           : false,     // true: Tolerate using `[]` notation when it can still be expressed in dot notation
+    "supernew"      : false,     // true: Tolerate `new function () { ... };` and `new Object;`
+    "validthis"     : false,     // true: Tolerate using this in a non-constructor function
+
+    // Environments
+    "browser"       : true,     // Web Browser (window, document, etc)
+    "browserify"    : true,     // Browserify (node.js code in the browser)
+    "couch"         : false,    // CouchDB
+    "devel"         : true,     // Development/debugging (alert, confirm, etc)
+    "dojo"          : false,    // Dojo Toolkit
+    "jasmine"       : false,    // Jasmine
+    "jquery"        : false,    // jQuery
+    "mocha"         : true,     // Mocha
+    "mootools"      : false,    // MooTools
+    "node"          : true,     // Node.js
+    "nonstandard"   : false,    // Widely adopted globals (escape, unescape, etc)
+    "prototypejs"   : false,    // Prototype and Scriptaculous
+    "qunit"         : false,    // QUnit
+    "rhino"         : false,    // Rhino
+    "shelljs"       : false,    // ShellJS
+    "worker"        : false,    // Web Workers
+    "wsh"           : false,    // Windows Scripting Host
+    "yui"           : false,    // Yahoo User Interface
+
+    // Custom Globals
+    "globals"       : {
+        "module": true
+    }        // additional predefined global variables
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-ip/.travis.yml
@@ -0,0 +1,15 @@
+sudo: false
+language: node_js
+node_js:
+  - "0.8"
+  - "0.10"
+  - "0.12"
+  - "4"
+  - "6"
+
+before_install:
+  - travis_retry npm install -g npm@2.14.5
+  - travis_retry npm install
+
+script:
+  - npm test
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-ip/README.md
@@ -0,0 +1,90 @@
+# IP  
+[![](https://badge.fury.io/js/ip.svg)](https://www.npmjs.com/package/ip)  
+
+IP address utilities for node.js
+
+## Installation
+
+###  npm
+```shell
+npm install ip
+```
+
+### git
+
+```shell
+git clone https://github.com/indutny/node-ip.git
+```
+  
+## Usage
+Get your ip address, compare ip addresses, validate ip addresses, etc.
+
+```js
+var ip = require('ip');
+
+ip.address() // my ip address
+ip.isEqual('::1', '::0:1'); // true
+ip.toBuffer('127.0.0.1') // Buffer([127, 0, 0, 1])
+ip.toString(new Buffer([127, 0, 0, 1])) // 127.0.0.1
+ip.fromPrefixLen(24) // 255.255.255.0
+ip.mask('192.168.1.134', '255.255.255.0') // 192.168.1.0
+ip.cidr('192.168.1.134/26') // 192.168.1.128
+ip.not('255.255.255.0') // 0.0.0.255
+ip.or('192.168.1.134', '0.0.0.255') // 192.168.1.255
+ip.isPrivate('127.0.0.1') // true
+ip.isV4Format('127.0.0.1'); // true
+ip.isV6Format('::ffff:127.0.0.1'); // true
+
+// operate on buffers in-place
+var buf = new Buffer(128);
+var offset = 64;
+ip.toBuffer('127.0.0.1', buf, offset);  // [127, 0, 0, 1] at offset 64
+ip.toString(buf, offset, 4);            // '127.0.0.1'
+
+// subnet information
+ip.subnet('192.168.1.134', '255.255.255.192')
+// { networkAddress: '192.168.1.128',
+//   firstAddress: '192.168.1.129',
+//   lastAddress: '192.168.1.190',
+//   broadcastAddress: '192.168.1.191',
+//   subnetMask: '255.255.255.192',
+//   subnetMaskLength: 26,
+//   numHosts: 62,
+//   length: 64,
+//   contains: function(addr){...} }
+ip.cidrSubnet('192.168.1.134/26')
+// Same as previous.
+
+// range checking
+ip.cidrSubnet('192.168.1.134/26').contains('192.168.1.190') // true
+
+
+// ipv4 long conversion
+ip.toLong('127.0.0.1'); // 2130706433
+ip.fromLong(2130706433); // '127.0.0.1'
+```
+
+### License
+
+This software is licensed under the MIT License.
+
+Copyright Fedor Indutny, 2012.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-ip/lib/ip.js
@@ -0,0 +1,416 @@
+'use strict';
+
+var ip = exports;
+var Buffer = require('buffer').Buffer;
+var os = require('os');
+
+ip.toBuffer = function(ip, buff, offset) {
+  offset = ~~offset;
+
+  var result;
+
+  if (this.isV4Format(ip)) {
+    result = buff || new Buffer(offset + 4);
+    ip.split(/\./g).map(function(byte) {
+      result[offset++] = parseInt(byte, 10) & 0xff;
+    });
+  } else if (this.isV6Format(ip)) {
+    var sections = ip.split(':', 8);
+
+    var i;
+    for (i = 0; i < sections.length; i++) {
+      var isv4 = this.isV4Format(sections[i]);
+      var v4Buffer;
+
+      if (isv4) {
+        v4Buffer = this.toBuffer(sections[i]);
+        sections[i] = v4Buffer.slice(0, 2).toString('hex');
+      }
+
+      if (v4Buffer && ++i < 8) {
+        sections.splice(i, 0, v4Buffer.slice(2, 4).toString('hex'));
+      }
+    }
+
+    if (sections[0] === '') {
+      while (sections.length < 8) sections.unshift('0');
+    } else if (sections[sections.length - 1] === '') {
+      while (sections.length < 8) sections.push('0');
+    } else if (sections.length < 8) {
+      for (i = 0; i < sections.length && sections[i] !== ''; i++);
+      var argv = [ i, 1 ];
+      for (i = 9 - sections.length; i > 0; i--) {
+        argv.push('0');
+      }
+      sections.splice.apply(sections, argv);
+    }
+
+    result = buff || new Buffer(offset + 16);
+    for (i = 0; i < sections.length; i++) {
+      var word = parseInt(sections[i], 16);
+      result[offset++] = (word >> 8) & 0xff;
+      result[offset++] = word & 0xff;
+    }
+  }
+
+  if (!result) {
+    throw Error('Invalid ip address: ' + ip);
+  }
+
+  return result;
+};
+
+ip.toString = function(buff, offset, length) {
+  offset = ~~offset;
+  length = length || (buff.length - offset);
+
+  var result = [];
+  if (length === 4) {
+    // IPv4
+    for (var i = 0; i < length; i++) {
+      result.push(buff[offset + i]);
+    }
+    result = result.join('.');
+  } else if (length === 16) {
+    // IPv6
+    for (var i = 0; i < length; i += 2) {
+      result.push(buff.readUInt16BE(offset + i).toString(16));
+    }
+    result = result.join(':');
+    result = result.replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3');
+    result = result.replace(/:{3,4}/, '::');
+  }
+
+  return result;
+};
+
+var ipv4Regex = /^(\d{1,3}\.){3,3}\d{1,3}$/;
+var ipv6Regex =
+    /^(::)?(((\d{1,3}\.){3}(\d{1,3}){1})?([0-9a-f]){0,4}:{0,2}){1,8}(::)?$/i;
+
+ip.isV4Format = function(ip) {
+  return ipv4Regex.test(ip);
+};
+
+ip.isV6Format = function(ip) {
+  return ipv6Regex.test(ip);
+};
+function _normalizeFamily(family) {
+  return family ? family.toLowerCase() : 'ipv4';
+}
+
+ip.fromPrefixLen = function(prefixlen, family) {
+  if (prefixlen > 32) {
+    family = 'ipv6';
+  } else {
+    family = _normalizeFamily(family);
+  }
+
+  var len = 4;
+  if (family === 'ipv6') {
+    len = 16;
+  }
+  var buff = new Buffer(len);
+
+  for (var i = 0, n = buff.length; i < n; ++i) {
+    var bits = 8;
+    if (prefixlen < 8) {
+      bits = prefixlen;
+    }
+    prefixlen -= bits;
+
+    buff[i] = ~(0xff >> bits) & 0xff;
+  }
+
+  return ip.toString(buff);
+};
+
+ip.mask = function(addr, mask) {
+  addr = ip.toBuffer(addr);
+  mask = ip.toBuffer(mask);
+
+  var result = new Buffer(Math.max(addr.length, mask.length));
+
+  var i = 0;
+  // Same protocol - do bitwise and
+  if (addr.length === mask.length) {
+    for (i = 0; i < addr.length; i++) {
+      result[i] = addr[i] & mask[i];
+    }
+  } else if (mask.length === 4) {
+    // IPv6 address and IPv4 mask
+    // (Mask low bits)
+    for (i = 0; i < mask.length; i++) {
+      result[i] = addr[addr.length - 4  + i] & mask[i];
+    }
+  } else {
+    // IPv6 mask and IPv4 addr
+    for (var i = 0; i < result.length - 6; i++) {
+      result[i] = 0;
+    }
+
+    // ::ffff:ipv4
+    result[10] = 0xff;
+    result[11] = 0xff;
+    for (i = 0; i < addr.length; i++) {
+      result[i + 12] = addr[i] & mask[i + 12];
+    }
+    i = i + 12;
+  }
+  for (; i < result.length; i++)
+    result[i] = 0;
+
+  return ip.toString(result);
+};
+
+ip.cidr = function(cidrString) {
+  var cidrParts = cidrString.split('/');
+
+  var addr = cidrParts[0];
+  if (cidrParts.length !== 2)
+    throw new Error('invalid CIDR subnet: ' + addr);
+
+  var mask = ip.fromPrefixLen(parseInt(cidrParts[1], 10));
+
+  return ip.mask(addr, mask);
+};
+
+ip.subnet = function(addr, mask) {
+  var networkAddress = ip.toLong(ip.mask(addr, mask));
+
+  // Calculate the mask's length.
+  var maskBuffer = ip.toBuffer(mask);
+  var maskLength = 0;
+
+  for (var i = 0; i < maskBuffer.length; i++) {
+    if (maskBuffer[i] === 0xff) {
+      maskLength += 8;
+    } else {
+      var octet = maskBuffer[i] & 0xff;
+      while (octet) {
+        octet = (octet << 1) & 0xff;
+        maskLength++;
+      }
+    }
+  }
+
+  var numberOfAddresses = Math.pow(2, 32 - maskLength);
+
+  return {
+    networkAddress: ip.fromLong(networkAddress),
+    firstAddress: numberOfAddresses <= 2 ?
+                    ip.fromLong(networkAddress) :
+                    ip.fromLong(networkAddress + 1),
+    lastAddress: numberOfAddresses <= 2 ?
+                    ip.fromLong(networkAddress + numberOfAddresses - 1) :
+                    ip.fromLong(networkAddress + numberOfAddresses - 2),
+    broadcastAddress: ip.fromLong(networkAddress + numberOfAddresses - 1),
+    subnetMask: mask,
+    subnetMaskLength: maskLength,
+    numHosts: numberOfAddresses <= 2 ?
+                numberOfAddresses : numberOfAddresses - 2,
+    length: numberOfAddresses,
+    contains: function(other) {
+      return networkAddress === ip.toLong(ip.mask(other, mask));
+    }
+  };
+};
+
+ip.cidrSubnet = function(cidrString) {
+  var cidrParts = cidrString.split('/');
+
+  var addr = cidrParts[0];
+  if (cidrParts.length !== 2)
+    throw new Error('invalid CIDR subnet: ' + addr);
+
+  var mask = ip.fromPrefixLen(parseInt(cidrParts[1], 10));
+
+  return ip.subnet(addr, mask);
+};
+
+ip.not = function(addr) {
+  var buff = ip.toBuffer(addr);
+  for (var i = 0; i < buff.length; i++) {
+    buff[i] = 0xff ^ buff[i];
+  }
+  return ip.toString(buff);
+};
+
+ip.or = function(a, b) {
+  a = ip.toBuffer(a);
+  b = ip.toBuffer(b);
+
+  // same protocol
+  if (a.length === b.length) {
+    for (var i = 0; i < a.length; ++i) {
+      a[i] |= b[i];
+    }
+    return ip.toString(a);
+
+  // mixed protocols
+  } else {
+    var buff = a;
+    var other = b;
+    if (b.length > a.length) {
+      buff = b;
+      other = a;
+    }
+
+    var offset = buff.length - other.length;
+    for (var i = offset; i < buff.length; ++i) {
+      buff[i] |= other[i - offset];
+    }
+
+    return ip.toString(buff);
+  }
+};
+
+ip.isEqual = function(a, b) {
+  a = ip.toBuffer(a);
+  b = ip.toBuffer(b);
+
+  // Same protocol
+  if (a.length === b.length) {
+    for (var i = 0; i < a.length; i++) {
+      if (a[i] !== b[i]) return false;
+    }
+    return true;
+  }
+
+  // Swap
+  if (b.length === 4) {
+    var t = b;
+    b = a;
+    a = t;
+  }
+
+  // a - IPv4, b - IPv6
+  for (var i = 0; i < 10; i++) {
+    if (b[i] !== 0) return false;
+  }
+
+  var word = b.readUInt16BE(10);
+  if (word !== 0 && word !== 0xffff) return false;
+
+  for (var i = 0; i < 4; i++) {
+    if (a[i] !== b[i + 12]) return false;
+  }
+
+  return true;
+};
+
+ip.isPrivate = function(addr) {
+  return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i
+      .test(addr) ||
+    /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) ||
+    /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})$/i
+      .test(addr) ||
+    /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) ||
+    /^(::f{4}:)?169\.254\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) ||
+    /^f[cd][0-9a-f]{2}:/i.test(addr) ||
+    /^fe80:/i.test(addr) ||
+    /^::1$/.test(addr) ||
+    /^::$/.test(addr);
+};
+
+ip.isPublic = function(addr) {
+  return !ip.isPrivate(addr);
+};
+
+ip.isLoopback = function(addr) {
+  return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/
+      .test(addr) ||
+    /^fe80::1$/.test(addr) ||
+    /^::1$/.test(addr) ||
+    /^::$/.test(addr);
+};
+
+ip.loopback = function(family) {
+  //
+  // Default to `ipv4`
+  //
+  family = _normalizeFamily(family);
+
+  if (family !== 'ipv4' && family !== 'ipv6') {
+    throw new Error('family must be ipv4 or ipv6');
+  }
+
+  return family === 'ipv4' ? '127.0.0.1' : 'fe80::1';
+};
+
+//
+// ### function address (name, family)
+// #### @name {string|'public'|'private'} **Optional** Name or security
+//      of the network interface.
+// #### @family {ipv4|ipv6} **Optional** IP family of the address (defaults
+//      to ipv4).
+//
+// Returns the address for the network interface on the current system with
+// the specified `name`:
+//   * String: First `family` address of the interface.
+//             If not found see `undefined`.
+//   * 'public': the first public ip address of family.
+//   * 'private': the first private ip address of family.
+//   * undefined: First address with `ipv4` or loopback address `127.0.0.1`.
+//
+ip.address = function(name, family) {
+  var interfaces = os.networkInterfaces();
+  var all;
+
+  //
+  // Default to `ipv4`
+  //
+  family = _normalizeFamily(family);
+
+  //
+  // If a specific network interface has been named,
+  // return the address.
+  //
+  if (name && name !== 'private' && name !== 'public') {
+    var res = interfaces[name].filter(function(details) {
+      var itemFamily = details.family.toLowerCase();
+      return itemFamily === family;
+    });
+    if (res.length === 0)
+      return undefined;
+    return res[0].address;
+  }
+
+  var all = Object.keys(interfaces).map(function (nic) {
+    //
+    // Note: name will only be `public` or `private`
+    // when this is called.
+    //
+    var addresses = interfaces[nic].filter(function (details) {
+      details.family = details.family.toLowerCase();
+      if (details.family !== family || ip.isLoopback(details.address)) {
+        return false;
+      } else if (!name) {
+        return true;
+      }
+
+      return name === 'public' ? ip.isPrivate(details.address) :
+          ip.isPublic(details.address);
+    });
+
+    return addresses.length ? addresses[0].address : undefined;
+  }).filter(Boolean);
+
+  return !all.length ? ip.loopback(family) : all[0];
+};
+
+ip.toLong = function(ip) {
+  var ipl = 0;
+  ip.split('.').forEach(function(octet) {
+    ipl <<= 8;
+    ipl += parseInt(octet);
+  });
+  return(ipl >>> 0);
+};
+
+ip.fromLong = function(ipl) {
+  return ((ipl >>> 24) + '.' +
+      (ipl >> 16 & 255) + '.' +
+      (ipl >> 8 & 255) + '.' +
+      (ipl & 255) );
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-ip/package.json
@@ -0,0 +1,21 @@
+{
+  "name": "ip",
+  "version": "1.1.5",
+  "author": "Fedor Indutny <fedor@indutny.com>",
+  "homepage": "https://github.com/indutny/node-ip",
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/indutny/node-ip.git"
+  },
+  "main": "lib/ip",
+  "devDependencies": {
+    "jscs": "^2.1.1",
+    "jshint": "^2.8.0",
+    "mocha": "~1.3.2"
+  },
+  "scripts": {
+    "test": "jscs lib/*.js test/*.js && jshint lib/*.js && mocha --reporter spec test/*-test.js",
+    "fix": "jscs lib/*.js test/*.js --fix"
+  },
+  "license": "MIT"
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-ip/test/api-test.js
@@ -0,0 +1,407 @@
+'use strict';
+
+var ip = require('..');
+var assert = require('assert');
+var net = require('net');
+var os = require('os');
+
+describe('IP library for node.js', function() {
+  describe('toBuffer()/toString() methods', function() {
+    it('should convert to buffer IPv4 address', function() {
+      var buf = ip.toBuffer('127.0.0.1');
+      assert.equal(buf.toString('hex'), '7f000001');
+      assert.equal(ip.toString(buf), '127.0.0.1');
+    });
+
+    it('should convert to buffer IPv4 address in-place', function() {
+      var buf = new Buffer(128);
+      var offset = 64;
+      ip.toBuffer('127.0.0.1', buf, offset);
+      assert.equal(buf.toString('hex', offset, offset + 4), '7f000001');
+      assert.equal(ip.toString(buf, offset, 4), '127.0.0.1');
+    });
+
+    it('should convert to buffer IPv6 address', function() {
+      var buf = ip.toBuffer('::1');
+      assert(/(00){15,15}01/.test(buf.toString('hex')));
+      assert.equal(ip.toString(buf), '::1');
+      assert.equal(ip.toString(ip.toBuffer('1::')), '1::');
+      assert.equal(ip.toString(ip.toBuffer('abcd::dcba')), 'abcd::dcba');
+    });
+
+    it('should convert to buffer IPv6 address in-place', function() {
+      var buf = new Buffer(128);
+      var offset = 64;
+      ip.toBuffer('::1', buf, offset);
+      assert(/(00){15,15}01/.test(buf.toString('hex', offset, offset + 16)));
+      assert.equal(ip.toString(buf, offset, 16), '::1');
+      assert.equal(ip.toString(ip.toBuffer('1::', buf, offset),
+                               offset, 16), '1::');
+      assert.equal(ip.toString(ip.toBuffer('abcd::dcba', buf, offset),
+                               offset, 16), 'abcd::dcba');
+    });
+
+    it('should convert to buffer IPv6 mapped IPv4 address', function() {
+      var buf = ip.toBuffer('::ffff:127.0.0.1');
+      assert.equal(buf.toString('hex'), '00000000000000000000ffff7f000001');
+      assert.equal(ip.toString(buf), '::ffff:7f00:1');
+
+      buf = ip.toBuffer('ffff::127.0.0.1');
+      assert.equal(buf.toString('hex'), 'ffff000000000000000000007f000001');
+      assert.equal(ip.toString(buf), 'ffff::7f00:1');
+
+      buf = ip.toBuffer('0:0:0:0:0:ffff:127.0.0.1');
+      assert.equal(buf.toString('hex'), '00000000000000000000ffff7f000001');
+      assert.equal(ip.toString(buf), '::ffff:7f00:1');
+    });
+  });
+
+  describe('fromPrefixLen() method', function() {
+    it('should create IPv4 mask', function() {
+      assert.equal(ip.fromPrefixLen(24), '255.255.255.0');
+    });
+    it('should create IPv6 mask', function() {
+      assert.equal(ip.fromPrefixLen(64), 'ffff:ffff:ffff:ffff::');
+    });
+    it('should create IPv6 mask explicitly', function() {
+      assert.equal(ip.fromPrefixLen(24, 'IPV6'), 'ffff:ff00::');
+    });
+  });
+
+  describe('not() method', function() {
+    it('should reverse bits in address', function() {
+      assert.equal(ip.not('255.255.255.0'), '0.0.0.255');
+    });
+  });
+
+  describe('or() method', function() {
+    it('should or bits in ipv4 addresses', function() {
+      assert.equal(ip.or('0.0.0.255', '192.168.1.10'), '192.168.1.255');
+    });
+    it('should or bits in ipv6 addresses', function() {
+      assert.equal(ip.or('::ff', '::abcd:dcba:abcd:dcba'),
+                   '::abcd:dcba:abcd:dcff');
+    });
+    it('should or bits in mixed addresses', function() {
+      assert.equal(ip.or('0.0.0.255', '::abcd:dcba:abcd:dcba'),
+                   '::abcd:dcba:abcd:dcff');
+    });
+  });
+
+  describe('mask() method', function() {
+    it('should mask bits in address', function() {
+      assert.equal(ip.mask('192.168.1.134', '255.255.255.0'), '192.168.1.0');
+      assert.equal(ip.mask('192.168.1.134', '::ffff:ff00'), '::ffff:c0a8:100');
+    });
+
+    it('should not leak data', function() {
+      for (var i = 0; i < 10; i++)
+        assert.equal(ip.mask('::1', '0.0.0.0'), '::');
+    });
+  });
+
+  describe('subnet() method', function() {
+    // Test cases calculated with http://www.subnet-calculator.com/
+    var ipv4Subnet = ip.subnet('192.168.1.134', '255.255.255.192');
+
+    it('should compute ipv4 network address', function() {
+      assert.equal(ipv4Subnet.networkAddress, '192.168.1.128');
+    });
+
+    it('should compute ipv4 network\'s first address', function() {
+      assert.equal(ipv4Subnet.firstAddress, '192.168.1.129');
+    });
+
+    it('should compute ipv4 network\'s last address', function() {
+      assert.equal(ipv4Subnet.lastAddress, '192.168.1.190');
+    });
+
+    it('should compute ipv4 broadcast address', function() {
+      assert.equal(ipv4Subnet.broadcastAddress, '192.168.1.191');
+    });
+
+    it('should compute ipv4 subnet number of addresses', function() {
+      assert.equal(ipv4Subnet.length, 64);
+    });
+
+    it('should compute ipv4 subnet number of addressable hosts', function() {
+      assert.equal(ipv4Subnet.numHosts, 62);
+    });
+
+    it('should compute ipv4 subnet mask', function() {
+      assert.equal(ipv4Subnet.subnetMask, '255.255.255.192');
+    });
+
+    it('should compute ipv4 subnet mask\'s length', function() {
+      assert.equal(ipv4Subnet.subnetMaskLength, 26);
+    });
+
+    it('should know whether a subnet contains an address', function() {
+      assert.equal(ipv4Subnet.contains('192.168.1.180'), true);
+    });
+
+    it('should know whether a subnet does not contain an address', function() {
+      assert.equal(ipv4Subnet.contains('192.168.1.195'), false);
+    });
+  });
+
+  describe('subnet() method with mask length 32', function() {
+    // Test cases calculated with http://www.subnet-calculator.com/
+    var ipv4Subnet = ip.subnet('192.168.1.134', '255.255.255.255');
+    it('should compute ipv4 network\'s first address', function() {
+      assert.equal(ipv4Subnet.firstAddress, '192.168.1.134');
+    });
+
+    it('should compute ipv4 network\'s last address', function() {
+      assert.equal(ipv4Subnet.lastAddress, '192.168.1.134');
+    });
+
+    it('should compute ipv4 subnet number of addressable hosts', function() {
+      assert.equal(ipv4Subnet.numHosts, 1);
+    });
+  });
+
+  describe('subnet() method with mask length 31', function() {
+    // Test cases calculated with http://www.subnet-calculator.com/
+    var ipv4Subnet = ip.subnet('192.168.1.134', '255.255.255.254');
+    it('should compute ipv4 network\'s first address', function() {
+      assert.equal(ipv4Subnet.firstAddress, '192.168.1.134');
+    });
+
+    it('should compute ipv4 network\'s last address', function() {
+      assert.equal(ipv4Subnet.lastAddress, '192.168.1.135');
+    });
+
+    it('should compute ipv4 subnet number of addressable hosts', function() {
+      assert.equal(ipv4Subnet.numHosts, 2);
+    });
+  });
+
+  describe('cidrSubnet() method', function() {
+    // Test cases calculated with http://www.subnet-calculator.com/
+    var ipv4Subnet = ip.cidrSubnet('192.168.1.134/26');
+
+    it('should compute an ipv4 network address', function() {
+      assert.equal(ipv4Subnet.networkAddress, '192.168.1.128');
+    });
+
+    it('should compute an ipv4 network\'s first address', function() {
+      assert.equal(ipv4Subnet.firstAddress, '192.168.1.129');
+    });
+
+    it('should compute an ipv4 network\'s last address', function() {
+      assert.equal(ipv4Subnet.lastAddress, '192.168.1.190');
+    });
+
+    it('should compute an ipv4 broadcast address', function() {
+      assert.equal(ipv4Subnet.broadcastAddress, '192.168.1.191');
+    });
+
+    it('should compute an ipv4 subnet number of addresses', function() {
+      assert.equal(ipv4Subnet.length, 64);
+    });
+
+    it('should compute an ipv4 subnet number of addressable hosts', function() {
+      assert.equal(ipv4Subnet.numHosts, 62);
+    });
+
+    it('should compute an ipv4 subnet mask', function() {
+      assert.equal(ipv4Subnet.subnetMask, '255.255.255.192');
+    });
+
+    it('should compute an ipv4 subnet mask\'s length', function() {
+      assert.equal(ipv4Subnet.subnetMaskLength, 26);
+    });
+
+    it('should know whether a subnet contains an address', function() {
+      assert.equal(ipv4Subnet.contains('192.168.1.180'), true);
+    });
+
+    it('should know whether a subnet contains an address', function() {
+      assert.equal(ipv4Subnet.contains('192.168.1.195'), false);
+    });
+
+  });
+
+  describe('cidr() method', function() {
+    it('should mask address in CIDR notation', function() {
+      assert.equal(ip.cidr('192.168.1.134/26'), '192.168.1.128');
+      assert.equal(ip.cidr('2607:f0d0:1002:51::4/56'), '2607:f0d0:1002::');
+    });
+  });
+
+  describe('isEqual() method', function() {
+    it('should check if addresses are equal', function() {
+      assert(ip.isEqual('127.0.0.1', '::7f00:1'));
+      assert(!ip.isEqual('127.0.0.1', '::7f00:2'));
+      assert(ip.isEqual('127.0.0.1', '::ffff:7f00:1'));
+      assert(!ip.isEqual('127.0.0.1', '::ffaf:7f00:1'));
+      assert(ip.isEqual('::ffff:127.0.0.1', '::ffff:127.0.0.1'));
+      assert(ip.isEqual('::ffff:127.0.0.1', '127.0.0.1'));
+    });
+  });
+
+
+  describe('isPrivate() method', function() {
+    it('should check if an address is localhost', function() {
+      assert.equal(ip.isPrivate('127.0.0.1'), true);
+    });
+
+    it('should check if an address is from a 192.168.x.x network', function() {
+      assert.equal(ip.isPrivate('192.168.0.123'), true);
+      assert.equal(ip.isPrivate('192.168.122.123'), true);
+      assert.equal(ip.isPrivate('192.162.1.2'), false);
+    });
+
+    it('should check if an address is from a 172.16.x.x network', function() {
+      assert.equal(ip.isPrivate('172.16.0.5'), true);
+      assert.equal(ip.isPrivate('172.16.123.254'), true);
+      assert.equal(ip.isPrivate('171.16.0.5'), false);
+      assert.equal(ip.isPrivate('172.25.232.15'), true);
+      assert.equal(ip.isPrivate('172.15.0.5'), false);
+      assert.equal(ip.isPrivate('172.32.0.5'), false);
+    });
+
+    it('should check if an address is from a 169.254.x.x network', function() {
+      assert.equal(ip.isPrivate('169.254.2.3'), true);
+      assert.equal(ip.isPrivate('169.254.221.9'), true);
+      assert.equal(ip.isPrivate('168.254.2.3'), false);
+    });
+
+    it('should check if an address is from a 10.x.x.x network', function() {
+      assert.equal(ip.isPrivate('10.0.2.3'), true);
+      assert.equal(ip.isPrivate('10.1.23.45'), true);
+      assert.equal(ip.isPrivate('12.1.2.3'), false);
+    });
+
+    it('should check if an address is from a private IPv6 network', function() {
+      assert.equal(ip.isPrivate('fd12:3456:789a:1::1'), true);
+      assert.equal(ip.isPrivate('fe80::f2de:f1ff:fe3f:307e'), true);
+      assert.equal(ip.isPrivate('::ffff:10.100.1.42'), true);
+      assert.equal(ip.isPrivate('::FFFF:172.16.200.1'), true);
+      assert.equal(ip.isPrivate('::ffff:192.168.0.1'), true);
+    });
+
+    it('should check if an address is from the internet', function() {
+      assert.equal(ip.isPrivate('165.225.132.33'), false); // joyent.com
+    });
+
+    it('should check if an address is a loopback IPv6 address', function() {
+      assert.equal(ip.isPrivate('::'), true);
+      assert.equal(ip.isPrivate('::1'), true);
+      assert.equal(ip.isPrivate('fe80::1'), true);
+    });
+  });
+
+  describe('loopback() method', function() {
+    describe('undefined', function() {
+      it('should respond with 127.0.0.1', function() {
+        assert.equal(ip.loopback(), '127.0.0.1')
+      });
+    });
+
+    describe('ipv4', function() {
+      it('should respond with 127.0.0.1', function() {
+        assert.equal(ip.loopback('ipv4'), '127.0.0.1')
+      });
+    });
+
+    describe('ipv6', function() {
+      it('should respond with fe80::1', function() {
+        assert.equal(ip.loopback('ipv6'), 'fe80::1')
+      });
+    });
+  });
+
+  describe('isLoopback() method', function() {
+    describe('127.0.0.1', function() {
+      it('should respond with true', function() {
+        assert.ok(ip.isLoopback('127.0.0.1'))
+      });
+    });
+
+    describe('127.8.8.8', function () {
+      it('should respond with true', function () {
+        assert.ok(ip.isLoopback('127.8.8.8'))
+      });
+    });
+
+    describe('8.8.8.8', function () {
+      it('should respond with false', function () {
+        assert.equal(ip.isLoopback('8.8.8.8'), false);
+      });
+    });
+
+    describe('fe80::1', function() {
+      it('should respond with true', function() {
+        assert.ok(ip.isLoopback('fe80::1'))
+      });
+    });
+
+    describe('::1', function() {
+      it('should respond with true', function() {
+        assert.ok(ip.isLoopback('::1'))
+      });
+    });
+
+    describe('::', function() {
+      it('should respond with true', function() {
+        assert.ok(ip.isLoopback('::'))
+      });
+    });
+  });
+
+  describe('address() method', function() {
+    describe('undefined', function() {
+      it('should respond with a private ip', function() {
+        assert.ok(ip.isPrivate(ip.address()));
+      });
+    });
+
+    describe('private', function() {
+      [ undefined, 'ipv4', 'ipv6' ].forEach(function(family) {
+        describe(family, function() {
+          it('should respond with a private ip', function() {
+            assert.ok(ip.isPrivate(ip.address('private', family)));
+          });
+        });
+      });
+    });
+
+    var interfaces = os.networkInterfaces();
+
+    Object.keys(interfaces).forEach(function(nic) {
+      describe(nic, function() {
+        [ undefined, 'ipv4' ].forEach(function(family) {
+          describe(family, function() {
+            it('should respond with an ipv4 address', function() {
+              var addr = ip.address(nic, family);
+              assert.ok(!addr || net.isIPv4(addr));
+            });
+          });
+        });
+
+        describe('ipv6', function() {
+          it('should respond with an ipv6 address', function() {
+            var addr = ip.address(nic, 'ipv6');
+            assert.ok(!addr || net.isIPv6(addr));
+          });
+        })
+      });
+    });
+  });
+
+  describe('toLong() method', function() {
+    it('should respond with a int', function() {
+      assert.equal(ip.toLong('127.0.0.1'), 2130706433);
+      assert.equal(ip.toLong('255.255.255.255'), 4294967295);
+    });
+  });
+
+  describe('fromLong() method', function() {
+    it('should repond with ipv4 address', function() {
+      assert.equal(ip.fromLong(2130706433), '127.0.0.1');
+      assert.equal(ip.fromLong(4294967295), '255.255.255.255');
+    });
+  })
+});
--- a/tools/rewriting/ThirdPartyPaths.txt
+++ b/tools/rewriting/ThirdPartyPaths.txt
@@ -100,16 +100,18 @@ security/sandbox/chromium/
 security/sandbox/chromium-shim/
 testing/gtest/gmock/
 testing/gtest/gtest/
 testing/talos/talos/tests/dromaeo/
 testing/talos/talos/tests/kraken/
 testing/talos/talos/tests/v8_7/
 testing/web-platform/tests/resources/webidl2/
 testing/web-platform/tests/tools/third_party/
+testing/xpcshell/node-ip/
+testing/xpcshell/dns-packet/
 third_party/
 toolkit/components/jsoncpp/
 toolkit/components/protobuf/
 toolkit/components/url-classifier/chromium/
 toolkit/components/url-classifier/protobuf/
 toolkit/crashreporter/breakpad-client/
 toolkit/crashreporter/google-breakpad/
 toolkit/recordreplay/udis86/