fune/netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm
Kris Maglione 3a5c05e76f Bug 1484496: Part 5e - Convert remaining nsISimpleEnumerator users to use JS iteration. r=mccr8
Differential Revision: https://phabricator.services.mozilla.com/D3733

--HG--
extra : rebase_source : c0fac176d7b3d840c4dbb14f8d95ccfc7f83a5a8
extra : histedit_source : a92c40117d0808a3ad68c972f622a7a42c9ae8ba
2018-08-18 18:13:14 -07:00

871 lines
25 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'];
ChromeUtils.import('resource://gre/modules/Services.jsm');
ChromeUtils.import('resource://gre/modules/Timer.jsm');
ChromeUtils.import('resource://gre/modules/XPCOMUtils.jsm');
ChromeUtils.import('resource://gre/modules/DNSPacket.jsm');
ChromeUtils.import('resource://gre/modules/DNSRecord.jsm');
ChromeUtils.import('resource://gre/modules/DNSResourceRecord.jsm');
ChromeUtils.import('resource://gre/modules/DNSTypes.jsm');
const NS_NETWORK_LINK_TOPIC = 'network:link-status-changed';
let observerService = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.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);
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.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;
}