FABIEN SANGLARD'S WEBSITE

ABOUT  CONTACT  RSS  GIVE


Aug 11, 2023
mDNS Primer

I recently had to ramp up on the topic of mDNS, starting from knowing next to nothing about it. It was not hard to find the motivation to learn about a technology which makes my life easier. From discovering AppleTV, Chromecast, AirPlay, and wireless printers, mDNS it a good thing.

However, I had trouble finding answers to some of my questions. There is definitely learning material out there but it is sprinkled all over the net. So I built myself a "mDNS starter pack".

Presentation

Stuart Cheshire, an mDNS pioneer, gave a talk named "Zero Configuration with Bonjour" (Apple's implementation of mDNS) in 2005. Even though it is twenty years old, it is still relevant...and preferably available on youtube.

Three pillars of mDNS

Zero Configuration Networking relies on three pillars which are described in RFCs.

RFC 3927 : Self-assigned IP
RFC 6762 : Multicast DNS protocol
RFC 6763 : DNS-Based Service Discovery

They are well-written and as easy to read as an RFC gets. It may also be useful to read the following.

RFC 1035 : TXT records
RFC 2136 : DNS Update
RFC 2782 : SRV records
RFC 4033 : DNS Security Extensions

Book

In the talk, Stuart mentions a book about to be published. It has been made available since and it is a gem. It elaborates extensively upon mDNS specs and discusses design decisions (e.g.: why application protocol names start with a '_').

Among many good pieces of advice, the volume recommends to play with dns-dn CLI[1].

Enumerate domain

$ dns-sd -E
Looking for recommended registration domains:
DATE: ---Mon 07 Aug 2023---
...STARTING...
Recommended Registration domain
Added                          local

Browse services[2]

$ dns-sd  -B  _services._dns-sd._udp  local.
Browsing for _services._dns-sd._udp.local.
DATE: ---Sun 30 Jul 2023---
...STARTING...
A/R    Flags  if Domain    Service Type     Instance Name
Add        3  15 .         _udp.local.      _gasetup
Add        3  15 .         _tcp.local.      _hap
Add        3  15 .         _tcp.local.      _airplay
Add        3  15 .         _tcp.local.      _pdl-datastream
Add        3  15 .         _tcp.local.      _printer
Add        3  15 .         _tcp.local.      _ipp
Add        3  15 .         _tcp.local.      _http
Add        3  15 .         _tcp.local.      _googlecast
Add        2  15 .         _tcp.local.      _googlezone

Browse by Service Type

$ dns-sd -B _http._tcp local.
Browsing for _http._tcp.local.
DATE: ---Sun 30 Jul 2023---
...STARTING...
A/R    Flags  if Domain    Service Type     Instance Name
Add        2  15 local.    _http._tcp.      Brother HL-L3230CDW series

Lookup a service

$ dns-sd -L "Brother HL-L3230CDW series" _http._tcp. 
Lookup Brother HL-L3230CDW series._http._tcp..local
DATE: ---Sun 30 Jul 2023---
...STARTING...
Brother\032HL-L3230CDW\032series._http._tcp.local. --> BRW106FD95E9F90.local.:80

hostname to ipv4/ipv6

$ dns-sd -Gv4v6 BRW106FD95E9F90.local
DATE: ---Sun 30 Jul 2023---
...STARTING...
A/R  Flags    IF  Hostname                 Address                      TTL
Add  3        15  BRW106FD95E9F90.local.   192.168.86.37                120
Add  2        15  BRW106FD95E9F90.local.   fe80::126f:d9ff:fe5e:9f90    120

Register a Service

$ dns-sd -R Example _http._tcp local 80

Proxy-Register a Service

$ dns-sd -P Google _http._tcp local 80 google.com google.com

Running wireshark while experimenting to see mDNS/DNS packets helps bringing extra clarity.

Why mDNS is better with a daemon

Something that is not well explained is why the two dominant mDNS solutions use a daemon (Bonjour has mdnsd and Avahi has avahi-daemon) while some other frameworks, like openscreen, do not.

┌──────────────────────────┐   ┌──────────────────────┐    ┌──────────────────────────┐   
│        Machine A         │   │       Printer        │    │        Machine B         │
│                          │   │    ┌────────────┐    │    │                          │
│  ┌──────┐    ┌────────┐  │   │    │    proc    │    │    │ ┌────────┐     ┌──────┐  │
│  │      │    │        │  │   │    └─────┬──────┘    │    │ │        │     │      │  │
│  │ proc ├────► mdnsd  ├──┼─┐ │          │           │  ┌─┼─┤  avahi ◄─────┤ proc │  │
│  │      │    │        │  │ │ └──────────┼───────────┘  │ │ │        │     │      │  │
│  └──────┘    └────────┘  │ │            │              │ │ └────────┘     └──────┘  │
│                          │ │            │              │ │                          │
│                ┌──────┐  │ │ ┌──────────▼───────────┐  │ │  ┌──────┐                │
│                │      │  │ └─►                      ◄──┘ │  │      │                │
│                │ proc │  │   │   224.0.0.251:5353   │    │  │ proc │                │
│                │      ├──┼───►                      ◄────┼──┤      │                │
│                └──────┘  │   │    ff02::fb]:5353    │    │  └──────┘                │
│                          │   │                      │    │                          │
└──────────────────────────┘   └──────────────────────┘    └──────────────────────────┘

