Bug 1286636 - Use dedicated custom-port query socket to do mDNS queries. Respond to queries using per-interface sockets. r=justindarc
authorKannan Vijayan <kvijayan@mozilla.com>
Mon, 18 Jul 2016 12:41:31 -0400
changeset 305419 048a09249037efad7c9cdb8a985c760c83bd0f71
parent 305418 514f963dfefd24b04ef05cd2889c3ea4b8836bc3
child 305420 10d9d28579dc18269f663f7cad4f1d8f4b9cd141
push id79565
push userkvijayan@mozilla.com
push dateMon, 18 Jul 2016 16:41:37 +0000
treeherdermozilla-inbound@048a09249037 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjustindarc
bugs1286636
milestone50.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 1286636 - Use dedicated custom-port query socket to do mDNS queries. Respond to queries using per-interface sockets. r=justindarc
netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm
--- a/netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm
+++ b/netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm
@@ -95,16 +95,17 @@ class PublishedService {
     clearTimeout(this.advertiseTimer);
     this.advertiseTimer = undefined;
   }
 }
 
 class MulticastDNS {
   constructor() {
     this._listeners       = new Map();
+    this._querySocket     = undefined;
     this._broadcastSocket = undefined;
     this._broadcastTimer  = undefined;
     this._sockets         = new Map();
     this._services        = new Map();
     this._discovered      = new Map();
   }
 
   startDiscovery(aServiceType, aListener) {
@@ -215,64 +216,78 @@ class MulticastDNS {
     this._checkStartBroadcastTimer();
 
     // Check to see if sockets should be closed, and if so close them.
     this._checkCloseSockets();
 
     aListener.onServiceUnregistered(aServiceInfo);
   }
 
+  _respondToQuery(serviceKey, message) {
+    let address = message.fromAddr.address;
+    let port = message.fromAddr.port;
+    DEBUG && debug('_respondToQuery(): key=' + serviceKey + ', fromAddr='
+                        + address + ":" + port);
+
+    let publishedService = this._services.get(serviceKey);
+    if (!publishedService) {
+      debug("_respondToQuery Could not find service (key=" + serviceKey + ")");
+      return;
+    }
+
+    DEBUG && debug('_respondToQuery(): key=' + serviceKey + ': SENDING RESPONSE');
+    this._advertiseServiceHelper(publishedService, {address,port});
+  }
+
   _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);
-          }
-        });
-      }
-
+    this._advertiseServiceHelper(publishedService, null).then(() => {
       // 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();
       }
     });
   }
 
+  _advertiseServiceHelper(svc, target) {
+    if (!target) {
+      target = {address:MDNS_MULTICAST_GROUP, port:MDNS_PORT};
+    }
+
+    return this._getSockets().then((sockets) => {
+      sockets.forEach((socket, address) => {
+        if (svc.address == "0.0.0.0" || address == svc.address)
+        {
+          let packet = this._makeServicePacket(svc, [address]);
+          let data = packet.serialize();
+          try {
+            socket.send(target.address, target.port, data, data.length);
+          } catch (err) {
+            DEBUG && debug("Failed to send packet to "
+                            + target.address + ":" + target.port);
+          }
+        }
+      });
+    });
+  }
+
   _cancelBroadcastTimer() {
     if (!this._broadcastTimer) {
       return;
     }
     clearTimeout(this._broadcastTimer);
     this._broadcastTimer = undefined;
   }
 
