fune/netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm
Victor Porof 2c53a5dcd9 Bug 1561435 - Format netwerk/, a=automatic-formatting
# ignore-this-changeset

Differential Revision: https://phabricator.services.mozilla.com/D35919

--HG--
extra : source : afa5bd771feba466ba7670c58f3d93233a14e202
2019-07-05 10:55:23 +02:00

986 lines
26 KiB
JavaScript

/* -*- 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";
var EXPORTED_SYMBOLS = ["MulticastDNS"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { clearTimeout, setTimeout } = ChromeUtils.import(
"resource://gre/modules/Timer.jsm"
);
const { DNSPacket } = ChromeUtils.import(
"resource://gre/modules/DNSPacket.jsm"
);
const { DNSRecord } = ChromeUtils.import(
"resource://gre/modules/DNSRecord.jsm"
);
const { DNSResourceRecord } = ChromeUtils.import(
"resource://gre/modules/DNSResourceRecord.jsm"
);
const {
DNS_AUTHORITATIVE_ANSWER_CODES,
DNS_CLASS_CODES,
DNS_QUERY_RESPONSE_CODES,
DNS_RECORD_TYPES,
} = ChromeUtils.import("resource://gre/modules/DNSTypes.jsm");
const NS_NETWORK_LINK_TOPIC = "network:link-status-changed";
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);
Services.obs.addObserver(
this._networkLinkObserver,
NS_NETWORK_LINK_TOPIC
);
this._isNetworkLinkObserverAttached = true;
}
}
_detachNetworkLinkObserver() {
if (this._isNetworkLinkObserverAttached) {
if (this._networkLinkObserverTimeout) {
clearTimeout(this._networkLinkObserverTimeout);
}
this._networkLinkObserverTimeout = setTimeout(() => {
DEBUG && debug("Detaching observer " + NS_NETWORK_LINK_TOPIC);
Services.obs.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);
} 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 [, 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,
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);
});
// 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);
}
});
}
_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,
serviceType,
host,
address,
port,
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,
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.includes("%p2p") && // No WiFi Direct interfaces
!address.includes(":") && // 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);
for (let prop of propBag.enumerator) {
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;
}