在同一个链路网络(Link Local)中,Windows平台上的域名解析经历了netBIOS,到LLMNR,直到Windows 10才有mDNS. LLMNR(Link Local Multicast Name Resultion)协议和mDNS(multicast DNS)都是基于DNS这个传统协议, 而且在局域网环境中都采用了广播机制,并且在一定的时间内一般会多次广播同一查询消息, 但LLMNR在Query时使用broadcast, Reply使用unicast机制, mDNS的应答通常也是广播(除非在QCLASS字段最高位设置1),可以让其他主机收到消息更新cache,由于LLMNR适用于任何域名的解析需求, 而不仅仅是.local的域名, 因此也带了一些潜在的安全隐患, 这或许是LLMNR没有被IETF采纳的原因吧. mDNS是Apple公司的Bonjour协议基础上发展起来的, 最终成为一个正式标准(RFC 6762), 从Windows 10, Mac, Linux到RaspberryPi都支持这个协议(例如在Ubuntu中可以通过查询命令sudo fuser 5353/udp发现是avahi实现的mDNS).
mDNS除了可以解析域名,也可以配合DNS-SD(Service Discovery)查询服务, 返回的结果称为Resource Records. 在IPv4中它使用广播组224.0.0.251(IPv6中为FF02::FB), 端口为UDP 5353 (传统的DNS中端口为UDP/TCP 53, TCP通常在UDP包大小受限时使用). 类似于DNS, 消息的格式(参考RFC 1035)由四部分组成: header, questions, answers, authorities和additionals. 其中:
1) header中的字段QR用来区分是查询还是应答, 其中下ID字段在广播查询和应答中被忽略,因为广播不需要识别符; 当QR=0表示查询, 当QR=1表示应答;OPCODE是0, 表示标准查询(在传统的DNS中可能还支持其他的OPCODE,比如逆向查询); 在应答消息中AA(Authoritative Answer)必须是1, 因为每一个应答都是单台主机发出的属于自己的权威信息;另外RA和RD这两个关于recursion的字段都被忽略 (c.f. 在DNS中要求实现iterative查询,而recursion查询是可选的). 剩下了的四个计数字段分别说明后面的questions, answers, authorities和additionals各有多少Resource Records (RR)条目.
2)questions部分是查询的域名信息,mDNS中每个查询的questions可以一次发送多个(比如同时发送A和AAAA),这样可以提高效率.每个question包括三个字段QNAME, QTYPE和QCLASS. 其中QNAME是要查询的域名,例如raspberrypi.local.或2.1.168.192.in-addr.arpa.等;QTYPE对应DNS中的types,比如A, AAAA, PTR等记录类型,当QTYPE为Any时,在接下来的应答中要求返回所有的QTYPE类型的信息,而不像传统的DNS那样可能只返回一部分信息;QCLASS使用IN(表示internet),需要注意的是,由于QCLASS是一个16位的字段,平时最高位并没有被使用,当最高位设置为1表示接下来期望得到的应答是unicast的,即QU消息(应用场景:比如在主机刚上电,系统刚启动的时候,发送QU查询可以提高网络使用效率),其他情况是broadcast,即QM消息.说到网络效率,在QM查询时, 主机也往往在answers部分填充已经知道的信息(Known-Answers)和这些已知资源的TTLs,避免接下来收到重复发送应答, 当Known-Answers太多时,还需要设置header中的TC字段,表明后面还有其他包需要发送.
3)answers, authorities和additionals都是Resource Records (RRs)部分,每一条RR记录包含NAME, RTYPE, RCLASS,TTL, RDLENGTH和RDATA字段.其中NAME表明该资源的名称,RTYPE是类型,比如A, PTR(在mDNS中没有SOA这样的record);RCLASS通常使用IN,在answers部分,将资源的RCLASS字段的最高有效位可以设置为cache-flush=1,告诉其他主机需要将该资源更新; TTL若等于0表明该信息已经过时(比如打印机等共享资源可以通过unsolicited responses的形式发送TTL=0的包告诉该条旧资源已经过时,这样其他主机可以将该资源删除);RDLENGTH是指定RDATA的长度(因为RDATA是可变字段); RDATA根据RTYPE和RCLASS的不同,具有不同的值,比如当RTYPE=A,RCLASS=IN时,RDATA字段就是IPv4地址.
系统刚启动时一般还有Probe和Announcement过程,这点可以联想到IPv4 Link-Local Address中使用ARP的机制实现类似功能(RFC3927),其中Probe是查询当前主机的信息是否合法可用,Announcement是用unsolicited responses的形式,告诉其他主机关于当前主机的信息;mDNS和DNS的另一个不同之处在于,mDNS可以让主机主动否定某资源的存在,这往往发生在该主机独占该资源,但当前该资源还未加入使用的情况,这是通过借用NSEC记录来设置的,更多的内容可以参考RFC6762.
mDNS的协议抓包可以使用Wireshark工具,例如通过设置udp and udp.port ==5353过滤可以更容易查看UDP包.在Linux上可以安装avahi-utils,发送查询请求,例如:avahi-resolve --name raspberrypi.local. 另外在python 3中可以使用dnslib查询.
总之,mDNS作为标准,几乎在任何最新设备上都有实现,掌握mDNS的消息格式开发应用中具有非常重要的意义.