Bonjour Android, it’s Zeroconf
Lately, I have been doing a lot of studying on the Zero Configuration Protocol and its implementations on Android: more than one way to do the same thing, with ups and downs. But first things first.
What is Zeroconf?
The concept itself is pretty straight forward:
Zero-configuration networking is a set of technologies that automatically creates a usable computer network based on the Internet Protocol Suite (TCP/IP) when computers or network peripherals are interconnected.
It does not require manual operator intervention or special configuration servers.
In simple words, this protocol’s focus is to allow the use of services available on a given network automagically without any programmer or technician setting the parameters for us.
Defined the protocol, we can see what kind of devices are actually making use of Zeroconf: a huge number of Apple’s services are based on this technology, spacing from Airport to AirDrop and AirPlay and also Google’s Chromecast is using the same set of APIs. Furthermore, NVIDIA streaming support and TeamViewer are powered from Zero Configuration Network.
Apple calls it’s implementation Bonjour, once known as Rendezvous. It looks like guys at Cupertino have a thing for french words!
What about UPnP, DLNA and all of these?
First thing, we could actually make use of both families of technologies, it’s not an either/or situation as we cannot really compare Zeroconf and UPnP: the first is a standard that provides stable IP networking in absence of configuration, while the latter is a set of protocols created by an organisation involved in producing the specifications for each and every new device. We can rely on the stability of Zeroconf and, at the same time, have a sticker saying our brand new product is compatible with UPnP.
Furthermore, adopting UPnP does not mean that the product will support everything that comes out of the aforementioned open-ended collection of device-specific protocols: they might either adopt a subset of rules and protocols given by the UPnP forum or just stick with a marketing request and only be compatible with themselves.
As a side note, we have to remember that, while Zeroconf is an IETF draft standard, UPnP (and the other similar technologies such as DLNA, Viiv and DHWG) is a set of networking protocols controlled by an organisation, which creates different set of rules and protocols for every new product that is invented, so it’s highly possible that you will end up using a UPnP device that is not recognised by your system (remember when we all tried to set up the first network printers with Windows?).
How does all of this work?
From the outside, Zeroconf works as simply as the image on the side can describe: a server advertises itself on the network, specifying on which IP and port it’s hosting the offered service, using a multicast technique to warn all the clients listening for this advertisement.
The aforementioned advertisement is the exact opposite of what we usually do when connecting to a service: we need its address in order to communicate with it, while in this scenario, it’s the server itself providing us with its IP.
The 4 stages
Zeroconf standard foresees four different phases to be implemented in order for each device to be perfectly compatible with all the others supporting the same protocol: the first one is the IP interface configuration, in which we need to set up a netmask and allocate a unique IP address in order to connect to the network. A very interesting point is that Zeroconf comes with defaults: that means that at first the interface will be configured using the Zeroconf standard subnet, then the DHCP will be queried and the settings of the device will be updated according to the new sets of instructions inherited.
This leads us to the conclusion that the zero-configuration network can work on its own, without the need of a DHCP server that, if present, will help in building the network.
The second layer is the translation between host names and IP address, more or less like what happens when we query a DNS for the IP address of the website we are visiting. This mapping is quite complete, as it brings both retry-on-error mechanism and conflict detection rules: if we are not able to resolve a service on the first try, the device will attempt a new resolution on the next iteration, while if two devices chose the same name, the last one registering on the network will have to change its name, so that it is unique.
Mac users may have seen traces of this when they connect to a network and the operating system shows a dialog informing them that the computer name has been changed as another one with the same name is already present in the current network.
The third step is related to the allocation of a multicast IP address, so that a scope (local, site-local or link-local) is defined and the service can start advertising itself to a group of clients without the risk of conflicts.
The fourth and last step is the service discovery, that states how we use the previous layers: it should be possible to discover services by identifier or type and the process of discovery should be fast enough to be completed in 10s of seconds, the same time that should take to find new services popping up in our network.
Moreover, Zeroconf is defined with a series of constraints, so that it must minimise its impact on existing networks and applications, be at least as secure as the other IETF protocols used on the network, it shall not destabilise the network in any way or under any condition and it shall be conscious when managing resources.
How can I define my own service?
Each service based on Zeroconf should be available on either TCP or UDP and provide a unique identifier. Both of this data has to be preceded by an underscore (_) and followed by a point (.), so that each schema is represented by this string:
For instance, _http._tcp. or _mydroid._udp. would be perfectly fine names. For further information, you could find a non-exhaustive list of commonly used schemas at this page.
Those, though, are not the only data we need: we have to set a name, that will identify our device (such as “MyPhone”), and a port, on which the service we are hosting is running. We could also set some custom attributes that the client will query, so that we can provide further information.
Now, what happens on Android?
On Android, the situation is actually pretty good, from a developer point of view: the Network Service Discovery API has been introduced back with the first release of Jelly Bean 4.1 but the implementation was not completed until Android Lollipop 5.1, when the custom attributes were actually added to the APIs.
The Android SDK provides us with a very nice set of functionalities, based on two steps: namely service discovery and service resolution. Although the flow is asynchronous and the result is automatically returned to the same thread that started the discovery through a Handler, the Android native implementation suffers of a overwhelming verbosity and a tremendous bottleneck: only one service can be resolved at a time, so that we actually need to implement our own logic in order to avoid the app crashing because the NsdManager is already busy. Apart from that, implementing the service with standard APIs is pretty easy, but it’s not our only choice: as we are bound to Java, we could take advantage of a library that is pretty famous and will work on all the droids out there: the JmDNS project. This library, that can be imported either by downloading a JAR into our project or by using Maven repository, is pretty easy to implement (and comes with the custom attributes support), but has two huge disadvantages: it has been coded to run on the thread that starts it (so we must take care of the multithreading here… and I can see a lot of AsyncTasks leaking memory) and it has a huge startup time, a thing that shall be kept in mind as when making fast and responsive applications.
The third approach is one coming directly from the Cupertino labs: Apple created a library that we can compile with the help of the Android NDK (and that’s the first pain point) and it’s incredibly fast. Despite the speed, this library is very hard to find already compiled as, at the time of writing, few projects come with it. And we still have to take into account the more complex logic that it’s needed in our Java code in order to make use of the JNI bridge in our apps.
Android APIs ship a custom implementation of mDNS (which stands for multicast DNS) ported from Apple’s
These three libraries we just saw are awesome and most of the projects out there encapsulate one or more of them: either functional or object-oriented, those APIs are the ones behind the scenes, and it’s up to you to decide which of them suits you, but below you can find a small recap of the most used ones and what library they ship:
- better-zeroconf (JmDNS)
- RxDNSSD (Apple mDNS)
- RxBonjour (Apple mDNS)
- ZeRxConf (JmDNS + Android native API)
- android-mdns (Apple mDNS)