Bug 1272101 - Changes in preparation for FlyWeb landing. Add JS implementation of nsDNSServiceDiscovery. r=hurley
authorJustin D'Arcangelo <jdarcangelo@mozilla.com>, Kannan Vijayan <kvijayan@mozilla.com>
Wed, 01 Jun 2016 16:22:27 -0400
changeset 339011 c4053b6d8c7704251776f52cdeb98a2c8d45fff4
parent 339010 724b9cdd239f27baab935e6ef3ab8b5b2ed59816
child 339012 7217724754491b5d40002d35012b73f670f76498
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershurley
bugs1272101
milestone49.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 1272101 - Changes in preparation for FlyWeb landing. Add JS implementation of nsDNSServiceDiscovery. r=hurley
netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp
netwerk/dns/mdns/libmdns/MulticastDNSFallback.jsm
netwerk/dns/mdns/libmdns/fallback/DNSPacket.jsm
netwerk/dns/mdns/libmdns/fallback/DNSRecord.jsm
netwerk/dns/mdns/libmdns/fallback/DNSResourceRecord.jsm
netwerk/dns/mdns/libmdns/fallback/DNSTypes.jsm
netwerk/dns/mdns/libmdns/fallback/DataReader.jsm
netwerk/dns/mdns/libmdns/fallback/DataWriter.jsm
netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm
netwerk/dns/mdns/libmdns/moz.build
netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js
netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp
--- a/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp
@@ -626,20 +626,21 @@ ResolveOperator::Reply(DNSServiceRef aSd
   if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) {
     LOG_E("ResolveOperator::Reply (%d)", aErrorCode);
     return;
   }
 
   // Resolve TXT record
   int count = TXTRecordGetCount(aTxtLen, aTxtRecord);
   LOG_I("resolve: txt count = %d, len = %d", count, aTxtLen);
