mDNSResponder is Apple's open source DNS, mDNS, and DNS-SD project which combined with link-local addressing forms their Bonjour zero-configuration networking stack. Apple distributes Bonjour on their own platforms as well as Windows via Bonjour Print Services or as a bundle with iTunes. mDNSResponder also underlies Android's Network Service Discovery API and is packaged on some open-source UNIX operating systems.
In embedded applications mDNSResponder may be integrated as a single monolith but it is more typically deployed as a daemon which applications communicate with via a small client shim. The protocol used between the two sides is documented only in the source code, and this is not an attempt at changing that. Instead it's an introduction to the protocol and relevant source code aimed at someone looking to reimplement the protocol for themselves.
First let's get the code. Apple publish mDNSResponder as a tarball but there's two other distributions worth knowing about. The first is by IETF participants (including Apple engineers) which I'd characterise as a development sneak peek. If you're interested in what's likely to appear in Apple products some time down the line you can find it on GitHub. The second is the Android Open Source Project's fork which has some small changes for the Android platform.
If you're completely new to the project your first stop should be
which forms the public API. You'll want to be familiar with
DNSServiceGetAddrInfo which are all self
descriptive as to the operations they start. Once an operation is started
DNSServiceRefSockFD is used to obtain a file descriptor which will become readable
when a new event is ready. To handle the event call
DNSServiceProcessResult, and when
finished with the operation call
DNSServiceRefDeallocate. For a further introduction
to the public API, see Apple's Introduction to DNS Service Discovery.
Behind these calls a stream is established to the daemon. On Windows it's TCP to
127.0.0.1:5354. Everywhere else it's UDS which by default connects to the
DNSSD_UDS_PATH, with a fallback to
. The POSIX Makefile changes the fallback to
while the Android fork changes it to
The stream carries an ad-hoc binary protocol composed of C friendly structures. Integers are in network byte order, strings are printable ASCII terminating with zero, and structs are packed.
All the previously mentioned operations begin with the client sending an
whose fields are used as follows:
version- protocol version, currently
datalen- length of the op specific data following the header
1for requesting no reply
op- identifies the request with a value from the
client_context- 8 bytes that are echoed in replies and which is used in the regular API to store a client pointer
reg_index- used to identify records by
Following the header is operation specific data. You can find the data necessary for an
operation by looking at its implementation in
example the browse op requires:
flags- a uint32 with a (relevant) set of flags from kDNSServiceFlags
interfaceIndex- a uint32 representing a network interface, zero means any
regtype- a string representing the service type
domain- a string containing a browse domain name, empty means any
If the request is poorly formed the daemon will close the connection, otherwise it
will respond with an int32 containing a kDNSServiceErr error value. If the value is
kDNSServiceErr_NoError (zero) the next value off the wire will be a response
CallbackHeader which consists of an
ipc_msg_hdr - this time
op containing a value from the
reply_op_t enum - along with a
uint32 set of flags from kDNSServiceFlags, an uint32 interface index, and an
int32 error from kDNSServiceErr. These last three values all count toward the
The remaining data will be operation specific. In the case of browse, we can see that in
DNSServiceBrowse a handler of
handle_browse_response was set, and from
its implementation we can expect a string containing a service name, a string
containing a service type, and a string containing a browse domain. We can determine
whether this service has been found or lost based on whether
kDNSServiceFlagsAdd is set in the flags recieved in the header, and expect
further sequences like this (without the initial error) as services come and go.
Most calls in the
dns_sd.h API will follow this pattern. There are exceptions like
DNSServiceGetProperty but you'll discover those as you go source diving to find the
request and response payload parameters.
The last concept worth mentioning is shared connections as created by
DNSServiceCreateConnection. Shared connections allow multiple operations on a
single stream by moving the initial error response to a seperate stream. On
Windows, this is done by creating a new TCP listening socket and including its port
immediately following the request
ipc_msg_hdr. For UNIX streams, you can
either start a named listener and pass its path as a string following the request header,
or you can create a new socket pair and pass one end to the daemon via
which is indicated by a null byte following the request header.
That's about it for this introduction. From here you'll likely want to spend some more
time leafing through
dnssd_ipc.c, all located in
mDNSShared. Let me know how you get on.