You are on page 1of 19

Home User Guide GitHub

mdns User Guide


Introduction
Tutorial
On Service Types
TXT Records
The Resolver Sequence
Network Interfaces
Error Handling
Reference
mdns.Advertisement
mdns.Browser
Resolver Sequence Tasks
mdns.ServiceType
Functions
Constants
mdns.dns_sd
Design Notes
Compatibility Notes
Further Reading

W ith PDFmyURL anyone can convert entire websites to PDF!


Introduction
mdns adds multicast DNS service discovery, also known as zeroconf or bonjour to node.js. It provides
an object based interface to announce and browse services on the local network.

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.

The API is documented in the reference section below.

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:

var mdns = require('mdns')


, ad = mdns.createAdvertisement(mdns.tcp('http'), 4321)
;
ad.start();

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:

var browser = mdns.createBrowser(mdns.tcp('http'));

browser.on('serviceUp', function(service) {
console.log("service up: ", service);
});
browser.on('serviceDown', function(service) {
console.log("service down: ", service);
});

W ith PDFmyURL anyone can convert entire websites to PDF!


browser.start();

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:

W ith PDFmyURL anyone can convert entire websites to PDF!


_http._tcp
_osc._udp
_osc._udp,_api-v1,_api-v2

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:

var r0 = mdns.tcp('http') // string form: _http._tcp


, r1 = mdns.udp('osc', 'api-v1') // string form: _osc._udp,_api-v1
, r2 = new mdns.ServiceType('http', 'tcp') // string form: _http._tcp
, r3 = mdns.makeServiceType('https', 'tcp') // string form: _https._tcp
;

Wherever mdns calls for a serviceType argument you can pass a ServiceType object or any of the
following representations:

var r0 = '_http._tcp,_api-v1' // string form


, r1 = ['http', 'tcp', 'api-v1'] // array form
, r2 = {name: 'http', protocol: 'tcp', subtypes: ['api-v1']} // object form
;

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.

W ith PDFmyURL anyone can convert entire websites to PDF!


TXT Records
Each service has an associated DNS TXT record. The application can use it to publish a small amount
of metadata. The record contains key-value pairs. The keys must be all printable ascii characters
excluding ‘=’. The value may contain any data.

The TXT record is passed to the Advertisement as an object:

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.

The Resolver Sequence


The Browser object uses a resolver sequence to collect address and port information. A resolver
sequence is basically just an array of functions. The functions are called in order and receive two
arguments: a service object to decorate and a next() function. Each function gathers information on
the service, often by invoking asynchronous operations. When done the data is stored on the service
object and the next function is invoked by calling next(). This is kind of like web server middleware as
it happens between service discovery and emitting the events. On the other hand it is just another
async function chain thing.

Resolver sequence tasks (RSTs) are created by calling factory functions:

var sequence = [
mdns.rst.DNSServiceResolve()
, mdns.rst.DNSServiceGetAddrInfo({families: [4] })
];

A browser with a custom sequence is created like this:

var browser = mdns.createBrowser(mdns.tcp('http'), {resolverSequence: sequence});

And of course you can write your own tasks:

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);
}
}

Although it seems a bit complicated this design solves a number of problems:

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:

Network Interface Names

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.

On windows interface names are only available on vista and better.

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.

On windows IP addresses are only available on vista and better.

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.

Special Case: The Loopback Interface

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:

var browser = mdns.createBrowser( mdns.tcp('http')


, { networkInterface: mdns.loopbackInterface()});

W ith PDFmyURL anyone can convert entire websites to PDF!


On current versions of Mac OS X a Browser listening on the loopback interface will still discover all
services running on the local host. To discover only services that are announced on the loopback
interface you’ll have to do some filtering in your serviceUp and serviceDown listeners. Just ignore any
event where service.interfaceIndex does not equal mdns.loopbackInterface(). This is the most
portable approach.

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.

Here is an example of an advertisement that is automatically restarted when an unknown error


occurs. This happens for example when the systems mdns daemon is currently down. All other errors,
like bad parameters, &c. are treated as fatal.

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.

new mdns.Advertisement(serviceType, port, [options], [callback])

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) {}

Emitted on asynchronous errors.

ad.start()

Start the advertisement.

ad.stop()

Stop the advertisement.

W ith PDFmyURL anyone can convert entire websites to PDF!


mdns.Browser
A mdns.Browser performs the discovery part. It emits events as services appear and disappear on the
network. For new services it also resolves host name, port and IP addresses. The resolver sequence is
fully user configurable.

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.

new mdns.Browser(serviceType, [options])

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) {}

Emitted when a new matching service is discovered.

Event: ‘serviceDown’
W ith PDFmyURL anyone can convert entire websites to PDF!
function onServiceDown(service) {}

Emitted when a matching service disappears.

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) {}

Emitted on asynchronous errors.

browser.start()

Start the browser.

browser.stop()

Stop the browser.

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:

W ith PDFmyURL anyone can convert entire websites to PDF!


var default_sequence = [
mdns.rst.DNSServiceResolve()
, mdns.rst.DNSServiceGetAddrInfo()
, mdns.rst.makeAddressesUnique()
];

On platforms that don’t, mdns.rst.getaddrinfo(...) is used instead. You could modify the default
sequence but you shouldn’t.

Resolver Sequence Tasks

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)

Resolve IP addresses using DNSServiceGetAddrInfo(...)

mdns.rst.getaddrinfo(options)

Resolve IP addresses using nodes cares.getaddrinfo(...)… but it’s a mess.

mdns.rst.makeAddressesUnique()

Filters the addresses to be unique.

mdns.rst.filterAddresses(f)

W ith PDFmyURL anyone can convert entire websites to PDF!


Filters the addresses by invoking f() on each address. If f() returns false the address is dropped.

mdns.rst.logService()

Print the service object.

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

a service type identifier (string)


an array, the first element being the type, the second the protocol. Additional items are
subtypes.
an object with properties name, protocol and optional subtypes

All tokens may have a leading underscore. The n-ary form treats its arguments as an array. Copy
construction works, too.

service_type.name

The primary service type.

service_type.protocol

The protocol used by the service. Must be ‘tcp’ or ‘udp’.

service_type.subtypes

Array of subtypes.

W ith PDFmyURL anyone can convert entire websites to PDF!


service_type.toString()

Convert the object to a service type identifier.

service_type.fromString(string)

Parse a service type identifier and store the values.

service_type.toArray()

Returns the service type in array form.

service_type.fromArray(array)

Set values from an array.

service_type.fromJSON(obj)

Set values from object, including other ServiceType objects.

Functions

mdns.tcp(…)

Expressive way to create a ServiceType with protocol tcp.

mdns.udp(…)

Expressive way to create a ServiceType with protocol 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])

This factory function constructs a Browser.

mdns.createAdvertisement(serviceType, port, [options], [callback])

This factory function constructs an Advertisement.

mdns.resolve(service, [sequence], callback)

Fill in a service object by running a resolver sequence. If no sequence is given the


Browser.defaultResolverSequence is used. The callback has the signature (error, service).

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

A boolean that is true when running on avahi. It’s a kludge though.


W ith PDFmyURL anyone can convert entire websites to PDF!
mdns.dns_sd
mdns.dns_sd contains the native functions and data structures. The functions are bound to javascript
using the exact C names and arguments. This breaks with the usual node convention of lower-case
function names.

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

W ith PDFmyURL anyone can convert entire websites to PDF!


W ith PDFmyURL anyone can convert entire websites to PDF!

You might also like