Embedded systems will tend to statically link a full mDNS stack (like openscreen) but going through a daemon has significant advantages.

Daemon connection

By far the most useful aspect of a daemon is automatic removal of a service when a process terminates. How does a daemon achieve such a feat?

Bonjour

Bonjour uses a Unix Domain Socket (UDS) (/var/run/mDNSResponder[3][4]) when they are available. A process registering a service opens a connection to the UDS. When the process finishes, mdnsd sees the connection ending and unpublish the service automagically.

How Bonjour Works
─────────────────
                                                      ┌────────────────────┐
                                                      │ Client Application │
      ┌────────────────┐                              ├────────────────────┤
      │  uds_daemon.c  │◄─────Unix Domain Socket ────►│      libmdns       │
      ├────────────────┤                              └────────────────────┘
      │    mDNSCore    │
      ├────────────────┤
      │  mDNSPosix.c   │
      └────────────────┘

On Windows, there are no UDS so instead Bonjour creates a TCP server socket on 127.0.0.1:5354[5]. If you are interested in learning about Bonjour further, the generous ReadMe.txt[6][7] in mdnsResponder source code are goldmines. Apple's Bonjour documentation is also pretty good.

Avahi

Avahi uses a different technique to keep track of processes' life cycle. Instead of UDS, it relies on Linux's dBus.

How Avahi Works
───────────────
                        
┌────────────────────────────────────────────────────────────────────────────┐
│                                 Linux dBUS                                 │
└───────────┬────────────────────────────▲─────────────────────────▲─────────┘
            │                            │                         │
            │                            │                         │
┌───────────▼───────────┐     ┌──────────┴─────────┐    ┌──────────┴─────────┐
│     daemon/main.c     │     │   Avahi dBUS API   │    │   Avahi dBUS API   │
├───────────────────────┤     ├────────────────────┤    ├────────────────────┤
│         Avahi         │     │ Client Application │    │ Client Application │
└───────────────────────┘     └────────────────────┘    └────────────────────┘


When a process connects to a dBUS, the bus assigns to the connection a unique connection name. When the process dies, the same dBUS is notified[8]. This process life-cycle monitoring allows Avahi to unpublish a mDNS service if the process dies without doing it due diligence.

If you are as OCD as I am, you will want to inspect the message transiting on dBUS. It is possible and the instructions are here and here.

UDP

Most developers are familiar with the TCP model involving socket/bind/listen/accept on the server side, socket/connect on the client side, and write/read in between.

mDNS uses UDP. Even though it is simpler, the protocol cannot be simplified to sending/receiving datagrams. There are unconnected UDP sockets using sendto/revcfrom, connected sockets using recv/send (or even read/write), and in the case of multicast, groups to join.

The bible to get up to speed is Unix Network Programming by W. Richard Stevens.

SO_REUSEPORT / SO_REUSEADDR

Knowing that a daemon may be running should influence how you design your mDNS program. The daemon may already be bound to port 5353 and prevent your program from using it. Or your program may have to be friendly with an up and running deamon. When using UDP multicast, there are ways to share a port via SO_REUSEPORT/SO_REUSEADDR socket option. When properly setup, all processes see all received/emitted packets for a shared multicast UDP port.

The subtle peculiarities of SO_REUSE* options are well explained on stackoverflow by Mecki[9]. The SO_REUSEPORT option is further explained on lwn.net[10]. The full picture and reference (p. 731 of TCPv2) comes, once again, from the bible Unix Network Programming.

5353 port hogging

Theory is nice but the only way to be sure is to verify. Here is a cross-platform mDNS workbench wb.c to test ways to share the mDNS port depending if SO_REUSEADDR and/or SO_REUSEPORT options are set.

System mDNS Daemon SO_REUSEADDR / SO_REUSEPORT
MacOS X Bonjour SO_REUSEPORT
Linux Avahi[11] SO_REUSEADDR and/or SO_REUSEPORT
Windows[12] UWP SO_REUSEADDR
Windows with Bonjour[13] Bonjour[14] SO_REUSEADDR
References

^ [ 1]On Linux, avahi CLI gets you the same result.
^ [ 2]List of service types
^ [ 3]mdnsd uses /dev/socket/mdnsd on Android.
^ [ 4]mDNSResponder IPC Protocol
^ [ 5]mDNSResponder USE_TCP_LOOPBACK
^ [ 6]ReadMe About mDNSPosix
^ [ 7]ReadMe About mDNSWindows
^ [ 8]D-Bus Tutorial
^ [ 9]How do SO_REUSEADDR and SO_REUSEPORT differ?
^ [10]The SO_REUSEPORT socket option
^ [11]Avahi has been commited to SO_REUSEPORT for at least 18 years!
^ [12]On Windows SO_REUSEPORT is not supported (compilation error).
^ [13]On Windows SO_REUSEPORT is not supported (compilation error).
^ [14]Packaged with iTunes, Bonjour is often found on Windows.


*