@@ -318,38 +333,39 @@ class MulticastDNS {
     // 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) {
+    DEBUG && debug('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);
-      });
+    this._getQuerySocket().then(querySocket => {
+      DEBUG && debug('sending query on query socket ("' + name + '")');
+      querySocket.send(MDNS_MULTICAST_GROUP, MDNS_PORT, data, data.length);
     });
 
     // Automatically announce previously-discovered
     // services that match and haven't expired yet.
     setTimeout(() => {
+      DEBUG && debug('announcing previously discovered services ("' + name + '")');
       let { serviceType } = _parseServiceDomainName(name);
 
       this._clearExpiredDiscoveries();
       this._discovered.forEach((discovery, key) => {
         let serviceInfo = discovery.serviceInfo;
         if (serviceInfo.serviceType !== serviceType) {
           return;
         }
@@ -381,18 +397,19 @@ class MulticastDNS {
 
       // 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) {
+        DEBUG && debug("_handleQueryPacket: " + packet.toJSON());
         if (publishedService.ptrMatch(record.name)) {
-          this._advertiseService(serviceKey);
+          this._respondToQuery(serviceKey, message);
         }
       }
     });
   }
 
   _makeServicePacket(service, addresses) {
     let packet = new DNSPacket();
     packet.setFlag('QR', DNS_QUERY_RESPONSE_CODES.RESPONSE);
@@ -537,57 +554,55 @@ class MulticastDNS {
     let listeners = this._listeners.get(serviceInfo.serviceType) || [];
     listeners.forEach((listener) => {
       listener.onServiceFound(serviceInfo);
     });
 
     DEBUG && debug('_onServiceFound()' + serviceInfo.serviceName);
   }
 
+  _getQuerySocket() {
+    return new Promise((resolve, reject) => {
+      if (!this._querySocket) {
+        this._querySocket = _openSocket(0, {
+          onPacketReceived: this._onResponsePacketReceived.bind(this),
+          onStopListening: this._onResponseStopListening.bind(this)
+        }, /* joinMulticast = */ false);
+      }
+      resolve(this._querySocket);
+    });
+  }
+
   _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({
+          let socket = _openSocket(MDNS_PORT, {
             onPacketReceived: this._onPacketReceived.bind(this),
             onStopListening: this._onStopListening.bind(this)
-          });
-          socket.joinMulticast(MDNS_MULTICAST_GROUP, address);
-
+          }, /* joinMulticast = */ address);
           this._sockets.set(address, socket);
         });
 
         resolve(this._sockets);
       });
     });
   }
 
   _getBroadcastSocket() {
     return new Promise((resolve) => {
-      if (this._broadcastSocket !== undefined) {
-        resolve(this._broadcastSocket);
-        return;
+      if (!this._broadcastSocket) {
+        this._broadcastSocket = _openSocket(MDNS_PORT, null,
+                                            /* joinMulticast = */ false);
       }
-
-      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;
@@ -604,16 +619,21 @@ class MulticastDNS {
     this._closeSockets();
   }
 
   _closeSockets() {
     this._sockets.forEach(socket => socket.close());
     this._sockets.clear();
   }
 
+  _onResponsePacketReceived(socket, message) {
+    DEBUG && debug('_onResponsePacketReceived');
+    this._onPacketReceived(socket, message);
+  }
+
   _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:
@@ -623,16 +643,21 @@ class MulticastDNS {
         break;
     }
   }
 
   _onStopListening(socket, status) {
     DEBUG && debug('_onStopListening() ' + status);
   }
 
+  _onResponseStopListening(socket, status) {
+    DEBUG && debug('_onResponseStopListening() ' + status);
+  }
+
+
   _addServiceListener(serviceType, listener) {
     let listeners = this._listeners.get(serviceType);
     if (!listeners) {
       listeners = [];
       this._listeners.set(serviceType, listeners);
     }
 
     if (!listeners.find(l => l === listener)) {
@@ -757,8 +782,31 @@ function _propertyBagToObject(propBag) {
     }
   } else {
     for (let name in propBag) {
       result[name] = propBag[name].toString();
     }
   }
   return result;
 }
+
+/**
+ * @private
+ */
+function _openSocket(port, handler, joinMulticast) {
+  let socket = Cc['@mozilla.org/network/udp-socket;1']
+                    .createInstance(Ci.nsIUDPSocket);
+
+  if (arguments.length <= 2)
+    joinMulticast = true;
+
+  socket.init(port, false,
+              Services.scriptSecurityManager.getSystemPrincipal());
+  if (handler) {
+    socket.asyncListen({
+      onPacketReceived: handler.onPacketReceived,
+      onStopListening: handler.onStopListening
+    });
+  }
+  if (joinMulticast !== false)
+    socket.joinMulticast(MDNS_MULTICAST_GROUP, joinMulticast);
+  return socket;
+}