Buongiorno attempts to map out mDNS and DNS-SD activity by inspecting the raw stream of mDNS packets on the local network. The goal of this post is to give a bit more context for what mDNS and DNS-SD are, how the tool was built, and the many limitations of my current approach.

image.png

mDNS and DNS-SD

The mDNS spec (RFC 6762) is 70 pages long, but the core idea can be communicated in one sentence: it is like normal DNS, but queries are multicast to the entire network instead of unicast to a specific DNS server.

The way this works is simple: UDP packets containing DNS questions and answers are sent to the special multicast IP address 224.0.0.251 (or FF02::FB for IPv6) on port 5353. This causes the packets to be routed to all devices in the network, eliminating the need for a central DNS authority. Responses to these queries can themselves be sent over either multicast or unicast. The rest of the spec fleshes out details and provides strategies for avoiding network congestion, which I suppose is important but is not particularly interesting.

However, this alone does not provide services like device discovery, e.g. finding local printers on the network. This turns out to be provided by DNS Service Discovery, or DNS-SD (RFC 6763). DNS-SD sits on top of mDNS to provide a scheme for querying and advertising services. Suppose my laptop wants to find local printers using the Internet Printing Protocol:

  • My laptop multicasts a DNS query for PTR _ipp._tcp.local. _ipp._tcp is the reserved name for IPP, and local is the domain.
  • My printer responds by multicasting a DNS PTR record mapping _ipp._tcp.local to Brother HL-L3270CDW series._ipp._tcp.local. This tells my laptop of a service instance, with name "Brother HL-L3270CDW series".
  • Along with the PTR, my printer also includes SRV and TXT records to flesh out the service instance. The SRV record provides a hostname and port, in this case BRW30C9AB401F18.local:631. The TXT record contains key/value pairs describing additional metadata about the instance, such as the manufacturer and max paper size.

All together, these three DNS records define a complete DNS-SD service instance that any device can connect to.

image.png

Devices can also list all services on the network by querying PTR _services._dns-sd._udp.local, which is a special meta-query defined by the spec. My printer might respond with this with the following PTR records for all the different print protocols it supports, plus the website it serves, along with the associated SRV and TXT records:

PTR Brother HL-L3270CDW series._http._tcp.local
PTR Brother HL-L3270CDW series._ipp._tcp.local
PTR Brother HL-L3270CDW series._pdl-datastream._tcp.local
PTR Brother HL-L3270CDW series._printer._tcp.local

If you capture mDNS activity using Wireshark you can see all of this traffic quite clearly. It also reveals lots of weird behavior that may or may not be recommended by the spec, but makes sense in practice. For example, my laptop seems to occasionally multicast queries for its own services, and then multicast an answer to its own question shortly thereafter. This is very convenient for me, because it tells my tool that these services exist—and indeed I suspect that is the point of this behavior.

Technical details

The tech for this project is not very remarkable. I wrote it in Go, using bindings for SDL and dear imgui (which take a horrifically long time to build). Packets are captured using gopacket, which uses libpcap behind the scenes. DNS messages are parsed using miekg/dns.

Limitations

Overall, this project is limited in what it can show because it only operates from the perspective of a single host. That means that it can only see multicast traffic. For example, if my phone requests AirPlay and my iPad responds over unicast, I may never learn that my iPad has advertised the AirPlay service. Furthermore, since caching is prevalent throughout the system, I frequently see only a portion of the records needed to fully resolve a service—for example, I will see PTR records without the corresponding SRV and TXT, and without A or AAAA records to resolve the hostname to an IP.

The overall impact of this is simply that it takes the tool a long time to "warm up". I sometimes need to leave the tool running for hours before I see a packet that actually tells me what services are being advertised.

It's possible that I could bypass this issue by occasionally sending my own DNS packets without the mechanisms used to reduce network congestion (e.g. Known Answer Suppression), and possibly even spoofing new source IP addresses to flush out any other possible caches. In the future, though, I may want to look into integrating with my router somehow so that I have a broader view of all traffic on the network. If anyone has suggestions on how to do this, or other approaches I could try, please let me know!