123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876 |
- /* -*- 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 NS_NETWORK_LINK_TOPIC = 'network:link-status-changed';
- let observerService = Cc["@mozilla.org/observer-service;1"]
- .getService(Components.interfaces.nsIObserverService);
- let networkInfoService = Cc['@mozilla.org/network-info-service;1']
- .createInstance(Ci.nsINetworkInfoService);
- 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._sockets = new Map();
- this._services = new Map();
- this._discovered = new Map();
- this._querySocket = undefined;
- this._broadcastReceiverSocket = undefined;
- this._broadcastTimer = undefined;
- this._networkLinkObserver = {
- observe: (subject, topic, data) => {
- DEBUG && debug(NS_NETWORK_LINK_TOPIC + '(' + data + '); Clearing list of previously discovered services');
- this._discovered.clear();
- }
- };
- }
- _attachNetworkLinkObserver() {
- if (this._networkLinkObserverTimeout) {
- clearTimeout(this._networkLinkObserverTimeout);
- }
- if (!this._isNetworkLinkObserverAttached) {
- DEBUG && debug('Attaching observer ' + NS_NETWORK_LINK_TOPIC);
- observerService.addObserver(this._networkLinkObserver, NS_NETWORK_LINK_TOPIC, false);
- this._isNetworkLinkObserverAttached = true;
- }
- }
- _detachNetworkLinkObserver() {
- if (this._isNetworkLinkObserverAttached) {
- if (this._networkLinkObserverTimeout) {
- clearTimeout(this._networkLinkObserverTimeout);
- }
- this._networkLinkObserverTimeout = setTimeout(() => {
- DEBUG && debug('Detaching observer ' + NS_NETWORK_LINK_TOPIC);
- observerService.removeObserver(this._networkLinkObserver, NS_NETWORK_LINK_TOPIC);
- this._isNetworkLinkObserverAttached = false;
- this._networkLinkObserverTimeout = null;
- }, 5000);
- }
- }
- startDiscovery(aServiceType, aListener) {
- DEBUG && debug('startDiscovery("' + aServiceType + '")');
- let { serviceType } = _parseServiceDomainName(aServiceType);
- this._attachNetworkLinkObserver();
- 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._detachNetworkLinkObserver();
- 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);
- // Initialize the broadcast receiver socket in case it
- // hasn't already been started so we can listen for
- // multicast queries/announcements on all interfaces.
- this._getBroadcastReceiverSocket();
- 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);
- }
- _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._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;
- }
- _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) {
- 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();
- // Initialize the broadcast receiver socket in case it
- // hasn't already been started so we can listen for
- // multicast queries/announcements on all interfaces.
- this._getBroadcastReceiverSocket();
- 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;
- }
- 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) {
- DEBUG && debug("_handleQueryPacket: " + packet.toJSON());
- if (publishedService.ptrMatch(record.name)) {
- this._respondToQuery(serviceKey, message);
- }
- }
- });
- }
- _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);
- }
- /**
- * Gets a non-exclusive socket on 0.0.0.0:{random} to send
- * multicast queries on all interfaces. This socket does
- * not need to join a multicast group since it is still
- * able to *send* multicast queries, but it does not need
- * to *listen* for multicast queries/announcements since
- * the `_broadcastReceiverSocket` is already handling them.
- */
- _getQuerySocket() {
- return new Promise((resolve, reject) => {
- if (!this._querySocket) {
- this._querySocket = _openSocket('0.0.0.0', 0, {
- onPacketReceived: this._onPacketReceived.bind(this),
- onStopListening: this._onStopListening.bind(this)
- });
- }
- resolve(this._querySocket);
- });
- }
- /**
- * Gets a non-exclusive socket on 0.0.0.0:5353 to listen
- * for multicast queries/announcements on all interfaces.
- * Since this socket needs to listen for multicast queries
- * and announcements, this socket joins the multicast
- * group on *all* interfaces (0.0.0.0).
- */
- _getBroadcastReceiverSocket() {
- return new Promise((resolve, reject) => {
- if (!this._broadcastReceiverSocket) {
- this._broadcastReceiverSocket = _openSocket('0.0.0.0', MDNS_PORT, {
- onPacketReceived: this._onPacketReceived.bind(this),
- onStopListening: this._onStopListening.bind(this)
- }, /* multicastInterface = */ '0.0.0.0');
- }
- resolve(this._broadcastReceiverSocket);
- });
- }
- /**
- * Gets a non-exclusive socket for each interface on
- * {iface-ip}:5353 for sending query responses as
- * well as for listening for unicast queries. These
- * sockets do not need to join a multicast group
- * since they are still able to *send* multicast
- * query responses, but they do not need to *listen*
- * for multicast queries since the `_querySocket` is
- * already handling them.
- */
- _getSockets() {
- return new Promise((resolve) => {
- if (this._sockets.size > 0) {
- resolve(this._sockets);
- return;
- }
- Promise.all([getAddresses(), getHostname()]).then(() => {
- _addresses.forEach((address) => {
- let socket = _openSocket(address, MDNS_PORT, null);
- this._sockets.set(address, socket);
- });
- resolve(this._sockets);
- });
- });
- }
- _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 _addresses;
- /**
- * @private
- */
- function getAddresses() {
- return new Promise((resolve, reject) => {
- if (_addresses) {
- resolve(_addresses);
- return;
- }
- networkInfoService.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;
- }
- networkInfoService.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;
- }
- /**
- * @private
- */
- function _openSocket(addr, port, handler, multicastInterface) {
- let socket = Cc['@mozilla.org/network/udp-socket;1'].createInstance(Ci.nsIUDPSocket);
- socket.init2(addr, port, Services.scriptSecurityManager.getSystemPrincipal(), true);
- if (handler) {
- socket.asyncListen({
- onPacketReceived: handler.onPacketReceived,
- onStopListening: handler.onStopListening
- });
- }
- if (multicastInterface) {
- socket.joinMulticast(MDNS_MULTICAST_GROUP, multicastInterface);
- }
- return socket;
- }
|