-  nsCOMPtr<nsIWritablePropertyBag2> attributes = nullptr;
+  nsCOMPtr<nsIWritablePropertyBag2> attributes = new nsHashPropertyBag();
+  if (NS_WARN_IF(!attributes)) {
+    return;
+  }
   if (count) {
-    attributes = new nsHashPropertyBag();
-    if (NS_WARN_IF(!attributes)) { return; }
     for (int i = 0; i < count; ++i) {
       char key[TXT_BUFFER_SIZE] = { '\0' };
       uint8_t vSize = 0;
       const void* value = nullptr;
       if (kDNSServiceErr_NoError !=
           TXTRecordGetItemAtIndex(aTxtLen,
                                   aTxtRecord,
                                   i,
deleted file mode 100644
--- a/netwerk/dns/mdns/libmdns/MulticastDNSFallback.jsm
+++ /dev/null
@@ -1,577 +0,0 @@
-/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-/* jshint esnext: true, moz: true */
-
-'use strict';
-
-this.EXPORTED_SYMBOLS = ['MulticastDNS'];
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-Cu.import('resource://gre/modules/Services.jsm');
-Cu.import('resource://gre/modules/XPCOMUtils.jsm');
-
-const MDNS_PORT = 5353;
-const MDNS_ADDRESS = '224.0.0.251';
-
-const DNS_REC_TYPE_PTR = 12;
-const DNS_REC_TYPE_TXT = 16;
-const DNS_REC_TYPE_SRV = 33;
-const DNS_REC_TYPE_A   = 1;
-const DNS_REC_TYPE_NSEC= 47;
-
-const DNS_CLASS_QU = 0x8000;
-const DNS_CLASS_IN = 0x0001;
-
-const DNS_SECTION_QD = 'qd';
-const DNS_SECTION_AN = 'an';
-const DNS_SECTION_NS = 'ns';
-const DNS_SECTION_AR = 'ar';
-
-const DEBUG = false;
-
-function debug(msg) {
-  Services.console.logStringMessage('MulticastDNSFallback: ' + msg);
-}
-
-/* The following was taken from https://raw.githubusercontent.com/GoogleChrome/chrome-app-samples/master/mdns-browser/dns.js */
-
-/**
- * DataWriter writes data to an ArrayBuffer, presenting it as the instance
- * variable 'buffer'.
- *
- * @constructor
- */
-let DataWriter = function(opt_size) {
-  let loc = 0;
-  let view = new Uint8Array(new ArrayBuffer(opt_size || 512));
-
-  this.byte_ = function(v) {
-    view[loc] = v;
-    ++loc;
-    this.buffer = view.buffer.slice(0, loc);
-  }.bind(this);
-};
-
-DataWriter.prototype.byte = function(v) {
-  this.byte_(v);
-  return this;
-};
-
-DataWriter.prototype.short = function(v) {
-  return this.byte((v >> 8) & 0xff).byte(v & 0xff);
-};
-
-DataWriter.prototype.long = function(v) {
-  return this.short((v >> 16) & 0xffff).short(v & 0xffff);
-};
-
-/**
- * Writes a DNS name. If opt_ref is specified, will finish this name with a
- * suffix reference (i.e., 0xc0 <ref>). If not, then will terminate with a NULL
- * byte.
- */
-DataWriter.prototype.name = function(v, opt_ref) {
-  let parts = v.split('.');
-  parts.forEach(function(part) {
-    this.byte(part.length);
-    for (let i = 0; i < part.length; ++i) {
-      this.byte(part.charCodeAt(i));
-    }
-  }.bind(this));
-  if (opt_ref) {
-    this.byte(0xc0).byte(opt_ref);
-  } else {
-    this.byte(0);
-  }
-  return this;
-};
-
-/**
- * DataConsumer consumes data from an ArrayBuffer.
- *
- * @constructor
- */
-let DataConsumer = function(arg) {
-  if (arg instanceof Uint8Array) {
-    this.view_ = arg;
-  } else {
-    this.view_ = new Uint8Array(arg);
-  }
-  this.loc_ = 0;
-};
-
-/**
- * @return whether this DataConsumer has consumed all its data
- */
-DataConsumer.prototype.isEOF = function() {
-  return this.loc_ >= this.view_.byteLength;
-};
-
-/**
- * @param length {integer} number of bytes to return from the front of the view
- * @return a Uint8Array
- */
-DataConsumer.prototype.slice = function(length) {
-  let view = this.view_.subarray(this.loc_, this.loc_ + length);
-  this.loc_ += length;
-  return view;
-};
-
-DataConsumer.prototype.byte = function() {
-  this.loc_ += 1;
-  return this.view_[this.loc_ - 1];
-};
-
-DataConsumer.prototype.short = function() {
-  return (this.byte() << 8) + this.byte();
-};
-
-DataConsumer.prototype.long = function() {
-  return (this.short() << 16) + this.short();
-};
-
-/**
- * Consumes a DNS name, which will finish with a NULL byte.
- */
-DataConsumer.prototype.name = function() {
-  let parts = [];
-  for (;;) {
-    let len = this.byte();
-    if (!len) {
-      break;
-    } else if (len == 0xc0) {
-      // concat suffix reference (i.e., 0xc0 <ref>).
-      let ref = this.byte();
-      let refConsumer = new DataConsumer(new Uint8Array(this.view_.buffer, ref));
-      parts.push(refConsumer.name());
-      break;
-    }
-
-    // Otherwise, consume a string!
-    let v = '';
-    while (len-- > 0) {
-      v += String.fromCharCode(this.byte());
-    }
-    parts.push(v);
-  }
-  return parts.join('.');
-};
-
-/**
- * Consumes a string according to length.
- */
-DataConsumer.prototype.string = function() {
-  let len = this.byte();
-  if (!len) {
-    return;
-  }
-
-  let v = '';
-  while (len-- > 0) {
-    v += String.fromCharCode(this.byte());
-  }
-  return v;
-};
-
-/**
- * DNSPacket holds the state of a DNS packet. It can be modified or serialized
- * in-place.
- *
- * @constructor
- */
-let DNSPacket = function(opt_flags) {
-  this.flags_ = opt_flags || 0; /* uint16 */
-  this.data_ = {};
-  this.data_[DNS_SECTION_QD] = [];
-  this.data_[DNS_SECTION_AN] = [];
-  this.data_[DNS_SECTION_NS] = [];
-  this.data_[DNS_SECTION_AR] = [];
-};
-
-/**
- * Parse a DNSPacket from an ArrayBuffer (or Uint8Array).
- */
-DNSPacket.parse = function(buffer) {
-  let consumer = new DataConsumer(buffer);
-  if (consumer.short()) {
-    throw new Error('DNS packet must start with 00 00');
-  }
-  let flags = consumer.short();
-  let count = {};
-  count[DNS_SECTION_QD] = consumer.short();
-  count[DNS_SECTION_AN] = consumer.short();
-  count[DNS_SECTION_NS] = consumer.short();
-  count[DNS_SECTION_AR] = consumer.short();
-
-  let packet = new DNSPacket(flags);
-
-  // Parse the QUESTION section.
-  for (let i = 0; i < count[DNS_SECTION_QD]; ++i) {
-    let part = new DNSRecord(
-      consumer.name(),
-      consumer.short(),  // type
-      consumer.short()); // class
-    packet.push(DNS_SECTION_QD, part);
-  }
-
-  // Parse the ANSWER, AUTHORITY and ADDITIONAL sections.
-  [DNS_SECTION_AN, DNS_SECTION_NS, DNS_SECTION_AR].forEach(function(section) {
-    for (let i = 0; i < count[section]; ++i) {
-      let part = new DNSRecord(
-        consumer.name(),
-        consumer.short(),
-        consumer.short(), // class
-        consumer.long(),  // ttl
-        consumer.slice(consumer.short()));
-      packet.push(section, part);
-    }
-  });
-
-  if (consumer.isEOF()) {
-    DEBUG && debug('was not EOF on incoming packet');
-  }
-  return packet;
-};
-
-DNSPacket.prototype.push = function(section, record) {
-  this.data_[section].push(record);
-};
-
-DNSPacket.prototype.each = function(section) {
-  let filter = false;
-  let call;
-  if (arguments.length == 2) {
-    call = arguments[1];
-  } else {
-    filter = arguments[1];
-    call = arguments[2];
-  }
-  this.data_[section].forEach(function(rec) {
-    if (!filter || rec.type == filter) {
-      call(rec);
-    }
-  });
-};
-
-/**
- * Serialize this DNSPacket into an ArrayBuffer for sending over UDP.
- */
-DNSPacket.prototype.serialize = function() {
-  let out = new DataWriter();
-  let s = [DNS_SECTION_QD, DNS_SECTION_AN, DNS_SECTION_NS, DNS_SECTION_AR];
-
-  out.short(0).short(this.flags_);
-
-  s.forEach(function(section) {
-    out.short(this.data_[section].length);
-  }.bind(this));
-
-  s.forEach(function(section) {
-    this.data_[section].forEach(function(rec) {
-      out.name(rec.name).short(rec.type).short(rec.cl);
-
-      if (section != DNS_SECTION_QD) {
-        // TODO: implement .bytes()
-        throw new Error('can\'t yet serialize non-QD records');
-        //        out.long(rec.ttl).bytes(rec.data_);
-      }
-    });
-  }.bind(this));
-
-  return out.buffer;
-};
-
-/**
- * DNSRecord is a record inside a DNS packet; e.g. a QUESTION, or an ANSWER,
- * AUTHORITY, or ADDITIONAL record. Note that QUESTION records are special,
- * and do not have ttl or data.
- */
-let DNSRecord = function(name, type, cl, opt_ttl, opt_data) {
-  this.name = name;
-  this.type = type;
-  this.cl = cl;
-
-  this.isQD = (arguments.length == 3);
-  if (!this.isQD) {
-    this.ttl = opt_ttl;
-    this.data_ = opt_data;
-  }
-};
-
-DNSRecord.prototype.asName = function() {
-  return new DataConsumer(this.data_).name();
-};
-
-DNSRecord.prototype.asSRV = function() {
-  if (this.type !== DNS_REC_TYPE_SRV) {
-    return null;
-  }
-
-  let consumer = new DataConsumer(this.data_);
-  let data_length = this.data_.length;
-  return {
-    priority: consumer.short(),
-    weight: consumer.short(),
-    port: consumer.short(),
-    target: consumer.name() + consumer.name(),
-  };
-};
-
-DNSRecord.prototype.asTXT = function() {
-  if (this.type !== DNS_REC_TYPE_TXT) {
-    return null;
-  }
-
-  let consumer = new DataConsumer(this.data_);
-  let attributes = [];
-  while(!consumer.isEOF()) {
-    attributes.push(consumer.string());
-  }
-
-  return attributes;
-};
-
-/* end https://raw.githubusercontent.com/GoogleChrome/chrome-app-samples/master/mdns-browser/dns.js */
-
-/**
- * Parse fully qualified domain name to service name, instance name,
- * and domain name. See https://tools.ietf.org/html/rfc6763#section-7.
- *
- * example: The Server._http._tcp.example.com
- *   instance name = "The Server"
- *   service type = "_http._tcp"
- *   domain = "example.com"
- * @private
- */
-function _parseDomainName(str) {
-  let items = str.split('.');
-  let idx = items.findIndex(function(element) {
-    return element === '_tcp' || element === '_udp';
-  });
-
-  return {
-    instanceName: items.splice(0, idx - 1).join('.'),
-    serviceType: items.splice(0, 2). join('.'),
-    domainName: items.join('.')
-  };
-}
-
-function _createPropertyBag(map) {
-  let bag = Cc['@mozilla.org/hash-property-bag;1']
-              .createInstance(Ci.nsIWritablePropertyBag);
-
-  for (let entry of map.entries()) {
-    bag.setProperty(entry[0], entry[1]);
-  }
-
-  return bag;
-}
-
-let MulticastDNS = function() {
-  this._targets = new Map();
-};
-
-MulticastDNS.prototype = {
-  socket: null,
-  //public API
-  startDiscovery: function(aServiceType, aListener) {
-    DEBUG && debug('startDiscovery for ' + aServiceType);
-    let { serviceType } = _parseDomainName(aServiceType);
-    this._addServiceListener(serviceType, aListener);
-
-    try {
-      this._ensureSocket();
-      this._query(serviceType + '.local', DNS_REC_TYPE_PTR);
-      aListener.onDiscoveryStarted(serviceType);
-    } catch (e) {
-      DEBUG && debug('onStartDiscoveryFailed: ' + serviceType + ' (' + e + ')');
-      this._removeServiceListener(serviceType, aListener);
-      aListener.onStartDiscoveryFailed(serviceType, Cr.NS_ERROR_FAILURE);
-    }
-  },
-
-  stopDiscovery: function(aServiceType, aListener) {
-    DEBUG && debug('stopDiscovery for ' + aServiceType);
-    let { serviceType } = _parseDomainName(aServiceType);
-    this._removeServiceListener(serviceType, aListener);
-
-    aListener.onDiscoveryStopped(serviceType);
-    if (this._targets.size === 0) {
-      DEBUG && debug('close current socket');
-      this.socket.close();
-      delete this.socket;
-    }
-  },
-
-  registerService: function(aServiceInfo, aListener) {
-    DEBUG && debug('service registration is not supported');
-    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-  },
-
-  unregisterService: function(aServiceInfo, aListener) {
-    DEBUG && debug('service registration is not supported');
-    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-  },
-
-  resolveService: function(aServiceInfo, aListener) {
-    DEBUG && debug('address info is already resolve during discovery phase');
-    aListener.onServiceResolved(aServiceInfo);
-  },
-
-  //private API
-  onReceive: function(info) {
-    let packet = DNSPacket.parse(info.rawData);
-    let serviceRecords = {};
-
-    packet.each(DNS_SECTION_AR, DNS_REC_TYPE_SRV, (rec) => {
-      DEBUG && debug('recieve SRV: ' + rec.name);
-      let srv = rec.asSRV();
-      serviceRecords[rec.name] = {
-        port: srv.port,
-        host: srv.target,
-      };
-    });
-
-    packet.each(DNS_SECTION_AR, DNS_REC_TYPE_TXT, (rec) => {
-      DEBUG && debug('recieve TXT: ' + rec.name);
-      if (!serviceRecords[rec.name]) {
-        return;
-      }
-
-      let txt =  rec.asTXT();
-      let attributes = new Map();
-      for(let x in txt) {
-        let idx = x.indexOf('=');
-        if (idx < 0) {
-          attributes.set(txt[x], true);
-          continue;
-        }
-
-        let key = txt[x].substring(0, idx);
-        let value = txt[x].substring(idx + 1);
-        attributes.set(key, value);
-      }
-      serviceRecords[rec.name].attributes = attributes;
-    });
-
-    packet.each(DNS_SECTION_AN, DNS_REC_TYPE_PTR, (rec) => {
-      DEBUG && debug('recieve PTR: ' + rec.name);
-      let { serviceType: answerType } = _parseDomainName(rec.name);
-      if (this._targets.has(answerType)) {
-        let name = rec.asName();
-        DEBUG && debug('>> for ' + name);
-        let {instanceName, serviceType, domainName} = _parseDomainName(name);
-        let serviceInfo = {
-          host: serviceRecords[name].host,
-          address: info.fromAddr.address,
-          port: serviceRecords[name].port,
-          serviceType: serviceType,
-          serviceName: instanceName,
-          domainName: domainName,
-          attributes: _createPropertyBag(serviceRecords[name].attributes),
-          QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceInfo]),
-        };
-        this._targets.get(serviceType).forEach(function(listener) {
-          listener.onServiceFound(serviceInfo);
-        });
-      }
-    });
-  },
-
-  /**
-   * Handles network error occured while waiting for data.
-   * @private
-   */
-  onReceiveError: function(socket, status) {
-    DEBUG && debug('receiver socket error' + status);
-    return true;
-  },
-
-  /**
-   * Broadcasts for services on the given socket/address.
-   * @private
-   */
-  _query: function(search, type) {
-    DEBUG && debug('query for service: ' + search);
-    let packet = new DNSPacket();
-    packet.push(DNS_SECTION_QD, new DNSRecord(search, type, DNS_CLASS_IN | DNS_CLASS_QU));
-
-    this._broadcast(this.socket, packet);
-  },
-
-  /**
-   * Broadcasts a MDNS packet on the given socket/address.
-   * @private
-   */
-  _broadcast: function(sock, packet) {
-    let raw = new DataView(packet.serialize());
-    let length =  raw.byteLength;
-    let buf = [];
-    for (let x = 0; x < length; x++) {
-      let charcode = raw.getUint8(x);
-      buf[x] = charcode;
-    }
-    sock.send(MDNS_ADDRESS, MDNS_PORT, buf, buf.length);
-  },
-
-  _addServiceListener: function(serviceType, listener) {
-    let listeners = this._targets.get(serviceType);
-    if (!listeners) {
-      listeners = [];
-      this._targets.set(serviceType, listeners);
-    }
-
-    if (!listeners.find((element) => {
-          return element === listener;
-        })) {
-      DEBUG && debug('insert new listener');
-      listeners.push(listener);
-    }
-  },
-
-  _removeServiceListener: function(serviceType, listener) {
-    if (!this._targets.has(serviceType)) {
-      DEBUG && debug('listener doesnt exist');
-      return;
-    }
-
-    let listeners = this._targets.get(serviceType);
-    let idx = listeners.findIndex(function(element) {
-      return element === listener;
-    });
-
-    if (idx >= 0) {
-      listeners.splice(idx, 1);
-    }
-
-    if (listeners.length === 0) {
-      this._targets.delete(serviceType);
-    }
-  },
-
-  _ensureSocket: function() {
-    if (this.socket) {
-      DEBUG && debug('reuse current socket');
-      return;
-    }
-
-    this.socket = Cc['@mozilla.org/network/udp-socket;1']
-                    .createInstance(Ci.nsIUDPSocket);
-    let self = this;
-    this.socket.init(MDNS_PORT, false,
-                     Services.scriptSecurityManager.getSystemPrincipal());
-    this.socket.asyncListen({
-      onPacketReceived: function(aSocket, aMessage) {
-        self.onReceive(aMessage);
-      },
-
-      onStopListening: function(aSocket, aStatus) {
-        self.onReceiveError(aSocket, aStatus);
-      },
-    });
-    this.socket.joinMulticast(MDNS_ADDRESS);
-  },
-};
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DNSPacket.jsm
@@ -0,0 +1,297 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.EXPORTED_SYMBOLS = ['DNSPacket'];
+
+const { utils: Cu } = Components;
+
+Cu.import('resource://gre/modules/Services.jsm');
+
+Cu.import('resource://gre/modules/DataReader.jsm');
+Cu.import('resource://gre/modules/DataWriter.jsm');
+Cu.import('resource://gre/modules/DNSRecord.jsm');
+Cu.import('resource://gre/modules/DNSResourceRecord.jsm');
+
+const DEBUG = true;
+
+function debug(msg) {
+  Services.console.logStringMessage('DNSPacket: ' + msg);
+}
+
+let DNS_PACKET_SECTION_TYPES = [
+  'QD', // Question
+  'AN', // Answer
+  'NS', // Authority
+  'AR'  // Additional
+];
+
+/**
+ * DNS Packet Structure
+ * *************************************************
+ *
+ * Header
+ * ======
+ *
+ * 00                   2-Bytes                   15
+ * -------------------------------------------------
+ * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
+ * -------------------------------------------------
+ * |<==================== ID =====================>|
+ * |QR|<== OP ===>|AA|TC|RD|RA|UN|AD|CD|<== RC ===>|
+ * |<================== QDCOUNT ==================>|
+ * |<================== ANCOUNT ==================>|
+ * |<================== NSCOUNT ==================>|
+ * |<================== ARCOUNT ==================>|
+ * -------------------------------------------------
+ *
+ * ID:        2-Bytes
+ * FLAGS:     2-Bytes
+ *  - QR:     1-Bit
+ *  - OP:     4-Bits
+ *  - AA:     1-Bit
+ *  - TC:     1-Bit
+ *  - RD:     1-Bit
+ *  - RA:     1-Bit
+ *  - UN:     1-Bit
+ *  - AD:     1-Bit
+ *  - CD:     1-Bit
+ *  - RC:     4-Bits
+ * QDCOUNT:   2-Bytes
+ * ANCOUNT:   2-Bytes
+ * NSCOUNT:   2-Bytes
+ * ARCOUNT:   2-Bytes
+ *
+ *
+ * Data
+ * ====
+ *
+ * 00                   2-Bytes                   15
+ * -------------------------------------------------
+ * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
+ * -------------------------------------------------
+ * |<???=============== QD[...] ===============???>|
+ * |<???=============== AN[...] ===============???>|
+ * |<???=============== NS[...] ===============???>|
+ * |<???=============== AR[...] ===============???>|
+ * -------------------------------------------------
+ *
+ * QD:        ??-Bytes
+ * AN:        ??-Bytes
+ * NS:        ??-Bytes
+ * AR:        ??-Bytes
+ *
+ *
+ * Question Record
+ * ===============
+ *
+ * 00                   2-Bytes                   15
+ * -------------------------------------------------
+ * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
+ * -------------------------------------------------
+ * |<???================ NAME =================???>|
+ * |<=================== TYPE ====================>|
+ * |<=================== CLASS ===================>|
+ * -------------------------------------------------
+ *
+ * NAME:      ??-Bytes
+ * TYPE:      2-Bytes
+ * CLASS:     2-Bytes
+ *
+ *
+ * Resource Record
+ * ===============
+ *
+ * 00                   4-Bytes                   31
+ * -------------------------------------------------
+ * |00|02|04|06|08|10|12|14|16|18|20|22|24|26|28|30|
+ * -------------------------------------------------
+ * |<???================ NAME =================???>|
+ * |<======= TYPE ========>|<======= CLASS =======>|
+ * |<==================== TTL ====================>|
+ * |<====== DATALEN ======>|<???==== DATA =====???>|
+ * -------------------------------------------------
+ *
+ * NAME:      ??-Bytes
+ * TYPE:      2-Bytes
+ * CLASS:     2-Bytes
+ * DATALEN:   2-Bytes
+ * DATA:      ??-Bytes (Specified By DATALEN)
+ */
+class DNSPacket {
+  constructor() {
+    this._flags = _valueToFlags(0x0000);
+    this._records = {};
+
+    DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
+      this._records[sectionType] = [];
+    });
+  }
+
+  static parse(data) {
+    let reader = new DataReader(data);
+    if (reader.getValue(2) !== 0x0000) {
+      throw new Error('Packet must start with 0x0000');
+    }
+
+    let packet = new DNSPacket();
+    packet._flags = _valueToFlags(reader.getValue(2));
+
+    let recordCounts = {};
+
+    // Parse the record counts.
+    DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
+      recordCounts[sectionType] = reader.getValue(2);
+    });
+
+    // Parse the actual records.
+    DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
+      let recordCount = recordCounts[sectionType];
+      for (let i = 0; i < recordCount; i++) {
+        if (sectionType === 'QD') {
+          packet.addRecord(sectionType,
+              DNSRecord.parseFromPacketReader(reader));
+        }
+
+        else {
+          packet.addRecord(sectionType,
+              DNSResourceRecord.parseFromPacketReader(reader));
+        }
+      }
+    });
+
+    if (!reader.eof) {
+      DEBUG && debug('Did not complete parsing packet data');
+    }
+
+    return packet;
+  }
+
+  getFlag(flag) {
+    return this._flags[flag];
+  }
+
+  setFlag(flag, value) {
+    this._flags[flag] = value;
+  }
+
+  addRecord(sectionType, record) {
+    this._records[sectionType].push(record);
+  }
+
+  getRecords(sectionTypes, recordType) {
+    let records = [];
+
+    sectionTypes.forEach((sectionType) => {
+      records = records.concat(this._records[sectionType]);
+    });
+
+    if (!recordType) {
+      return records;
+    }
+
+    return records.filter(r => r.recordType === recordType);
+  }
+
+  serialize() {
+    let writer = new DataWriter();
+
+    // Write leading 0x0000 (2 bytes)
+    writer.putValue(0x0000, 2);
+
+    // Write `flags` (2 bytes)
+    writer.putValue(_flagsToValue(this._flags), 2);
+
+    // Write lengths of record sections (2 bytes each)
+    DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
+      writer.putValue(this._records[sectionType].length, 2);
+    });
+
+    // Write records
+    DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
+      this._records[sectionType].forEach((record) => {
+        writer.putBytes(record.serialize());
+      });
+    });
+
+    return writer.data;
+  }
+
+  toJSON() {
+    return JSON.stringify(this.toJSONObject());
+  }
+
+  toJSONObject() {
+    let result = {flags: this._flags};
+    DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
+      result[sectionType] = [];
+
+      let records = this._records[sectionType];
+      records.forEach((record) => {
+        result[sectionType].push(record.toJSONObject());
+      });
+    });
+
+    return result;
+  }
+}
+
+/**
+ * @private
+ */
+function _valueToFlags(value) {
+  return {
+    QR: (value & 0x8000) >> 15,
+    OP: (value & 0x7800) >> 11,
+    AA: (value & 0x0400) >> 10,
+    TC: (value & 0x0200) >>  9,
+    RD: (value & 0x0100) >>  8,
+    RA: (value & 0x0080) >>  7,
+    UN: (value & 0x0040) >>  6,
+    AD: (value & 0x0020) >>  5,
+    CD: (value & 0x0010) >>  4,
+    RC: (value & 0x000f) >>  0
+  };
+}
+
+/**
+ * @private
+ */
+function _flagsToValue(flags) {
+  let value = 0x0000;
+
+  value += flags.QR & 0x01;
+
+  value <<= 4;
+  value += flags.OP & 0x0f;
+
+  value <<= 1;
+  value += flags.AA & 0x01;
+
+  value <<= 1;
+  value += flags.TC & 0x01;
+
+  value <<= 1;
+  value += flags.RD & 0x01;
+
+  value <<= 1;
+  value += flags.RA & 0x01;
+
+  value <<= 1;
+  value += flags.UN & 0x01;
+
+  value <<= 1;
+  value += flags.AD & 0x01;
+
+  value <<= 1;
+  value += flags.CD & 0x01;
+
+  value <<= 4;
+  value += flags.RC & 0x0f;
+
+  return value;
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DNSRecord.jsm
@@ -0,0 +1,70 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.EXPORTED_SYMBOLS = ['DNSRecord'];
+
+const { utils: Cu } = Components;
+
+Cu.import('resource://gre/modules/DataWriter.jsm');
+Cu.import('resource://gre/modules/DNSTypes.jsm');
+
+class DNSRecord {
+  constructor(properties = {}) {
+    this.name       = properties.name       || '';
+    this.recordType = properties.recordType || DNS_RECORD_TYPES.ANY;
+    this.classCode  = properties.classCode  || DNS_CLASS_CODES.IN;
+    this.cacheFlush = properties.cacheFlush || false;
+  }
+
+  static parseFromPacketReader(reader) {
+    let name       = reader.getLabel();
+    let recordType = reader.getValue(2);
+    let classCode  = reader.getValue(2);
+    let cacheFlush = (classCode & 0x8000) ? true : false;
+    classCode &= 0xff;
+
+    return new this({
+      name: name,
+      recordType: recordType,
+      classCode: classCode,
+      cacheFlush: cacheFlush
+    });
+  }
+
+  serialize() {
+    let writer = new DataWriter();
+
+    // Write `name` (ends with trailing 0x00 byte)
+    writer.putLabel(this.name);
+
+    // Write `recordType` (2 bytes)
+    writer.putValue(this.recordType, 2);
+
+    // Write `classCode` (2 bytes)
+    let classCode = this.classCode;
+    if (this.cacheFlush) {
+      classCode |= 0x8000;
+    }
+    writer.putValue(classCode, 2);
+
+    return writer.data;
+  }
+
+  toJSON() {
+    return JSON.stringify(this.toJSONObject());
+  }
+
+  toJSONObject() {
+    return {
+      name: this.name,
+      recordType: this.recordType,
+      classCode: this.classCode,
+      cacheFlush: this.cacheFlush
+    };
+  }
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DNSResourceRecord.jsm
@@ -0,0 +1,221 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.EXPORTED_SYMBOLS = ['DNSResourceRecord'];
+
+const { utils: Cu } = Components;
+
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/DataReader.jsm');
+Cu.import('resource://gre/modules/DataWriter.jsm');
+Cu.import('resource://gre/modules/DNSRecord.jsm');
+Cu.import('resource://gre/modules/DNSTypes.jsm');
+
+function debug(msg) {
+  Services.console.logStringMessage('MulticastDNS: ' + msg);
+}
+
+const DNS_RESOURCE_RECORD_DEFAULT_TTL = 120; // 120 seconds
+
+class DNSResourceRecord extends DNSRecord {
+  constructor(properties = {}) {
+    super(properties);
+
+    this.ttl  = properties.ttl  || DNS_RESOURCE_RECORD_DEFAULT_TTL;
+    this.data = properties.data || {};
+  }
+
+  static parseFromPacketReader(reader) {
+    let record = super.parseFromPacketReader(reader);
+
+    let ttl        = reader.getValue(4);
+    let recordData = reader.getBytes(reader.getValue(2));
+    let packetData = reader.data;
+
+    let data;
+
+    switch (record.recordType) {
+      case DNS_RECORD_TYPES.A:
+        data = _parseA(recordData, packetData);
+        break;
+      case DNS_RECORD_TYPES.PTR:
+        data = _parsePTR(recordData, packetData);
+        break;
+      case DNS_RECORD_TYPES.TXT:
+        data = _parseTXT(recordData, packetData);
+        break;
+      case DNS_RECORD_TYPES.SRV:
+        data = _parseSRV(recordData, packetData);
+        break;
+      default:
+        data = null;
+        break;
+    }
+
+    record.ttl  = ttl;
+    record.data = data;
+
+    return record;
+  }
+
+  serialize() {
+    let writer = new DataWriter(super.serialize());
+
+    // Write `ttl` (4 bytes)
+    writer.putValue(this.ttl, 4);
+
+    let data;
+
+    switch (this.recordType) {
+      case DNS_RECORD_TYPES.A:
+        data = _serializeA(this.data);
+        break;
+      case DNS_RECORD_TYPES.PTR:
+        data = _serializePTR(this.data);
+        break;
+      case DNS_RECORD_TYPES.TXT:
+        data = _serializeTXT(this.data);
+        break;
+      case DNS_RECORD_TYPES.SRV:
+        data = _serializeSRV(this.data);
+        break;
+      default:
+        data = new Uint8Array();
+        break;
+    }
+
+    // Write `data` length.
+    writer.putValue(data.length, 2);
+
+    // Write `data` (ends with trailing 0x00 byte)
+    writer.putBytes(data);
+
+    return writer.data;
+  }
+
+  toJSON() {
+    return JSON.stringify(this.toJSONObject());
+  }
+
+  toJSONObject() {
+    let result = super.toJSONObject();
+    result.ttl = this.ttl;
+    result.data = this.data;
+    return result;
+  }
+}
+
+/**
+ * @private
+ */
+function _parseA(recordData, packetData) {
+  let reader = new DataReader(recordData);
+
+  let parts = [];
+  for (let i = 0; i < 4; i++) {
+    parts.push(reader.getValue(1));
+  }
+
+  return parts.join('.');
+}
+
+/**
+ * @private
+ */
+function _parsePTR(recordData, packetData) {
+  let reader = new DataReader(recordData);
+
+  return reader.getLabel(packetData);
+}
+
+/**
+ * @private
+ */
+function _parseTXT(recordData, packetData) {
+  let reader = new DataReader(recordData);
+
+  let result = {};
+
+  let label = reader.getLabel(packetData);
+  if (label.length > 0) {
+    let parts = label.split('.');
+    parts.forEach((part) => {
+      let [name] = part.split('=', 1);
+      let value = part.substr(name.length + 1);
+      result[name] = value;
+    });
+  }
+
+  return result;
+}
+
+/**
+ * @private
+ */
+function _parseSRV(recordData, packetData) {
+  let reader = new DataReader(recordData);
+
+  let priority = reader.getValue(2);
+  let weight   = reader.getValue(2);
+  let port     = reader.getValue(2);
+  let target   = reader.getLabel(packetData);
+
+  return { priority, weight, port, target };
+}
+
+/**
+ * @private
+ */
+function _serializeA(data) {
+  let writer = new DataWriter();
+
+  let parts = data.split('.');
+  for (let i = 0; i < 4; i++) {
+    writer.putValue(parseInt(parts[i], 10) || 0);
+  }
+
+  return writer.data;
+}
+
+/**
+ * @private
+ */
+function _serializePTR(data) {
+  let writer = new DataWriter();
+
+  writer.putLabel(data);
+
+  return writer.data;
+}
+
+/**
+ * @private
+ */
+function _serializeTXT(data) {
+  let writer = new DataWriter();
+
+  for (let name in data) {
+    writer.putLengthString(name + '=' + data[name]);
+  }
+
+  return writer.data;
+}
+
+/**
+ * @private
+ */
+function _serializeSRV(data) {
+  let writer = new DataWriter();
+
+  writer.putValue(data.priority || 0, 2);
+  writer.putValue(data.weight   || 0, 2);
+  writer.putValue(data.port     || 0, 2);
+  writer.putLabel(data.target);
+
+  return writer.data;
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DNSTypes.jsm
@@ -0,0 +1,100 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.EXPORTED_SYMBOLS = [
+  'DNS_QUERY_RESPONSE_CODES',
+  'DNS_AUTHORITATIVE_ANSWER_CODES',
+  'DNS_CLASS_CODES',
+  'DNS_RECORD_TYPES'
+];
+
+let DNS_QUERY_RESPONSE_CODES = {
+  QUERY           : 0,      // RFC 1035 - Query
+  RESPONSE        : 1       // RFC 1035 - Reponse
+};
+
+let DNS_AUTHORITATIVE_ANSWER_CODES = {
+  NO              : 0,      // RFC 1035 - Not Authoritative
+  YES             : 1       // RFC 1035 - Is Authoritative
+};
+
+let DNS_CLASS_CODES = {
+  IN              : 0x01,   // RFC 1035 - Internet
+  CS              : 0x02,   // RFC 1035 - CSNET
+  CH              : 0x03,   // RFC 1035 - CHAOS
+  HS              : 0x04,   // RFC 1035 - Hesiod
+  NONE            : 0xfe,   // RFC 2136 - None
+  ANY             : 0xff,   // RFC 1035 - Any
+};
+
+let DNS_RECORD_TYPES = {
+  SIGZERO         : 0,      // RFC 2931
+  A               : 1,      // RFC 1035
+  NS              : 2,      // RFC 1035
+  MD              : 3,      // RFC 1035
+  MF              : 4,      // RFC 1035
+  CNAME           : 5,      // RFC 1035
+  SOA             : 6,      // RFC 1035
+  MB              : 7,      // RFC 1035
+  MG              : 8,      // RFC 1035
+  MR              : 9,      // RFC 1035
+  NULL            : 10,     // RFC 1035
+  WKS             : 11,     // RFC 1035
+  PTR             : 12,     // RFC 1035
+  HINFO           : 13,     // RFC 1035
+  MINFO           : 14,     // RFC 1035
+  MX              : 15,     // RFC 1035
+  TXT             : 16,     // RFC 1035
+  RP              : 17,     // RFC 1183
+  AFSDB           : 18,     // RFC 1183
+  X25             : 19,     // RFC 1183
+  ISDN            : 20,     // RFC 1183
+  RT              : 21,     // RFC 1183
+  NSAP            : 22,     // RFC 1706
+  NSAP_PTR        : 23,     // RFC 1348
+  SIG             : 24,     // RFC 2535
+  KEY             : 25,     // RFC 2535
+  PX              : 26,     // RFC 2163
+  GPOS            : 27,     // RFC 1712
+  AAAA            : 28,     // RFC 1886
+  LOC             : 29,     // RFC 1876
+  NXT             : 30,     // RFC 2535
+  EID             : 31,     // RFC ????
+  NIMLOC          : 32,     // RFC ????
+  SRV             : 33,     // RFC 2052
+  ATMA            : 34,     // RFC ????
+  NAPTR           : 35,     // RFC 2168
+  KX              : 36,     // RFC 2230
+  CERT            : 37,     // RFC 2538
+  DNAME           : 39,     // RFC 2672
+  OPT             : 41,     // RFC 2671
+  APL             : 42,     // RFC 3123
+  DS              : 43,     // RFC 4034
+  SSHFP           : 44,     // RFC 4255
+  IPSECKEY        : 45,     // RFC 4025
+  RRSIG           : 46,     // RFC 4034
+  NSEC            : 47,     // RFC 4034
+  DNSKEY          : 48,     // RFC 4034
+  DHCID           : 49,     // RFC 4701
+  NSEC3           : 50,     // RFC ????
+  NSEC3PARAM      : 51,     // RFC ????
+  HIP             : 55,     // RFC 5205
+  SPF             : 99,     // RFC 4408
+  UINFO           : 100,    // RFC ????
+  UID             : 101,    // RFC ????
+  GID             : 102,    // RFC ????
+  UNSPEC          : 103,    // RFC ????
+  TKEY            : 249,    // RFC 2930
+  TSIG            : 250,    // RFC 2931
+  IXFR            : 251,    // RFC 1995
+  AXFR            : 252,    // RFC 1035
+  MAILB           : 253,    // RFC 1035
+  MAILA           : 254,    // RFC 1035
+  ANY             : 255,    // RFC 1035
+  DLV             : 32769   // RFC 4431
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DataReader.jsm
@@ -0,0 +1,133 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.EXPORTED_SYMBOLS = ['DataReader'];
+
+class DataReader {
+  // `data` is `Uint8Array`
+  constructor(data, startByte = 0) {
+    this._data = data;
+    this._cursor = startByte;
+  }
+
+  get buffer() {
+    return this._data.buffer;
+  }
+
+  get data() {
+    return this._data;
+  }
+
+  get eof() {
+    return this._cursor >= this._data.length;
+  }
+
+  getBytes(length = 1) {
+    if (!length) {
+      return new Uint8Array();
+    }
+
+    let end = this._cursor + length;
+    if (end > this._data.length) {
+      return new Uint8Array();
+    }
+
+    let uint8Array = new Uint8Array(this.buffer.slice(this._cursor, end));
+    this._cursor += length;
+
+    return uint8Array;
+  }
+
+  getString(length) {
+    let uint8Array = this.getBytes(length);
+    return _uint8ArrayToString(uint8Array);
+  }
+
+  getValue(length) {
+    let uint8Array = this.getBytes(length);
+    return _uint8ArrayToValue(uint8Array);
+  }
+
+  getLabel(decompressData) {
+    let parts = [];
+    let partLength;
+
+    while ((partLength = this.getValue(1))) {
+      // If a length has been specified instead of a pointer,
+      // read the string of the specified length.
+      if (partLength !== 0xc0) {
+        parts.push(this.getString(partLength));
+        continue;
+      }
+
+      // TODO: Handle case where we have a pointer to the label
+      parts.push(String.fromCharCode(0xc0) + this.getString(1));
+      break;
+    }
+
+    let label = parts.join('.');
+
+    return _decompressLabel(label, decompressData || this._data);
+  }
+}
+
+/**
+ * @private
+ */
+function _uint8ArrayToValue(uint8Array) {
+  let length = uint8Array.length;
+  if (length === 0) {
+    return null;
+  }
+
+  let value = 0;
+  for (let i = 0; i < length; i++) {
+    value = value << 8;
+    value += uint8Array[i];
+  }
+
+  return value;
+}
+
+/**
+ * @private
+ */
+function _uint8ArrayToString(uint8Array) {
+  let length = uint8Array.length;
+  if (length === 0) {
+    return '';
+  }
+
+  let results = [];
+  for (let i = 0; i < length; i += 1024) {
+    results.push(String.fromCharCode.apply(null, uint8Array.subarray(i, i + 1024)));
+  }
+
+  return results.join('');
+}
+
+/**
+ * @private
+ */
+function _decompressLabel(label, decompressData) {
+  let result = '';
+
+  for (let i = 0, length = label.length; i < length; i++) {
+    if (label.charCodeAt(i) !== 0xc0) {
+      result += label.charAt(i);
+      continue;
+    }
+
+    i++;
+
+    let reader = new DataReader(decompressData, label.charCodeAt(i));
+    result += _decompressLabel(reader.getLabel(), decompressData);
+  }
+
+  return result;
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DataWriter.jsm
@@ -0,0 +1,98 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.EXPORTED_SYMBOLS = ['DataWriter'];
+
+class DataWriter {
+  constructor(data, maxBytes = 512) {
+    if (typeof data === 'number') {
+      maxBytes = data;
+      data = undefined;
+    }
+
+    this._buffer = new ArrayBuffer(maxBytes);
+    this._data = new Uint8Array(this._buffer);
+    this._cursor = 0;
+
+    if (data) {
+      this.putBytes(data);
+    }
+  }
+
+  get buffer() {
+    return this._buffer.slice(0, this._cursor);
+  }
+
+  get data() {
+    return new Uint8Array(this.buffer);
+  }
+
+  // `data` is `Uint8Array`
+  putBytes(data) {
+    if (this._cursor + data.length > this._data.length) {
+      throw new Error('DataWriter buffer is exceeded');
+    }
+
+    for (let i = 0, length = data.length; i < length; i++) {
+      this._data[this._cursor] = data[i];
+      this._cursor++;
+    }
+  }
+
+  putByte(byte) {
+    if (this._cursor + 1 > this._data.length) {
+      throw new Error('DataWriter buffer is exceeded');
+    }
+
+    this._data[this._cursor] = byte
+    this._cursor++;
+  }
+
+  putValue(value, length) {
+    length = length || 1;
+    if (length == 1) {
+      this.putByte(value);
+    } else {
+      this.putBytes(_valueToUint8Array(value, length));
+    }
+  }
+
+  putLabel(label) {
+    // Eliminate any trailing '.'s in the label (valid in text representation).
+    label = label.replace(/\.$/, '');
+    let parts = label.split('.');
+    parts.forEach((part) => {
+      this.putLengthString(part);
+    });
+    this.putValue(0);
+  }
+
+  putLengthString(string) {
+    if (string.length > 0xff) {
+        throw new Error("String too long.");
+    }
+    this.putValue(string.length);
+    for (let i = 0; i < string.length; i++) {
+      this.putValue(string.charCodeAt(i));
+    }
+  }
+}
+
+/**
+ * @private
+ */
+function _valueToUint8Array(value, length) {
+  let arrayBuffer = new ArrayBuffer(length);
+  let uint8Array = new Uint8Array(arrayBuffer);
+  for (let i = length - 1; i >= 0; i--) {
+    uint8Array[i] = value & 0xff;
+    value = value >> 8;
+  }
+
+  return uint8Array;
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm
@@ -0,0 +1,764 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* jshint esnext: true, moz: true */
+
+'use strict';
+
+this.EXPORTED_SYMBOLS = ['MulticastDNS'];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/Timer.jsm');
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+Cu.import('resource://gre/modules/DNSPacket.jsm');
+Cu.import('resource://gre/modules/DNSRecord.jsm');
+Cu.import('resource://gre/modules/DNSResourceRecord.jsm');
+Cu.import('resource://gre/modules/DNSTypes.jsm');
+
+const DEBUG = true;
+
+const MDNS_MULTICAST_GROUP = '224.0.0.251';
+const MDNS_PORT            = 5353;
+const DEFAULT_TTL          = 120;
+
+function debug(msg) {
+  dump('MulticastDNS: ' + msg + '\n');
+}
+
+function ServiceKey(svc) {
+  return "" + svc.serviceType.length + "/" + svc.serviceType + "|" +
+              svc.serviceName.length + "/" + svc.serviceName + "|" +
+              svc.port;
+}
+
+function TryGet(obj, name) {
+  try {
+    return obj[name];
+  } catch (err) {
+    return undefined;
+  }
+}
+
+function IsIpv4Address(addr) {
+  let parts = addr.split('.');
+  if (parts.length != 4) {
+    return false;
+  }
+  for (let part of parts) {
+    let partInt = Number.parseInt(part, 10);
+    if (partInt.toString() != part) {
+      return false;
+    }
+    if (partInt < 0 || partInt >= 256) {
+      return false;
+    }
+  }
+  return true;
+}
+
+class PublishedService {
+  constructor(attrs) {
+    this.serviceType = attrs.serviceType.replace(/\.$/, '');
+    this.serviceName = attrs.serviceName;
+    this.domainName = TryGet(attrs, 'domainName') || "local";
+    this.address = TryGet(attrs, 'address') || "0.0.0.0";
+    this.port = attrs.port;
+    this.serviceAttrs = _propertyBagToObject(TryGet(attrs, 'attributes') || {});
+    this.host = TryGet(attrs, 'host');
+    this.key = this.generateKey();
+    this.lastAdvertised = undefined;
+    this.advertiseTimer = undefined;
+  }
+
+  equals(svc) {
+    return (this.port == svc.port) &&
+           (this.serviceName == svc.serviceName) &&
+           (this.serviceType == svc.serviceType);
+  }
+
+  generateKey() {
+    return ServiceKey(this);
+  }
+
+  ptrMatch(name) {
+    return name == (this.serviceType + "." + this.domainName);
+  }
+
+  clearAdvertiseTimer() {
+    if (!this.advertiseTimer) {
+      return;
+    }
+    clearTimeout(this.advertiseTimer);
+    this.advertiseTimer = undefined;
+  }
+}
+
+class MulticastDNS {
+  constructor() {
+    this._listeners       = new Map();
+    this._broadcastSocket = undefined;
+    this._broadcastTimer  = undefined;
+    this._sockets         = new Map();
+    this._services        = new Map();
+    this._discovered      = new Map();
+  }
+
+  startDiscovery(aServiceType, aListener) {
+    DEBUG && debug('startDiscovery("' + aServiceType + '")');
+    let { serviceType } = _parseServiceDomainName(aServiceType);
+
+    this._addServiceListener(serviceType, aListener);
+
+    try {
+      this._query(serviceType + '.local');
+      aListener.onDiscoveryStarted(serviceType);
+    } catch (e) {
+      DEBUG && debug('startDiscovery("' + serviceType + '") FAILED: ' + e);
+      this._removeServiceListener(serviceType, aListener);
+      aListener.onStartDiscoveryFailed(serviceType, Cr.NS_ERROR_FAILURE);
+    }
+  }
+
+  stopDiscovery(aServiceType, aListener) {
+    DEBUG && debug('stopDiscovery("' + aServiceType + '")');
+    let { serviceType } = _parseServiceDomainName(aServiceType);
+
+    this._removeServiceListener(serviceType, aListener);
+    aListener.onDiscoveryStopped(serviceType);
+
+    this._checkCloseSockets();
+  }
+
+  resolveService(aServiceInfo, aListener) {
+    DEBUG && debug('resolveService(): ' + aServiceInfo.serviceName);
+
+    // Address info is already resolved during discovery
+    setTimeout(() => aListener.onServiceResolved(aServiceInfo));
+  }
+
+  registerService(aServiceInfo, aListener) {
+    DEBUG && debug('registerService(): ' + aServiceInfo.serviceName);
+
+    for (let name of ['port', 'serviceName', 'serviceType']) {
+      if (!TryGet(aServiceInfo, name)) {
+        aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE);
+        throw new Error('Invalid nsIDNSServiceInfo; Missing "' + name + '"');
+      }
+    }
+
+    let publishedService;
+    try {
+      publishedService = new PublishedService(aServiceInfo);
+    } catch (e) {
+      DEBUG && debug("Error constructing PublishedService: " + e + " - " + e.stack);
+      setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
+      return;
+    }
+
+    // Ensure such a service does not already exist.
+    if (this._services.get(publishedService.key)) {
+      setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
+      return;
+    }
+
+    // Make sure that the service addr is '0.0.0.0', or there is at least one
+    // socket open on the address the service is open on.
+    this._getSockets().then((sockets) => {
+      if (publishedService.address != '0.0.0.0' && !sockets.get(publishedService.address)) {
+        setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
+        return;
+      }
+
+      this._services.set(publishedService.key, publishedService);
+
+      // Service registered.. call onServiceRegistered on next tick.
+      setTimeout(() => aListener.onServiceRegistered(aServiceInfo));
+
+      // Set a timeout to start advertising the service too.
+      publishedService.advertiseTimer = setTimeout(() => {
+        this._advertiseService(publishedService.key, /* firstAdv = */ true);
+      });
+    });
+  }
+
+  unregisterService(aServiceInfo, aListener) {
+    DEBUG && debug('unregisterService(): ' + aServiceInfo.serviceName);
+
+    let serviceKey;
+    try {
+      serviceKey = ServiceKey(aServiceInfo);
+    } catch (e) {
+      setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
+      return;
+    }
+
+    let publishedService = this._services.get(serviceKey);
+    if (!publishedService) {
+      setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
+      return;
+    }
+
+    // Clear any advertise timeout for this published service.
+    publishedService.clearAdvertiseTimer();
+
+    // Delete the service from the service map.
+    if (!this._services.delete(serviceKey)) {
+      setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
+      return;
+    }
+
+    // Check the broadcast timer again to rejig when it should run next.
+    this._checkStartBroadcastTimer();
+
+    // Check to see if sockets should be closed, and if so close them.
+    this._checkCloseSockets();
+
+    aListener.onServiceUnregistered(aServiceInfo);
+  }
+
+  _advertiseService(serviceKey, firstAdv) {
+    DEBUG && debug('_advertiseService(): key=' + serviceKey);
+    let publishedService = this._services.get(serviceKey);
+    if (!publishedService) {
+      debug("_advertiseService Could not find service to advertise (key=" + serviceKey + ")");
+      return;
+    }
+
+    publishedService.advertiseTimer = undefined;
+
+    this._getSockets().then((sockets) => {
+      if (publishedService.address == "0.0.0.0") {
+        let addressList = [];
+
+        sockets.forEach((socket, address) => {
+          if (address != "127.0.0.1") {
+            addressList.push(address);
+          }
+        });
+
+        this._getBroadcastSocket().then(socket => {
+          let packet = this._makeServicePacket(publishedService, addressList);
+          let data = packet.serialize();
+          socket.send(MDNS_MULTICAST_GROUP, MDNS_PORT, data, data.length);
+        });
+      } else {
+        sockets.forEach((socket, address) => {
+          if (address == publishedService.address) {
+            let packet = this._makeServicePacket(publishedService, [address]);
+            let data = packet.serialize();
+            socket.send(MDNS_MULTICAST_GROUP, MDNS_PORT, data, data.length);
+          }
+        });
+      }
+
+      // If first advertisement, re-advertise in 1 second.
+      // Otherwise, set the lastAdvertised time.
+      if (firstAdv) {
+        publishedService.advertiseTimer = setTimeout(() => {
+          this._advertiseService(serviceKey)
+        }, 1000);
+      } else {
+        publishedService.lastAdvertised = Date.now();
+        this._checkStartBroadcastTimer();
+      }
+    });
+  }
+
+  _cancelBroadcastTimer() {
+    if (!this._broadcastTimer) {
+      return;
+    }
+    clearTimeout(this._broadcastTimer);
+    this._broadcastTimer = undefined;
+  }
+
+  _checkStartBroadcastTimer() {
+    DEBUG && debug("_checkStartBroadcastTimer()");
+    // Cancel any existing broadcasting timer.
+    this._cancelBroadcastTimer();
+
+    let now = Date.now();
+
+    // Go through services and find services to broadcast.
+    let bcastServices = [];
+    let nextBcastWait = undefined;
+    for (let [serviceKey, publishedService] of this._services) {
+      // if lastAdvertised is undefined, service hasn't finished it's initial
+      // two broadcasts.
+      if (publishedService.lastAdvertised === undefined) {
+        continue;
+      }
+
+      // Otherwise, check lastAdvertised against now.
+      let msSinceAdv = now - publishedService.lastAdvertised;
+
+      // If msSinceAdv is more than 90% of the way to the TTL, advertise now.
+      if (msSinceAdv > (DEFAULT_TTL * 1000 * 0.9)) {
+        bcastServices.push(publishedService);
+        continue;
+      }
+
+      // Otherwise, calculate the next time to advertise for this service.
+      // We set that at 95% of the time to the TTL expiry.
+      let nextAdvWait = (DEFAULT_TTL * 1000 * 0.95) - msSinceAdv;
+      if (nextBcastWait === undefined || nextBcastWait > nextAdvWait) {
+        nextBcastWait = nextAdvWait;
+      }
+    }
+
+    // Schedule an immediate advertisement of all services to be advertised now.
+    for (let svc of bcastServices) {
+        svc.advertiseTimer = setTimeout(() => this._advertiseService(svc.key));
+    }
+
+    // Schedule next broadcast check for the next bcast time.
+    if (nextBcastWait !== undefined) {
+      DEBUG && debug("_checkStartBroadcastTimer(): Scheduling next check in " + nextBcastWait + "ms");
+      this._broadcastTimer = setTimeout(() => this._checkStartBroadcastTimer(), nextBcastWait);
+    }
+  }
+
+  _query(name) {
+    let packet = new DNSPacket();
+    packet.setFlag('QR', DNS_QUERY_RESPONSE_CODES.QUERY);
+
+    // PTR Record
+    packet.addRecord('QD', new DNSRecord({
+      name: name,
+      recordType: DNS_RECORD_TYPES.PTR,
+      classCode: DNS_CLASS_CODES.IN,
+      cacheFlush: true
+    }));
+
+    let data = packet.serialize();
+
+    this._getSockets().then((sockets) => {
+      sockets.forEach((socket) => {
+        socket.send(MDNS_MULTICAST_GROUP, MDNS_PORT, data, data.length);
+      });
+    });
+
+    // Automatically announce previously-discovered
+    // services that match and haven't expired yet.
+    setTimeout(() => {
+      let { serviceType } = _parseServiceDomainName(name);
+
+      this._clearExpiredDiscoveries();
+      this._discovered.forEach((discovery, key) => {
+        let serviceInfo = discovery.serviceInfo;
+        if (serviceInfo.serviceType !== serviceType) {
+          return;
+        }
+
+        let listeners = this._listeners.get(serviceInfo.serviceType) || [];
+        listeners.forEach((listener) => {
+          listener.onServiceFound(serviceInfo);
+        });
+      });
+    });
+  }
+
+  _clearExpiredDiscoveries() {
+    this._discovered.forEach((discovery, key) => {
+      if (discovery.expireTime < Date.now()) {
+        this._discovered.delete(key);
+        return;
+      }
+    });
+  }
+
+  _handleQueryPacket(packet, message) {
+    packet.getRecords(['QD']).forEach((record) => {
+      // Don't respond if the query's class code is not IN or ANY.
+      if (record.classCode !== DNS_CLASS_CODES.IN &&
+          record.classCode !== DNS_CLASS_CODES.ANY) {
+        return;
+      }
+
+      // Don't respond if the query's record type is not PTR or ANY.
+      if (record.recordType !== DNS_RECORD_TYPES.PTR &&
+          record.recordType !== DNS_RECORD_TYPES.ANY) {
+        return;
+      }
+
+      for (let [serviceKey, publishedService] of this._services) {
+        if (publishedService.ptrMatch(record.name)) {
+          this._advertiseService(serviceKey);
+        }
+      }
+    });
+  }
+
+  _makeServicePacket(service, addresses) {
+    let packet = new DNSPacket();
+    packet.setFlag('QR', DNS_QUERY_RESPONSE_CODES.RESPONSE);
+    packet.setFlag('AA', DNS_AUTHORITATIVE_ANSWER_CODES.YES);
+
+    let host = service.host || _hostname;
+
+    // e.g.: foo-bar-service._http._tcp.local
+    let serviceDomainName = service.serviceName + '.' + service.serviceType + '.local';
+
+    // PTR Record
+    packet.addRecord('AN', new DNSResourceRecord({
+      name: service.serviceType + '.local', // e.g.: _http._tcp.local
+      recordType: DNS_RECORD_TYPES.PTR,
+      data: serviceDomainName
+    }));
+
+    // SRV Record
+    packet.addRecord('AR', new DNSResourceRecord({
+      name: serviceDomainName,
+      recordType: DNS_RECORD_TYPES.SRV,
+      classCode: DNS_CLASS_CODES.IN,
+      cacheFlush: true,
+      data: {
+        priority: 0,
+        weight: 0,
+        port: service.port,
+        target: host // e.g.: My-Android-Phone.local
+      }
+    }));
+
+    // A Records
+    for (let address of addresses) {
+        packet.addRecord('AR', new DNSResourceRecord({
+          name: host,
+          recordType: DNS_RECORD_TYPES.A,
+          data: address
+        }));
+    }
+
+    // TXT Record
+    packet.addRecord('AR', new DNSResourceRecord({
+      name: serviceDomainName,
+      recordType: DNS_RECORD_TYPES.TXT,
+      classCode: DNS_CLASS_CODES.IN,
+      cacheFlush: true,
+      data: service.serviceAttrs || {}
+    }));
+
+    return packet;
+  }
+
+  _handleResponsePacket(packet, message) {
+    let services = {};
+    let hosts = {};
+
+    let srvRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.SRV);
+    let txtRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.TXT);
+    let ptrRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.PTR);
+    let aRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.A);
+
+    srvRecords.forEach((record) => {
+      let data = record.data || {};
+
+      services[record.name] = {
+        host: data.target,
+        port: data.port,
+        ttl: record.ttl
+      };
+    });
+
+    txtRecords.forEach((record) => {
+      if (!services[record.name]) {
+        return;
+      }
+
+      services[record.name].attributes = record.data;
+    });
+
+    aRecords.forEach((record) => {
+      if (IsIpv4Address(record.data)) {
+        hosts[record.name] = record.data;
+      }
+    });
+
+    ptrRecords.forEach((record) => {
+      let name = record.data;
+      if (!services[name]) {
+        return;
+      }
+
+      let {host, port} = services[name];
+      if (!host || !port) {
+        return;
+      }
+
+      let { serviceName, serviceType, domainName } = _parseServiceDomainName(name);
+      if (!serviceName || !serviceType || !domainName) {
+        return;
+      }
+
+      let address = hosts[host];
+      if (!address) {
+        return;
+      }
+
+      let ttl = services[name].ttl || 0;
+      let serviceInfo = {
+        serviceName: serviceName,
+        serviceType: serviceType,
+        host: host,
+        address: address,
+        port: port,
+        domainName: domainName,
+        attributes: services[name].attributes || {}
+      };
+
+      this._onServiceFound(serviceInfo, ttl);
+    });
+  }
+
+  _onServiceFound(serviceInfo, ttl = 0) {
+    let expireTime = Date.now() + (ttl * 1000);
+    let key = serviceInfo.serviceName + '.' +
+              serviceInfo.serviceType + '.' +
+              serviceInfo.domainName + ' @' +
+              serviceInfo.address + ':' +
+              serviceInfo.port;
+
+    // If this service was already discovered, just update
+    // its expiration time and don't re-emit it.
+    if (this._discovered.has(key)) {
+      this._discovered.get(key).expireTime = expireTime;
+      return;
+    }
+
+    this._discovered.set(key, {
+      serviceInfo: serviceInfo,
+      expireTime: expireTime
+    });
+
+    let listeners = this._listeners.get(serviceInfo.serviceType) || [];
+    listeners.forEach((listener) => {
+      listener.onServiceFound(serviceInfo);
+    });
+
+    DEBUG && debug('_onServiceFound()' + serviceInfo.serviceName);
+  }
+
+  _getSockets() {
+    return new Promise((resolve) => {
+      if (this._sockets.size > 0) {
+        resolve(this._sockets);
+        return;
+      }
+
+      Promise.all([getAddresses(), getHostname()]).then(() => {
+        _addresses.forEach((address) => {
+          let socket = Cc['@mozilla.org/network/udp-socket;1']
+                        .createInstance(Ci.nsIUDPSocket);
+
+          socket.init(MDNS_PORT, false,
+                      Services.scriptSecurityManager.getSystemPrincipal());
+          socket.asyncListen({
+            onPacketReceived: this._onPacketReceived.bind(this),
+            onStopListening: this._onStopListening.bind(this)
+          });
+          socket.joinMulticast(MDNS_MULTICAST_GROUP, address);
+
+          this._sockets.set(address, socket);
+        });
+
+        resolve(this._sockets);
+      });
+    });
+  }
+
+  _getBroadcastSocket() {
+    return new Promise((resolve) => {
+      if (this._broadcastSocket !== undefined) {
+        resolve(this._broadcastSocket);
+        return;
+      }
+
+      let socket = Cc['@mozilla.org/network/udp-socket;1']
+                    .createInstance(Ci.nsIUDPSocket);
+
+      socket.init(MDNS_PORT, false,
+                  Services.scriptSecurityManager.getSystemPrincipal());
+      this._broadcastSocket = socket;
+      resolve(this._broadcastSocket);
+    });
+  }
+
+  _checkCloseSockets() {
+    // Nothing to do if no sockets to close.
+    if (this._sockets.size == 0)
+      return;
+
+    // Don't close sockets if discovery listeners are still present.
+    if (this._listeners.size > 0)
+      return;
+
+    // Don't close sockets if advertised services are present.
+    // Since we need to listen for service queries and respond to them.
+    if (this._services.size > 0)
+      return;
+
+    this._closeSockets();
+  }
+
+  _closeSockets() {
+    this._sockets.forEach(socket => socket.close());
+    this._sockets.clear();
+  }
+
+  _onPacketReceived(socket, message) {
+    let packet = DNSPacket.parse(message.rawData);
+
+    switch (packet.getFlag('QR')) {
+      case DNS_QUERY_RESPONSE_CODES.QUERY:
+        this._handleQueryPacket(packet, message);
+        break;
+      case DNS_QUERY_RESPONSE_CODES.RESPONSE:
+        this._handleResponsePacket(packet, message);
+        break;
+      default:
+        break;
+    }
+  }
+
+  _onStopListening(socket, status) {
+    DEBUG && debug('_onStopListening() ' + status);
+  }
+
+  _addServiceListener(serviceType, listener) {
+    let listeners = this._listeners.get(serviceType);
+    if (!listeners) {
+      listeners = [];
+      this._listeners.set(serviceType, listeners);
+    }
+
+    if (!listeners.find(l => l === listener)) {
+      listeners.push(listener);
+    }
+  }
+
+  _removeServiceListener(serviceType, listener) {
+    let listeners = this._listeners.get(serviceType);
+    if (!listeners) {
+      return;
+    }
+
+    let index = listeners.findIndex(l => l === listener);
+    if (index >= 0) {
+      listeners.splice(index, 1);
+    }
+
+    if (listeners.length === 0) {
+      this._listeners.delete(serviceType);
+    }
+  }
+}
+
+let _networkInfo = Cc['@mozilla.org/network-info-service;1']
+                    .createInstance(Ci.nsINetworkInfoService);
+
+let _addresses;
+
+/**
+ * @private
+ */
+function getAddresses() {
+  return new Promise((resolve, reject) => {
+    if (_addresses) {
+      resolve(_addresses);
+      return;
+    }
+
+    _networkInfo.listNetworkAddresses({
+      onListedNetworkAddresses(aAddressArray) {
+        _addresses = aAddressArray.filter((address) => {
+          return address.indexOf('%p2p') === -1 &&  // No WiFi Direct interfaces
+                 address.indexOf(':')    === -1 &&  // XXX: No IPv6 for now
+                 address != "127.0.0.1"             // No ipv4 loopback addresses.
+        });
+
+        DEBUG && debug('getAddresses(): ' + _addresses);
+        resolve(_addresses);
+      },
+
+      onListNetworkAddressesFailed() {
+        DEBUG && debug('getAddresses() FAILED!');
+        resolve([]);
+      }
+    });
+  });
+}
+
+let _hostname;
+
+/**
+ * @private
+ */
+function getHostname() {
+  return new Promise((resolve) => {
+    if (_hostname) {
+      resolve(_hostname);
+      return;
+    }
+
+    _networkInfo.getHostname({
+      onGotHostname(aHostname) {
+        _hostname = aHostname.replace(/\s/g, '-') + '.local';
+
+        DEBUG && debug('getHostname(): ' + _hostname);
+        resolve(_hostname);
+      },
+
+      onGetHostnameFailed() {
+        DEBUG && debug('getHostname() FAILED');
+        resolve('localhost');
+      }
+    });
+  });
+}
+
+/**
+ * Parse fully qualified domain name to service name, instance name,
+ * and domain name. See https://tools.ietf.org/html/rfc6763#section-7.
+ *
+ * Example: 'foo-bar-service._http._tcp.local' -> {
+ *   serviceName: 'foo-bar-service',
+ *   serviceType: '_http._tcp',
+ *   domainName: 'local'
+ * }
+ *
+ * @private
+ */
+function _parseServiceDomainName(serviceDomainName) {
+  let parts = serviceDomainName.split('.');
+  let index = Math.max(parts.lastIndexOf('_tcp'), parts.lastIndexOf('_udp'));
+
+  return {
+    serviceName: parts.splice(0, index - 1).join('.'),
+    serviceType: parts.splice(0, 2).join('.'),
+    domainName: parts.join('.')
+  };
+}
+
+/**
+ * @private
+ */
+function _propertyBagToObject(propBag) {
+  let result = {};
+  if (propBag.QueryInterface) {
+    propBag.QueryInterface(Ci.nsIPropertyBag2);
+    let propEnum = propBag.enumerator;
+    while (propEnum.hasMoreElements()) {
+      let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
+      result[prop.name] = prop.value.toString();
+    }
+  } else {
+    for (let name in propBag) {
+      result[name] = propBag[name].toString();
+    }
+  }
+  return result;
+}
--- a/netwerk/dns/mdns/libmdns/moz.build
+++ b/netwerk/dns/mdns/libmdns/moz.build
@@ -1,26 +1,15 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
-    EXTRA_COMPONENTS += [
-        'nsDNSServiceDiscovery.js',
-        'nsDNSServiceDiscovery.manifest',
-    ]
-
-    EXTRA_JS_MODULES += [
-        'MulticastDNSAndroid.jsm',
-        'MulticastDNSFallback.jsm',
-    ]
-
-elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa' or \
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa' or \
         (CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['ANDROID_VERSION'] >= '16'):
     UNIFIED_SOURCES += [
         'MDNSResponderOperator.cpp',
         'MDNSResponderReply.cpp',
         'nsDNSServiceDiscovery.cpp',
     ]
 
     LOCAL_INCLUDES += [
@@ -29,16 +18,37 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'co
 
     if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
         LOCAL_INCLUDES += [
             '%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
                 'external/mdnsresponder/mDNSShared',
             ]
         ]
 
+else:
+    EXTRA_COMPONENTS += [
+        'nsDNSServiceDiscovery.js',
+        'nsDNSServiceDiscovery.manifest',
+    ]
+
+    EXTRA_JS_MODULES += [
+        'fallback/DataReader.jsm',
+        'fallback/DataWriter.jsm',
+        'fallback/DNSPacket.jsm',
+        'fallback/DNSRecord.jsm',
+        'fallback/DNSResourceRecord.jsm',
+        'fallback/DNSTypes.jsm',
+        'fallback/MulticastDNS.jsm',
+    ]
+
+    if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+        EXTRA_JS_MODULES += [
+            'MulticastDNSAndroid.jsm',
+        ]
+
 UNIFIED_SOURCES += [
     'nsDNSServiceInfo.cpp',
     'nsMulticastDNSModule.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 FINAL_LIBRARY = 'xul'
 
--- a/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.js
@@ -1,22 +1,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-if (Services.prefs.getBoolPref("network.mdns.use_js_fallback")) {
-  Cu.import("resource://gre/modules/MulticastDNSFallback.jsm");
+let { PlatformInfo } = ExtensionUtils;
+
+if (PlatformInfo.os == "android" && !Services.prefs.getBoolPref("network.mdns.use_js_fallback")) {
+  Cu.import("resource://gre/modules/MulticastDNSAndroid.jsm");
 } else {
-  Cu.import("resource://gre/modules/MulticastDNSAndroid.jsm");
+  Cu.import("resource://gre/modules/MulticastDNS.jsm");
 }
 
 const DNSSERVICEDISCOVERY_CID = Components.ID("{f9346d98-f27a-4e89-b744-493843416480}");
 const DNSSERVICEDISCOVERY_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1";
 const DNSSERVICEINFO_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1";
 
 function log(aMsg) {
   dump("-*- nsDNSServiceDiscovery.js : " + aMsg + "\n");
@@ -50,16 +53,27 @@ ListenerWrapper.prototype = {
     for (let name of ['host', 'address', 'port', 'serviceName', 'serviceType']) {
       try {
         serviceInfo[name] = aServiceInfo[name];
       } catch (e) {
         // ignore exceptions
       }
     }
 
+    let attributes;
+    try {
+      attributes = _toPropertyBag2(aServiceInfo.attributes);
+    } catch (err) {
+        // Ignore unset attributes in object.
+        log("Caught unset attributes error: " + err + " - " + err.stack);
+        attributes = Cc['@mozilla.org/hash-property-bag;1']
+                        .createInstance(Ci.nsIWritablePropertyBag2);
+    }
+    serviceInfo.attributes = attributes;
+
     return serviceInfo;
   },
 
   /* transparent types */
   onDiscoveryStarted: function(aServiceType) {
     this.discoveryStarting = false;
     this.listener.onDiscoveryStarted(aServiceType);
 
@@ -166,8 +180,22 @@ nsDNSServiceDiscovery.prototype = {
 
   resolveService: function(aServiceInfo, aListener) {
     log("resolveService");
     this.mdns.resolveService(aServiceInfo, new ListenerWrapper(aListener));
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDNSServiceDiscovery]);
+
+function _toPropertyBag2(obj)
+{
+  if (obj.QueryInterface) {
+    return obj.QueryInterface(Ci.nsIPropertyBag2);
+  }
+
+  let result = Cc['@mozilla.org/hash-property-bag;1']
+                  .createInstance(Ci.nsIWritablePropertyBag2);
+  for (let name in obj) {
+    result.setPropertyAsAString(name, obj[name]);
+  }
+  return result;
+}
--- a/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp
@@ -58,17 +58,20 @@ nsDNSServiceInfo::nsDNSServiceInfo(nsIDN
       NS_WARN_IF(NS_FAILED(enumerator->GetNext(getter_AddRefs(element))));
       nsCOMPtr<nsIProperty> property = do_QueryInterface(element);
       MOZ_ASSERT(property);
 
       nsAutoString name;
       nsCOMPtr<nsIVariant> value;
       NS_WARN_IF(NS_FAILED(property->GetName(name)));
       NS_WARN_IF(NS_FAILED(property->GetValue(getter_AddRefs(value))));
-      NS_WARN_IF(NS_FAILED(newAttributes->SetPropertyAsInterface(name, value)));
+      nsAutoCString valueStr;
+      NS_WARN_IF(NS_FAILED(value->GetAsACString(valueStr)));
+
+      NS_WARN_IF(NS_FAILED(newAttributes->SetPropertyAsACString(name, valueStr)));
     }
 
     NS_WARN_IF(NS_FAILED(SetAttributes(newAttributes)));
   }
 }
 
 NS_IMETHODIMP
 nsDNSServiceInfo::GetHost(nsACString& aHost)