MulticastDNS.jsm 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876
  1. /* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  4. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. /* jshint esnext: true, moz: true */
  6. 'use strict';
  7. this.EXPORTED_SYMBOLS = ['MulticastDNS'];
  8. const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
  9. Cu.import('resource://gre/modules/Services.jsm');
  10. Cu.import('resource://gre/modules/Timer.jsm');
  11. Cu.import('resource://gre/modules/XPCOMUtils.jsm');
  12. Cu.import('resource://gre/modules/DNSPacket.jsm');
  13. Cu.import('resource://gre/modules/DNSRecord.jsm');
  14. Cu.import('resource://gre/modules/DNSResourceRecord.jsm');
  15. Cu.import('resource://gre/modules/DNSTypes.jsm');
  16. const NS_NETWORK_LINK_TOPIC = 'network:link-status-changed';
  17. let observerService = Cc["@mozilla.org/observer-service;1"]
  18. .getService(Components.interfaces.nsIObserverService);
  19. let networkInfoService = Cc['@mozilla.org/network-info-service;1']
  20. .createInstance(Ci.nsINetworkInfoService);
  21. const DEBUG = true;
  22. const MDNS_MULTICAST_GROUP = '224.0.0.251';
  23. const MDNS_PORT = 5353;
  24. const DEFAULT_TTL = 120;
  25. function debug(msg) {
  26. dump('MulticastDNS: ' + msg + '\n');
  27. }
  28. function ServiceKey(svc) {
  29. return "" + svc.serviceType.length + "/" + svc.serviceType + "|" +
  30. svc.serviceName.length + "/" + svc.serviceName + "|" +
  31. svc.port;
  32. }
  33. function TryGet(obj, name) {
  34. try {
  35. return obj[name];
  36. } catch (err) {
  37. return undefined;
  38. }
  39. }
  40. function IsIpv4Address(addr) {
  41. let parts = addr.split('.');
  42. if (parts.length != 4) {
  43. return false;
  44. }
  45. for (let part of parts) {
  46. let partInt = Number.parseInt(part, 10);
  47. if (partInt.toString() != part) {
  48. return false;
  49. }
  50. if (partInt < 0 || partInt >= 256) {
  51. return false;
  52. }
  53. }
  54. return true;
  55. }
  56. class PublishedService {
  57. constructor(attrs) {
  58. this.serviceType = attrs.serviceType.replace(/\.$/, '');
  59. this.serviceName = attrs.serviceName;
  60. this.domainName = TryGet(attrs, 'domainName') || "local";
  61. this.address = TryGet(attrs, 'address') || "0.0.0.0";
  62. this.port = attrs.port;
  63. this.serviceAttrs = _propertyBagToObject(TryGet(attrs, 'attributes') || {});
  64. this.host = TryGet(attrs, 'host');
  65. this.key = this.generateKey();
  66. this.lastAdvertised = undefined;
  67. this.advertiseTimer = undefined;
  68. }
  69. equals(svc) {
  70. return (this.port == svc.port) &&
  71. (this.serviceName == svc.serviceName) &&
  72. (this.serviceType == svc.serviceType);
  73. }
  74. generateKey() {
  75. return ServiceKey(this);
  76. }
  77. ptrMatch(name) {
  78. return name == (this.serviceType + "." + this.domainName);
  79. }
  80. clearAdvertiseTimer() {
  81. if (!this.advertiseTimer) {
  82. return;
  83. }
  84. clearTimeout(this.advertiseTimer);
  85. this.advertiseTimer = undefined;
  86. }
  87. }
  88. class MulticastDNS {
  89. constructor() {
  90. this._listeners = new Map();
  91. this._sockets = new Map();
  92. this._services = new Map();
  93. this._discovered = new Map();
  94. this._querySocket = undefined;
  95. this._broadcastReceiverSocket = undefined;
  96. this._broadcastTimer = undefined;
  97. this._networkLinkObserver = {
  98. observe: (subject, topic, data) => {
  99. DEBUG && debug(NS_NETWORK_LINK_TOPIC + '(' + data + '); Clearing list of previously discovered services');
  100. this._discovered.clear();
  101. }
  102. };
  103. }
  104. _attachNetworkLinkObserver() {
  105. if (this._networkLinkObserverTimeout) {
  106. clearTimeout(this._networkLinkObserverTimeout);
  107. }
  108. if (!this._isNetworkLinkObserverAttached) {
  109. DEBUG && debug('Attaching observer ' + NS_NETWORK_LINK_TOPIC);
  110. observerService.addObserver(this._networkLinkObserver, NS_NETWORK_LINK_TOPIC, false);
  111. this._isNetworkLinkObserverAttached = true;
  112. }
  113. }
  114. _detachNetworkLinkObserver() {
  115. if (this._isNetworkLinkObserverAttached) {
  116. if (this._networkLinkObserverTimeout) {
  117. clearTimeout(this._networkLinkObserverTimeout);
  118. }
  119. this._networkLinkObserverTimeout = setTimeout(() => {
  120. DEBUG && debug('Detaching observer ' + NS_NETWORK_LINK_TOPIC);
  121. observerService.removeObserver(this._networkLinkObserver, NS_NETWORK_LINK_TOPIC);
  122. this._isNetworkLinkObserverAttached = false;
  123. this._networkLinkObserverTimeout = null;
  124. }, 5000);
  125. }
  126. }
  127. startDiscovery(aServiceType, aListener) {
  128. DEBUG && debug('startDiscovery("' + aServiceType + '")');
  129. let { serviceType } = _parseServiceDomainName(aServiceType);
  130. this._attachNetworkLinkObserver();
  131. this._addServiceListener(serviceType, aListener);
  132. try {
  133. this._query(serviceType + '.local');
  134. aListener.onDiscoveryStarted(serviceType);
  135. } catch (e) {
  136. DEBUG && debug('startDiscovery("' + serviceType + '") FAILED: ' + e);
  137. this._removeServiceListener(serviceType, aListener);
  138. aListener.onStartDiscoveryFailed(serviceType, Cr.NS_ERROR_FAILURE);
  139. }
  140. }
  141. stopDiscovery(aServiceType, aListener) {
  142. DEBUG && debug('stopDiscovery("' + aServiceType + '")');
  143. let { serviceType } = _parseServiceDomainName(aServiceType);
  144. this._detachNetworkLinkObserver();
  145. this._removeServiceListener(serviceType, aListener);
  146. aListener.onDiscoveryStopped(serviceType);
  147. this._checkCloseSockets();
  148. }
  149. resolveService(aServiceInfo, aListener) {
  150. DEBUG && debug('resolveService(): ' + aServiceInfo.serviceName);
  151. // Address info is already resolved during discovery
  152. setTimeout(() => aListener.onServiceResolved(aServiceInfo));
  153. }
  154. registerService(aServiceInfo, aListener) {
  155. DEBUG && debug('registerService(): ' + aServiceInfo.serviceName);
  156. // Initialize the broadcast receiver socket in case it
  157. // hasn't already been started so we can listen for
  158. // multicast queries/announcements on all interfaces.
  159. this._getBroadcastReceiverSocket();
  160. for (let name of ['port', 'serviceName', 'serviceType']) {
  161. if (!TryGet(aServiceInfo, name)) {
  162. aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE);
  163. throw new Error('Invalid nsIDNSServiceInfo; Missing "' + name + '"');
  164. }
  165. }
  166. let publishedService;
  167. try {
  168. publishedService = new PublishedService(aServiceInfo);
  169. } catch (e) {
  170. DEBUG && debug("Error constructing PublishedService: " + e + " - " + e.stack);
  171. setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
  172. return;
  173. }
  174. // Ensure such a service does not already exist.
  175. if (this._services.get(publishedService.key)) {
  176. setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
  177. return;
  178. }
  179. // Make sure that the service addr is '0.0.0.0', or there is at least one
  180. // socket open on the address the service is open on.
  181. this._getSockets().then((sockets) => {
  182. if (publishedService.address != '0.0.0.0' && !sockets.get(publishedService.address)) {
  183. setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
  184. return;
  185. }
  186. this._services.set(publishedService.key, publishedService);
  187. // Service registered.. call onServiceRegistered on next tick.
  188. setTimeout(() => aListener.onServiceRegistered(aServiceInfo));
  189. // Set a timeout to start advertising the service too.
  190. publishedService.advertiseTimer = setTimeout(() => {
  191. this._advertiseService(publishedService.key, /* firstAdv = */ true);
  192. });
  193. });
  194. }
  195. unregisterService(aServiceInfo, aListener) {
  196. DEBUG && debug('unregisterService(): ' + aServiceInfo.serviceName);
  197. let serviceKey;
  198. try {
  199. serviceKey = ServiceKey(aServiceInfo);
  200. } catch (e) {
  201. setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
  202. return;
  203. }
  204. let publishedService = this._services.get(serviceKey);
  205. if (!publishedService) {
  206. setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
  207. return;
  208. }
  209. // Clear any advertise timeout for this published service.
  210. publishedService.clearAdvertiseTimer();
  211. // Delete the service from the service map.
  212. if (!this._services.delete(serviceKey)) {
  213. setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
  214. return;
  215. }
  216. // Check the broadcast timer again to rejig when it should run next.
  217. this._checkStartBroadcastTimer();
  218. // Check to see if sockets should be closed, and if so close them.
  219. this._checkCloseSockets();
  220. aListener.onServiceUnregistered(aServiceInfo);
  221. }
  222. _respondToQuery(serviceKey, message) {
  223. let address = message.fromAddr.address;
  224. let port = message.fromAddr.port;
  225. DEBUG && debug('_respondToQuery(): key=' + serviceKey + ', fromAddr='
  226. + address + ":" + port);
  227. let publishedService = this._services.get(serviceKey);
  228. if (!publishedService) {
  229. debug("_respondToQuery Could not find service (key=" + serviceKey + ")");
  230. return;
  231. }
  232. DEBUG && debug('_respondToQuery(): key=' + serviceKey + ': SENDING RESPONSE');
  233. this._advertiseServiceHelper(publishedService, {address,port});
  234. }
  235. _advertiseService(serviceKey, firstAdv) {
  236. DEBUG && debug('_advertiseService(): key=' + serviceKey);
  237. let publishedService = this._services.get(serviceKey);
  238. if (!publishedService) {
  239. debug("_advertiseService Could not find service to advertise (key=" + serviceKey + ")");
  240. return;
  241. }
  242. publishedService.advertiseTimer = undefined;
  243. this._advertiseServiceHelper(publishedService, null).then(() => {
  244. // If first advertisement, re-advertise in 1 second.
  245. // Otherwise, set the lastAdvertised time.
  246. if (firstAdv) {
  247. publishedService.advertiseTimer = setTimeout(() => {
  248. this._advertiseService(serviceKey)
  249. }, 1000);
  250. } else {
  251. publishedService.lastAdvertised = Date.now();
  252. this._checkStartBroadcastTimer();
  253. }
  254. });
  255. }
  256. _advertiseServiceHelper(svc, target) {
  257. if (!target) {
  258. target = {address:MDNS_MULTICAST_GROUP, port:MDNS_PORT};
  259. }
  260. return this._getSockets().then((sockets) => {
  261. sockets.forEach((socket, address) => {
  262. if (svc.address == "0.0.0.0" || address == svc.address)
  263. {
  264. let packet = this._makeServicePacket(svc, [address]);
  265. let data = packet.serialize();
  266. try {
  267. socket.send(target.address, target.port, data, data.length);
  268. } catch (err) {
  269. DEBUG && debug("Failed to send packet to "
  270. + target.address + ":" + target.port);
  271. }
  272. }
  273. });
  274. });
  275. }
  276. _cancelBroadcastTimer() {
  277. if (!this._broadcastTimer) {
  278. return;
  279. }
  280. clearTimeout(this._broadcastTimer);
  281. this._broadcastTimer = undefined;
  282. }
  283. _checkStartBroadcastTimer() {
  284. DEBUG && debug("_checkStartBroadcastTimer()");
  285. // Cancel any existing broadcasting timer.
  286. this._cancelBroadcastTimer();
  287. let now = Date.now();
  288. // Go through services and find services to broadcast.
  289. let bcastServices = [];
  290. let nextBcastWait = undefined;
  291. for (let [serviceKey, publishedService] of this._services) {
  292. // if lastAdvertised is undefined, service hasn't finished it's initial
  293. // two broadcasts.
  294. if (publishedService.lastAdvertised === undefined) {
  295. continue;
  296. }
  297. // Otherwise, check lastAdvertised against now.
  298. let msSinceAdv = now - publishedService.lastAdvertised;
  299. // If msSinceAdv is more than 90% of the way to the TTL, advertise now.
  300. if (msSinceAdv > (DEFAULT_TTL * 1000 * 0.9)) {
  301. bcastServices.push(publishedService);
  302. continue;
  303. }
  304. // Otherwise, calculate the next time to advertise for this service.
  305. // We set that at 95% of the time to the TTL expiry.
  306. let nextAdvWait = (DEFAULT_TTL * 1000 * 0.95) - msSinceAdv;
  307. if (nextBcastWait === undefined || nextBcastWait > nextAdvWait) {
  308. nextBcastWait = nextAdvWait;
  309. }
  310. }
  311. // Schedule an immediate advertisement of all services to be advertised now.
  312. for (let svc of bcastServices) {
  313. svc.advertiseTimer = setTimeout(() => this._advertiseService(svc.key));
  314. }
  315. // Schedule next broadcast check for the next bcast time.
  316. if (nextBcastWait !== undefined) {
  317. DEBUG && debug("_checkStartBroadcastTimer(): Scheduling next check in " + nextBcastWait + "ms");
  318. this._broadcastTimer = setTimeout(() => this._checkStartBroadcastTimer(), nextBcastWait);
  319. }
  320. }
  321. _query(name) {
  322. DEBUG && debug('query("' + name + '")');
  323. let packet = new DNSPacket();
  324. packet.setFlag('QR', DNS_QUERY_RESPONSE_CODES.QUERY);
  325. // PTR Record
  326. packet.addRecord('QD', new DNSRecord({
  327. name: name,
  328. recordType: DNS_RECORD_TYPES.PTR,
  329. classCode: DNS_CLASS_CODES.IN,
  330. cacheFlush: true
  331. }));
  332. let data = packet.serialize();
  333. // Initialize the broadcast receiver socket in case it
  334. // hasn't already been started so we can listen for
  335. // multicast queries/announcements on all interfaces.
  336. this._getBroadcastReceiverSocket();
  337. this._getQuerySocket().then((querySocket) => {
  338. DEBUG && debug('sending query on query socket ("' + name + '")');
  339. querySocket.send(MDNS_MULTICAST_GROUP, MDNS_PORT, data, data.length);
  340. });
  341. // Automatically announce previously-discovered
  342. // services that match and haven't expired yet.
  343. setTimeout(() => {
  344. DEBUG && debug('announcing previously discovered services ("' + name + '")');
  345. let { serviceType } = _parseServiceDomainName(name);
  346. this._clearExpiredDiscoveries();
  347. this._discovered.forEach((discovery, key) => {
  348. let serviceInfo = discovery.serviceInfo;
  349. if (serviceInfo.serviceType !== serviceType) {
  350. return;
  351. }
  352. let listeners = this._listeners.get(serviceInfo.serviceType) || [];
  353. listeners.forEach((listener) => {
  354. listener.onServiceFound(serviceInfo);
  355. });
  356. });
  357. });
  358. }
  359. _clearExpiredDiscoveries() {
  360. this._discovered.forEach((discovery, key) => {
  361. if (discovery.expireTime < Date.now()) {
  362. this._discovered.delete(key);
  363. return;
  364. }
  365. });
  366. }
  367. _handleQueryPacket(packet, message) {
  368. packet.getRecords(['QD']).forEach((record) => {
  369. // Don't respond if the query's class code is not IN or ANY.
  370. if (record.classCode !== DNS_CLASS_CODES.IN &&
  371. record.classCode !== DNS_CLASS_CODES.ANY) {
  372. return;
  373. }
  374. // Don't respond if the query's record type is not PTR or ANY.
  375. if (record.recordType !== DNS_RECORD_TYPES.PTR &&
  376. record.recordType !== DNS_RECORD_TYPES.ANY) {
  377. return;
  378. }
  379. for (let [serviceKey, publishedService] of this._services) {
  380. DEBUG && debug("_handleQueryPacket: " + packet.toJSON());
  381. if (publishedService.ptrMatch(record.name)) {
  382. this._respondToQuery(serviceKey, message);
  383. }
  384. }
  385. });
  386. }
  387. _makeServicePacket(service, addresses) {
  388. let packet = new DNSPacket();
  389. packet.setFlag('QR', DNS_QUERY_RESPONSE_CODES.RESPONSE);
  390. packet.setFlag('AA', DNS_AUTHORITATIVE_ANSWER_CODES.YES);
  391. let host = service.host || _hostname;
  392. // e.g.: foo-bar-service._http._tcp.local
  393. let serviceDomainName = service.serviceName + '.' + service.serviceType + '.local';
  394. // PTR Record
  395. packet.addRecord('AN', new DNSResourceRecord({
  396. name: service.serviceType + '.local', // e.g.: _http._tcp.local
  397. recordType: DNS_RECORD_TYPES.PTR,
  398. data: serviceDomainName
  399. }));
  400. // SRV Record
  401. packet.addRecord('AR', new DNSResourceRecord({
  402. name: serviceDomainName,
  403. recordType: DNS_RECORD_TYPES.SRV,
  404. classCode: DNS_CLASS_CODES.IN,
  405. cacheFlush: true,
  406. data: {
  407. priority: 0,
  408. weight: 0,
  409. port: service.port,
  410. target: host // e.g.: My-Android-Phone.local
  411. }
  412. }));
  413. // A Records
  414. for (let address of addresses) {
  415. packet.addRecord('AR', new DNSResourceRecord({
  416. name: host,
  417. recordType: DNS_RECORD_TYPES.A,
  418. data: address
  419. }));
  420. }
  421. // TXT Record
  422. packet.addRecord('AR', new DNSResourceRecord({
  423. name: serviceDomainName,
  424. recordType: DNS_RECORD_TYPES.TXT,
  425. classCode: DNS_CLASS_CODES.IN,
  426. cacheFlush: true,
  427. data: service.serviceAttrs || {}
  428. }));
  429. return packet;
  430. }
  431. _handleResponsePacket(packet, message) {
  432. let services = {};
  433. let hosts = {};
  434. let srvRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.SRV);
  435. let txtRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.TXT);
  436. let ptrRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.PTR);
  437. let aRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.A);
  438. srvRecords.forEach((record) => {
  439. let data = record.data || {};
  440. services[record.name] = {
  441. host: data.target,
  442. port: data.port,
  443. ttl: record.ttl
  444. };
  445. });
  446. txtRecords.forEach((record) => {
  447. if (!services[record.name]) {
  448. return;
  449. }
  450. services[record.name].attributes = record.data;
  451. });
  452. aRecords.forEach((record) => {
  453. if (IsIpv4Address(record.data)) {
  454. hosts[record.name] = record.data;
  455. }
  456. });
  457. ptrRecords.forEach((record) => {
  458. let name = record.data;
  459. if (!services[name]) {
  460. return;
  461. }
  462. let {host, port} = services[name];
  463. if (!host || !port) {
  464. return;
  465. }
  466. let { serviceName, serviceType, domainName } = _parseServiceDomainName(name);
  467. if (!serviceName || !serviceType || !domainName) {
  468. return;
  469. }
  470. let address = hosts[host];
  471. if (!address) {
  472. return;
  473. }
  474. let ttl = services[name].ttl || 0;
  475. let serviceInfo = {
  476. serviceName: serviceName,
  477. serviceType: serviceType,
  478. host: host,
  479. address: address,
  480. port: port,
  481. domainName: domainName,
  482. attributes: services[name].attributes || {}
  483. };
  484. this._onServiceFound(serviceInfo, ttl);
  485. });
  486. }
  487. _onServiceFound(serviceInfo, ttl = 0) {
  488. let expireTime = Date.now() + (ttl * 1000);
  489. let key = serviceInfo.serviceName + '.' +
  490. serviceInfo.serviceType + '.' +
  491. serviceInfo.domainName + ' @' +
  492. serviceInfo.address + ':' +
  493. serviceInfo.port;
  494. // If this service was already discovered, just update
  495. // its expiration time and don't re-emit it.
  496. if (this._discovered.has(key)) {
  497. this._discovered.get(key).expireTime = expireTime;
  498. return;
  499. }
  500. this._discovered.set(key, {
  501. serviceInfo: serviceInfo,
  502. expireTime: expireTime
  503. });
  504. let listeners = this._listeners.get(serviceInfo.serviceType) || [];
  505. listeners.forEach((listener) => {
  506. listener.onServiceFound(serviceInfo);
  507. });
  508. DEBUG && debug('_onServiceFound()' + serviceInfo.serviceName);
  509. }
  510. /**
  511. * Gets a non-exclusive socket on 0.0.0.0:{random} to send
  512. * multicast queries on all interfaces. This socket does
  513. * not need to join a multicast group since it is still
  514. * able to *send* multicast queries, but it does not need
  515. * to *listen* for multicast queries/announcements since
  516. * the `_broadcastReceiverSocket` is already handling them.
  517. */
  518. _getQuerySocket() {
  519. return new Promise((resolve, reject) => {
  520. if (!this._querySocket) {
  521. this._querySocket = _openSocket('0.0.0.0', 0, {
  522. onPacketReceived: this._onPacketReceived.bind(this),
  523. onStopListening: this._onStopListening.bind(this)
  524. });
  525. }
  526. resolve(this._querySocket);
  527. });
  528. }
  529. /**
  530. * Gets a non-exclusive socket on 0.0.0.0:5353 to listen
  531. * for multicast queries/announcements on all interfaces.
  532. * Since this socket needs to listen for multicast queries
  533. * and announcements, this socket joins the multicast
  534. * group on *all* interfaces (0.0.0.0).
  535. */
  536. _getBroadcastReceiverSocket() {
  537. return new Promise((resolve, reject) => {
  538. if (!this._broadcastReceiverSocket) {
  539. this._broadcastReceiverSocket = _openSocket('0.0.0.0', MDNS_PORT, {
  540. onPacketReceived: this._onPacketReceived.bind(this),
  541. onStopListening: this._onStopListening.bind(this)
  542. }, /* multicastInterface = */ '0.0.0.0');
  543. }
  544. resolve(this._broadcastReceiverSocket);
  545. });
  546. }
  547. /**
  548. * Gets a non-exclusive socket for each interface on
  549. * {iface-ip}:5353 for sending query responses as
  550. * well as for listening for unicast queries. These
  551. * sockets do not need to join a multicast group
  552. * since they are still able to *send* multicast
  553. * query responses, but they do not need to *listen*
  554. * for multicast queries since the `_querySocket` is
  555. * already handling them.
  556. */
  557. _getSockets() {
  558. return new Promise((resolve) => {
  559. if (this._sockets.size > 0) {
  560. resolve(this._sockets);
  561. return;
  562. }
  563. Promise.all([getAddresses(), getHostname()]).then(() => {
  564. _addresses.forEach((address) => {
  565. let socket = _openSocket(address, MDNS_PORT, null);
  566. this._sockets.set(address, socket);
  567. });
  568. resolve(this._sockets);
  569. });
  570. });
  571. }
  572. _checkCloseSockets() {
  573. // Nothing to do if no sockets to close.
  574. if (this._sockets.size == 0)
  575. return;
  576. // Don't close sockets if discovery listeners are still present.
  577. if (this._listeners.size > 0)
  578. return;
  579. // Don't close sockets if advertised services are present.
  580. // Since we need to listen for service queries and respond to them.
  581. if (this._services.size > 0)
  582. return;
  583. this._closeSockets();
  584. }
  585. _closeSockets() {
  586. this._sockets.forEach(socket => socket.close());
  587. this._sockets.clear();
  588. }
  589. _onPacketReceived(socket, message) {
  590. let packet = DNSPacket.parse(message.rawData);
  591. switch (packet.getFlag('QR')) {
  592. case DNS_QUERY_RESPONSE_CODES.QUERY:
  593. this._handleQueryPacket(packet, message);
  594. break;
  595. case DNS_QUERY_RESPONSE_CODES.RESPONSE:
  596. this._handleResponsePacket(packet, message);
  597. break;
  598. default:
  599. break;
  600. }
  601. }
  602. _onStopListening(socket, status) {
  603. DEBUG && debug('_onStopListening() ' + status);
  604. }
  605. _addServiceListener(serviceType, listener) {
  606. let listeners = this._listeners.get(serviceType);
  607. if (!listeners) {
  608. listeners = [];
  609. this._listeners.set(serviceType, listeners);
  610. }
  611. if (!listeners.find(l => l === listener)) {
  612. listeners.push(listener);
  613. }
  614. }
  615. _removeServiceListener(serviceType, listener) {
  616. let listeners = this._listeners.get(serviceType);
  617. if (!listeners) {
  618. return;
  619. }
  620. let index = listeners.findIndex(l => l === listener);
  621. if (index >= 0) {
  622. listeners.splice(index, 1);
  623. }
  624. if (listeners.length === 0) {
  625. this._listeners.delete(serviceType);
  626. }
  627. }
  628. }
  629. let _addresses;
  630. /**
  631. * @private
  632. */
  633. function getAddresses() {
  634. return new Promise((resolve, reject) => {
  635. if (_addresses) {
  636. resolve(_addresses);
  637. return;
  638. }
  639. networkInfoService.listNetworkAddresses({
  640. onListedNetworkAddresses(aAddressArray) {
  641. _addresses = aAddressArray.filter((address) => {
  642. return address.indexOf('%p2p') === -1 && // No WiFi Direct interfaces
  643. address.indexOf(':') === -1 && // XXX: No IPv6 for now
  644. address != "127.0.0.1" // No ipv4 loopback addresses.
  645. });
  646. DEBUG && debug('getAddresses(): ' + _addresses);
  647. resolve(_addresses);
  648. },
  649. onListNetworkAddressesFailed() {
  650. DEBUG && debug('getAddresses() FAILED!');
  651. resolve([]);
  652. }
  653. });
  654. });
  655. }
  656. let _hostname;
  657. /**
  658. * @private
  659. */
  660. function getHostname() {
  661. return new Promise((resolve) => {
  662. if (_hostname) {
  663. resolve(_hostname);
  664. return;
  665. }
  666. networkInfoService.getHostname({
  667. onGotHostname(aHostname) {
  668. _hostname = aHostname.replace(/\s/g, '-') + '.local';
  669. DEBUG && debug('getHostname(): ' + _hostname);
  670. resolve(_hostname);
  671. },
  672. onGetHostnameFailed() {
  673. DEBUG && debug('getHostname() FAILED');
  674. resolve('localhost');
  675. }
  676. });
  677. });
  678. }
  679. /**
  680. * Parse fully qualified domain name to service name, instance name,
  681. * and domain name. See https://tools.ietf.org/html/rfc6763#section-7.
  682. *
  683. * Example: 'foo-bar-service._http._tcp.local' -> {
  684. * serviceName: 'foo-bar-service',
  685. * serviceType: '_http._tcp',
  686. * domainName: 'local'
  687. * }
  688. *
  689. * @private
  690. */
  691. function _parseServiceDomainName(serviceDomainName) {
  692. let parts = serviceDomainName.split('.');
  693. let index = Math.max(parts.lastIndexOf('_tcp'), parts.lastIndexOf('_udp'));
  694. return {
  695. serviceName: parts.splice(0, index - 1).join('.'),
  696. serviceType: parts.splice(0, 2).join('.'),
  697. domainName: parts.join('.')
  698. };
  699. }
  700. /**
  701. * @private
  702. */
  703. function _propertyBagToObject(propBag) {
  704. let result = {};
  705. if (propBag.QueryInterface) {
  706. propBag.QueryInterface(Ci.nsIPropertyBag2);
  707. let propEnum = propBag.enumerator;
  708. while (propEnum.hasMoreElements()) {
  709. let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
  710. result[prop.name] = prop.value.toString();
  711. }
  712. } else {
  713. for (let name in propBag) {
  714. result[name] = propBag[name].toString();
  715. }
  716. }
  717. return result;
  718. }
  719. /**
  720. * @private
  721. */
  722. function _openSocket(addr, port, handler, multicastInterface) {
  723. let socket = Cc['@mozilla.org/network/udp-socket;1'].createInstance(Ci.nsIUDPSocket);
  724. socket.init2(addr, port, Services.scriptSecurityManager.getSystemPrincipal(), true);
  725. if (handler) {
  726. socket.asyncListen({
  727. onPacketReceived: handler.onPacketReceived,
  728. onStopListening: handler.onStopListening
  729. });
  730. }
  731. if (multicastInterface) {
  732. socket.joinMulticast(MDNS_MULTICAST_GROUP, multicastInterface);
  733. }
  734. return socket;
  735. }