Professional Documents
Culture Documents
Internally, it uses the dns_sd API which is available on all major platforms. However, that does not
mean it is equally well supported on all platforms. See Compatibility Notes for more information.
Tutorial
Before we begin go to the internet and get you a bonjour browser so that you can ALL the service
discovery.
Multicast DNS service discovery is a solution to announce and discover services on the local network.
Here is how to announce a HTTP server running on port 4321:
A good place to do this is the 'listening' event handler of your HTTP server. Here is how to browse
all HTTP servers on the local network:
browser.on('serviceUp', function(service) {
console.log("service up: ", service);
});
browser.on('serviceDown', function(service) {
console.log("service down: ", service);
});
As you can see the browser object is an EventEmitter. For each HTTP server a 'serviceUp' event is
emitted. Likewise, if a server disappears 'serviceDown' is sent. The service object of a 'serviceUp'
event might look like this:
{ interfaceIndex: 4
, name: 'somehost'
, networkInterface: 'en0'
, type: {name: 'http', protocol: 'tcp', subtypes: []}
, replyDomain: 'local.'
, fullname: 'somehost._http._tcp.local.'
, host: 'somehost.local.'
, port: 4321
, addresses: [ '10.1.1.50', 'fe80::21f:5bff:fecd:ce64' ]
}
In fact you might receive more than one event per service instance. That is because dns_sd reports
each available network path to a service. Also, note that you might get more (or less) addresses. This
depends on the network topology. While testing you will often run both peers on the same machine.
Now, if you have both a wired and a wireless connection to the same network you will see both
addresses on both interfaces (not including IPv6 addresses). The number of IP addresses also
depends on the platform and the resolver being used. More on this later.
The name property is not necessarily the host name. It is a user defined string specifically meant to be
displayed in the user interface. It only defaults to the host name.
Note that the examples above intentionally omit error handling. See Error Handling below on how to
deal with synchronous and asynchronous errors.
On service types
Service type identifiers are strings used to match service instances to service queries. A service type
always contains the service name and the protocol. Additionally it may contain one or more subtype
identifiers. Here are some examples:
That’s an awful lot of underscores and punctuation. To make things easier mdns has a helper class,
called ServiceType and some utility functions like mdns.tcp(...) in the example above. Here are some
ways to create a ServiceType object:
Wherever mdns calls for a serviceType argument you can pass a ServiceType object or any of the
following representations:
In fact all of these are legal constructor arguments for ServiceType. JSON (de-)serialization works too.
And finally there is makeServiceType(...) which turns any representation into a ServiceType object
unless it already is one.
Note: mdns liberally makes up service types for testing purposes and it is probably OK if you do the
same for your media art project or something. But if you ship a product you should register your
service type with the IANA.
Subtypes
TBD.
var txt_record = {
name: 'bacon'
, chunky: true
, strips: 5
};
var ad = mdns.createAdvertisement(mdns.tcp('http'), 4321, {txtRecord: txt_record});
Non-string values are automatically converted. Buffer objects as values work too.
The size of the TXT record is very limited. That is because everything has to fit into a single DNS
message (512 bytes)1 . The documentation mentions a “typical size” of 100-200 bytes, whatever that
means. There also is a hard limit of 255 bytes for each key-value pair. That’s why they also
recommend short keys (9 bytes max). The bottom line is: Keep it brief.
DNS distinguishes between keys with no value and keys with an empty value:
var record = {
empty: ''
, just_a_flag: null // or undefined
};
When browsing for services, the incoming TXT record is automatically decoded and attached to the
txtRecord property of the service object.
Now, what to put into a TXT record? Let’s start with what not to put in there. You should not put
anything in the TXT record that is required to successfully establish a connection to your service.
Things like the protocol version should be negotiated in-band whenever possible. Multicast DNS is
pretty much a local thing. If your application relies to much on mDNS it will not work in a wide area
network. So, just think twice before depending on the TXT record. That said, the TXT record may be
W ith PDFmyURL anyone can convert entire websites to PDF!
used to help with legacy or proprietary protocols. Another application is to convey additional
information to the user. Think about a printer dialog. It is very helpful to display the printers location,
information about color support &c. before the user makes a choice.
1This is not entirely accurate. It is possible to use larger TXT records. But you should read the
relevant sections of the internet draft before doing so.
var sequence = [
mdns.rst.DNSServiceResolve()
, mdns.rst.DNSServiceGetAddrInfo({families: [4] })
];
function MCHammer(options) {
options = options || {};
return function MCHammer(service, next) {
console.log('STOP!');
setTimeout(function() {
W ith PDFmyURL anyone can convert entire websites to PDF!
console.log('hammertime...');
service.hammerTime = new Date();
next();
}, options.delay || 1000);
}
}
1. The default behavior to resolve all services down to the IP is very convenient. But it is very
expensive and very time consuming too. Many applications just don’t need every port number
and IP address for every service instance. They just need one. Now it is up to the user to plug
together whatever the application requires.
2. Portability was another issue. Not all platforms support all functions required by the (old)
default behavior. The resolver sequence provides the necessary abstraction to handle this
cleanly.
3. Something with separation of concerns.
Network Interfaces
Sometimes it is necessary to restrict a browser or advertisement to a certain network interface.
Sometimes the service is only available on one interface like on a router. Or maybe you want to run
your mdns stuff on the loopback interface to keep it from interfering with a production system.
Restricting operation to the loopback interface is also very handy in unit tests.
Browser and Advertisment both support a networkInterface option. It may be set to network
interface name, an IP address or an interface index. All three variants have different properties:
These are the same names as returned by os.networkInterfaces(). They are persistent accross
reboots, as far as I know even if hot pluggable network adapters (think USB to ethernet) are involved.
They are human readable. However, please note that they are not portable across platforms. On
Linux ethernet interfaces have an eth prefix while on darwin (and probably other BSDs) en is used.
Also note that on windows the interface names are localized and, to make things worse, user
configurable.
When browsing the service object passed to the event listeners has a networkInterface property. It
W ith PDFmyURL anyone can convert entire websites to PDF!
contains the human readable name of the network interface the service was discovered on. See the
service object example above.
IP Addresses
IP addresses are portable across platforms. In an environment that uses dynamic addresses (DHCP,
mdns, zeroconf or MS autoconf) they are not necessarily persistent across reboots or disconnects.
Also, note that currently simple string comparison is used to find the address in the result returned by
os.networkInterfaces(). This works great for IPv4 addresses. However, IPv6 addresses have
multiple equivalent string representations. That means at present you have to use the exact same
string as found in the result of os.networkInterfaces(). Otherwise mdns will fail to find the
corresponding interface. This makes IPv6 addresses less portable than IPv4 ones.
Interface Indices
The underlying library dns_sd uses interface indices to identify network interfaces. It is a one-based
index as returned by the if_nametoindex(...) family of calls. It is not a valid index into the list
returned by os.networkInterfaces() because node intentionally skips interfaces that have no address
assigned or are down. Passing zero as networkInterface means “do the right thing” or, to put it simple
“listen on all interfaces”. Refer to the dns_sd API documentation under Further Reading for details.
This is the default behavior.
Newer versions of dns_sd do not use the interface index of the loopback interface to specify local
operation. They use the constant kDNSServiceInterfaceIndexLocalOnly. To write cross platform code
that works on most versions of dns_sd you should use the function mdns.loopbackInterface() like so:
As far as I can tell avahi’s dns_sd compatibility library does not support operation on the loopback
interface. Neither using the appropriate interface index nor passing the constant seems to work. If you
happen to know how to do it please get in touch.
Please note that setting networkInterface to the loopback name or passing 127.0.0.1 results in
undefined behavior. It may work on some platforms and/or versions and fail on others. Even worse, it
may just do nothing useful without reporting an error.
Error Handling
In production code error handling is probably a good idea. Synchronous errors are reported by
throwing exceptions. EventEmitters report asynchronous errors by emitting an error event.
Asynchronous functions report errors by invoking their callback with an error object as first
argument.
var ad;
function createAdvertisement() {
try {
ad = mdns.createAdvertisement(mdns.tcp('http'), 1337);
ad.on('error', handleError);
ad.start();
} catch (ex) {
handleError(ex);
}
}
function handleError(error) {
switch (error.errorCode) {
case mdns.kDNSServiceErr_Unknown:
W ith PDFmyURL anyone can convert entire websites to PDF!
console.warn(error);
setTimeout(createAdvertisement, 5000);
break;
default:
throw error;
}
}
All errors generated by the underlying dns_sd library have an errorCode property. Feel free to
extend the code above to treat other errors as non-fatal. See the API documentation under Further
Reading for a list of error codes. Errors generated by mdns itself do not have error codes. Adding a
maximum retry count is left as an exercise for the reader.
Reference
Many arguments and options in mdns are directly passed to the dns_sd API. This document only
covers the more important features. For in depth information on the API and how zeroconf service
discovery works refer to Further Reading.
mdns.Advertisement
An Advertisement publishes information about a service on the local network.
The hack0r takes a good look at the local network, someones local network and sprinkles it
with fairydust. He watches the particles being swirled up into vortices originating in the
passing network traffic. Datadevils on a parking lot next to the information freeway. Visible
entropy. The vortices are illuminated by open ports and the pale neon light of multicast
DNS service advertisements. The hack0r smiles.
Create a new service advertisement with the given serviceType and port. The callback has the
arguments (error, service) and it is run after successful registration and if an error occurs. If the
advertisement is used without a callback an handler should be attached to the 'error' event. The
W ith PDFmyURL anyone can convert entire websites to PDF!
options object contains additional arguments to DNSServiceRegister(...):
name
up to 63 bytes of Unicode to be used as the instance name. Think iTunes shared library names.
If not given the host name is used instead.
interfaceIndex
one-based index of the network interface the service should be announced on. Deprecated:
Use networkInterface instead.
networkInterface
the network interface to use. See Network Interfaces for details.
txtRecord
an object to be published as a TXT record. Refer to the TXT record section for details.
host
see documentation of DNSServiceRegister(...)
domain
see documentation of DNSServiceRegister(...)
flags
see documentation of DNSServiceRegister(...)
context
see documentation of DNSServiceRegister(...)
Event: ‘error’
function onError(exception) {}
ad.start()
ad.stop()
Services are reported for each interface they are reachable on. Partly because that is what dns_sd is
doing, partly because anything else would mean making assumptions.
Create a new browser to discover services that match the given serviceType. options may contain the
following properties:
resolverSequence
custom resolver sequence for this browser
interfaceIndex
one-based index of the network interface the services should be discovered on. Deprecated:
Use networkInterface instead.
networkInterface
the network interface to use. See Network Interfaces for details.
domain
see documentation of DNSServiceBrowse(...)
context
see documentation of DNSServiceBrowse(...)
flags
see documentation of DNSServiceBrowse(...)
Event: ‘serviceUp’
function onServiceUp(service) {}
Event: ‘serviceDown’
W ith PDFmyURL anyone can convert entire websites to PDF!
function onServiceDown(service) {}
Event: ‘serviceChanged’
function onServiceChanged(service) {}
Emitted when a matching service either appears or disappears. It is a new service if service.flags
has mdns.kDNSServiceFlagsAdd set.
Event: ‘error’
function onError(exception) {}
browser.start()
browser.stop()
mdns.Browser.defaultResolverSequence
This is the resolver sequence used by all browser objects that do not override it. It contains three
steps. On platforms that have DNSServiceGetAddrInfo(...) it has the following items:
On platforms that don’t, mdns.rst.getaddrinfo(...) is used instead. You could modify the default
sequence but you shouldn’t.
mdns.rst.DNSServiceResolve(options)
Resolve host name and port. Probably all but the empty sequence start with this task. The options
object may have the following properties:
flags
flags passed to DNSServiceResolve(...)
mdns.rst.DNSServiceGetAddrInfo(options)
mdns.rst.getaddrinfo(options)
mdns.rst.makeAddressesUnique()
mdns.rst.filterAddresses(f)
mdns.rst.logService()
mdns.ServiceType
ServiceType objects represent service type identifiers which have been discussed above. They store
the required information in a normalized way and help with formating and parsing of these strings.
new mdns.ServiceType(…)
Construct a ServiceType object. When called with one argument the argument may be
All tokens may have a leading underscore. The n-ary form treats its arguments as an array. Copy
construction works, too.
service_type.name
service_type.protocol
service_type.subtypes
Array of subtypes.
service_type.fromString(string)
service_type.toArray()
service_type.fromArray(array)
service_type.fromJSON(obj)
Functions
mdns.tcp(…)
mdns.udp(…)
mdns.makeServiceType(…)
Constructs a ServiceType from its arguments. If the first and only argument is a ServiceType it is just
W ith PDFmyURL anyone can convert entire websites to PDF!
returned.
mdns.createBrowser(serviceType, [options])
mdns.browseThemAll(options)
Creates a browser initialized with the wildcard service type. When started the browser emits events
for each service type instead of each service instance. The service objects have no name property. By
default the browser has an empty resolver sequence. You still can set one using the options object.
mdns.loopbackInterface()
Returns the platform and version dependent constant to set up a browser or advertisement for local
operation. See Network Interfaces for details.
Constants
All dns_sd constants (supported by the implementation) are exposed on the mdns object. Refer to the
dns_sd API documentation for a list.
mdns.isAvahi
Design Notes
The implementation has two layers: A low-level API and a more user friendly object based API. The
low-level API is implemented in C++ and just wraps functions, data structures and constants from
dns_sd.h. Most of the code deals with argument conversion and error handling. A smaller portion
deals with callbacks from C(++) to javascript.
The high-level API is written in javascript. It connects the low-level API to nodes non-blocking IO
infrastructure.
Compatibility Notes
TBD.
Further Reading
dns_sd API documentation
Bonjour Overview
DNS Service Discovery Programming Guide
Internet Draft: DNS-Based Service Discovery