mDNS

mDNS

在计算机网络中,多播DNS(Multicast DNS,mDNS)协议将主机名(hostname)解析为不包含本地名称服务器端的小型网络中的IP地址,旨在提供 本地网络服务主机发现。这是一种零配置的服务器端,mDNS与传统域名解析服务(DNS)有着基本相同的编程接口、数据包格式和操作方式。

mDNS由Bill Woodcock和Bill Manning于2000年在IETF中首次提出,在2013年最终由Stuart Cheshire和Marc Krochmal作为标准协议发布在 RFC 6762,并由Apple Bonjour开源Avahi 软件包实现,包含在大多数Linux发行版中(例如Ubuntu 22.04)。

mDNS 是本地网络的域名解析协议,使用 5353 端口,组播地址是 224.0.0.251,是运行于 UDP 之上的应用协议。不同于传统的DNS协议,mDNS协议不需要DNS服务器端进行域名解析,可节省本地网络的域名服务器端配置。

如果主机查询一个域名,那么该怎么区分该域名是DNS域名还是mDNS域名呢?mDNS域名与DNS域名是通过后缀.local区分的。

DNS-SD

DNS-SD(DNS-Based Service Discovery,即基于DNS的服务发现)是一个网络协议框架,它基于mDNS实现。DNS-SD主要用于在本地网络中自动发现并使用网络服务,如打印机、文件共享、媒体播放等。

工作原理

DNS-SD借助于多播DNS(mDNS),在本地网络(local network)内进行服务的发现和发布。以下是DNS-SD的基本工作原理:

  1. 服务注册
    服务提供者自注册。该信息通常包括服务名称、类型(如 “_http._tcp”)、端口号、及其他附加参数。
  2. 服务发现
    客户端在本地网络中查询特定类型的服务。例如,客户端广播一个查询,想找到所有的 “_http._tcp” 服务。其它设备收到这个查询后,会响应其提供的服务信息。
  3. 服务解析
    客户端在发现服务后,可以进一步解析获取具体的服务细节。例如,从服务名称解析出具体的 IP 地址和端口号。

DNS-SD 记录类型

DNS-SD 中采用了多个特定的 DNS 记录类型来进行服务的发现和信息传递:

  • PTR 记录:用于枚举网络中 某一类型 的所有服务。例如,”_http._tcp.local” PTR 记录列出了网络中所有的 HTTP 服务。
  • SRV 记录:提供具体的服务主机名和端口信息;每个服务有一个或者多个 SRV 记录。
  • TXT 记录:用来存储与服务相关的任意文本信息,如服务的配置选项等。
  • A/AAAA 记录:将服务主机名解析为一个或多个 IP 地址,其中A记录为IPv4地址,AAAA记录为IPv6地址。

限制

  • 局域网范围:DNS-SD 和 mDNS 一般只在局域网内工作,因为它们使用多播地址。
  • 名称冲突:多个设备在同一个局域网内提供相同名称服务时,会发生名称冲突,需要进行名称分配和冲突解决机制。

应用场景

DNS-SD 被广泛应用于各种需要自动发现网络服务的应用环境,如家庭网络、办公室局域网、多媒体设备连接等。例如:

  • Apple 的 Bonjour 服务发现技术就是基于 DNS-SD 和 mDNS 实现的。
  • 各种智能家居设备和 IoT 设备的互联互通,比如Matter、Home Assistant。

DNS-SD设备发现

例1:我想查询本地局域网所有(全局搜索)的 smb 服务,即查询PTR:_smb._tcp.local,若存在该服务,那么就会返回并解析结果(SRV、A/AAAA、TXT)。

1
2
3
4
5
6
I (641773) mdns-test: Query PTR: _smb._tcp.local
1: Interface: WIFI_STA_DEF, Type: V4, TTL: 120
PTR : raspberrypi._smb._tcp
SRV : raspberrypi.local:445
A : 192.168.0.210
AAAA: fe80:0000:0000:0000:0145:95a9:8787:0030

例2:我想查询本地局域网所有的 http 服务,即查询PTR:_http._tcp.local,若存在该服务,那么就会返回并解析结果(SRV、A/AAAA、TXT)。

1
2
3
4
5
6
7
I (651073) mdns-test: Lookup selfhosted service: _http._tcp.local
PTR : ESP32-WebServer._http._tcp
SRV : esp32-mdns.local:80
TXT : [3] p=password(8); u=user(4); board=esp32(5);
PTR : ESP32-WebServer1._http._tcp
SRV : esp32-mdns.local:80
TXT : [2] u=admin(5); path=/foobar(7);

例3:我想查询本地局域网所有的 HomeAssistant 服务,即查询PTR:_home-assistant._tcp,若存在该服务,那么就会返回并解析结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
I (42702) mdns-test: Query PTR: _home-assistant._tcp.local
1: Interface: WIFI_STA_DEF, Type: V6, TTL: 120
PTR : MyHome._home-assistant._tcp
SRV : ab96469f357e444280b0182d96da73b3.local:8123
TXT : [7] location_name=MyHome(6); uuid=ab96469f357e444280b0182d96da73b3(32); version=2024.11.3(9); external_url=NULL(0); internal_url=http://192.168.5.193:8123(25); base_url=http://192.168.5.193:8123(25); requires_api_password=True(4);
AAAA: fdfc:869c:9ebb:0000:0000:0000:0000:0936
A : 192.168.5.193
AAAA: fdfc:869c:9ebb:0000:c1a5:a79e:bc90:54b8
AAAA: fe80:0000:0000:0000:2498:72a5:dcc1:61e8
2: Interface: WIFI_STA_DEF, Type: V4, TTL: 120
PTR : MyHome._home-assistant._tcp
SRV : ab96469f357e444280b0182d96da73b3.local:8123
TXT : [7] location_name=MyHome(6); uuid=ab96469f357e444280b0182d96da73b3(32); version=2024.11.3(9); external_url=NULL(0); internal_url=http://192.168.5.193:8123(25); base_url=http://192.168.5.193:8123(25); requires_api_password=True(4);
AAAA: fdfc:869c:9ebb:0000:0000:0000:0000:0936
A : 192.168.5.193
AAAA: fdfc:869c:9ebb:0000:c1a5:a79e:bc90:54b8
AAAA: fe80:0000:0000:0000:2498:72a5:dcc1:61e8

验证

mDNS示例:query_advertise

当设备(例如esp32-c3)和PC处于同一个局域网,那么PC如何找到设备的IP地址呢? 此时mDNS就起作用了,设备启用了mDNS服务,当连接上路由器后,设备会向局域网组播地址发送组播报文:“我是谁,我的IP地址是多少,我提供的服务和端口号是多少”。如果PC想要查询mDNS域名,会先查询自己的缓存信息,如果没有查询到目标mDNS域名,则会向局域网组播查询目标mDNS域名(esp32-mdns.local)的IP是多少,以及提供的服务(service)和端口(port)是多少。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
I (373) mdns-test: mdns hostname set to: [esp32-mdns]

I (613) example_connect: Connecting to ZTE_5GCPE_F876...
I (613) example_connect: Waiting for IP(s)
I (3323) wifi:new:<7,1>, old:<1,0>, ap:<255,255>, sta:<7,1>, prof:1
I (3323) wifi:state: init -> auth (b0)
I (3333) wifi:state: auth -> assoc (0)
I (3363) wifi:state: assoc -> run (10)
I (3383) wifi:connected with ZTE_5GCPE_F876, aid = 1, channel 7, 40U, bssid = b0:0a:d5:75:f8:76
I (3393) wifi:security: WPA2-PSK, phy: bgn, rssi: -58
I (3393) wifi:pm start, type: 1

I (3393) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us
I (3403) wifi:set rx beacon pti, rx_bcn_pti: 0, bcn_timeout: 25000, mt_pti: 0, mt_time: 10000
I (3593) wifi:dp: 5, bi: 102400, li: 5, scale listen interval from 307200 us to 512000 us
I (3593) wifi:AP's beacon interval = 102400 us, DTIM period = 5
I (5373) example_connect: Got IPv6 event: Interface "example_netif_sta" address: fe80:0000:0000:0000:f29e:9eff:fe99:1260, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (6483) wifi:<ba-add>idx:0 (ifx:0, b0:0a:d5:75:f8:76), tid:0, ssn:0, winSize:64
I (7413) esp_netif_handlers: example_netif_sta ip: 192.168.0.169, mask: 255.255.255.0, gw: 192.168.0.1
I (7413) example_connect: Got IPv4 event: Interface "example_netif_sta" address: 192.168.0.169
I (7423) example_common: Connected to example_netif_sta
I (7423) example_common: - IPv4 address: 192.168.0.169,
I (7433) example_common: - IPv6 address: fe80:0000:0000:0000:f29e:9eff:fe99:1260, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (7443) gpio: GPIO[9]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (7453) main_task: Returned from app_main()
I (623103) mdns-test: Query both A and AAA: esp32-mdns.local
I (624203) mdns-test: Query A esp32-mdns.local finished
I (624203) mdns-test: Query AAAA esp32-mdns.local finished
I (624253) mdns-test: Query A: esp32.local
W (626273) mdns-test: ESP_ERR_NOT_FOUND: Host was not found!
I (626273) mdns-test: Query PTR: _arduino._tcp.local
W (629373) mdns-test: No results found!
I (629373) mdns-test: Query PTR: _http._tcp.local
W (632473) mdns-test: No results found!
I (632473) mdns-test: Query PTR: _printer._tcp.local
W (635573) mdns-test: No results found!
I (635573) mdns-test: Query PTR: _ipp._tcp.local
W (638673) mdns-test: No results found!
I (638673) mdns-test: Query PTR: _afpovertcp._tcp.local
W (641773) mdns-test: No results found!
I (641773) mdns-test: Query PTR: _smb._tcp.local
1: Interface: WIFI_STA_DEF, Type: V4, TTL: 120
PTR : raspberrypi._smb._tcp
SRV : raspberrypi.local:445
A : 192.168.0.210
AAAA: fe80:0000:0000:0000:0145:95a9:8787:0030
2: Interface: WIFI_STA_DEF, Type: V6, TTL: 120
PTR : raspberrypi._smb._tcp
SRV : raspberrypi.local:445
AAAA: fe80:0000:0000:0000:0145:95a9:8787:0030
I (644893) mdns-test: Query PTR: _ftp._tcp.local
W (647973) mdns-test: No results found!
I (647973) mdns-test: Query PTR: _nfs._tcp.local
W (651073) mdns-test: No results found!
I (651073) mdns-test: Lookup selfhosted service: _http._tcp.local
PTR : ESP32-WebServer._http._tcp
SRV : esp32-mdns.local:80
TXT : [3] p=password(8); u=user(4); board=esp32(5);
PTR : ESP32-WebServer1._http._tcp
SRV : esp32-mdns.local:80
TXT : [2] u=admin(5); path=/foobar(7);

抓包

image-20241107103144932

猜想

1、如果将esp32 mDNS demo固件烧录到相同的开发板,那么理论上两块开发板设置的本地mDNS域名应该都是 esp32-mdns.local

验证结果:实际的结果跟猜想不一致,开发板A的本地mDNS域名是 esp32-mdns.local,而开发板B的本地mDNS域名是 esp32-mdns-2.local

1
2
3
4
5
6
7
8
9
10
11
12
13
# 通过和开发板在同一局域网的PC利用avahi-browse
$ avahi-browse -a -r -l
= wlp1s0 IPv4 ESP32-WebServer1-10 Web Site local
hostname = [esp32-mdns.local]
address = [192.168.0.169]
port = [80]
txt = ["path=/foobar" "u=admin"]
= wlp1s0 IPv4 ESP32-WebServer1 Web Site local
hostname = [esp32-mdns-2.local]
address = [192.168.0.155]
port = [80]
txt = ["path=/foobar" "u=admin"]

2、通过mDNS可以获取局域网内的主机名和IP,那么还能获取什么信息?

还是以 “esp32 mDNS demo” 为例,看一下在发布mDNS组播数据包需要设置哪些东西。

  • mDNS属性

    • hostname:设备会去响应的主机名,如果没有设置,会根据设备的网络接口名定义 hostname 。例如,my-esp32 会被解析为 my-esp32.local
    • default_instance:默认实例名(即易记的设备名),例如 Jhon's ESP32 Thing。如果没有设置,将会使用 hostname
  • mDNS服务( DNS-Based Service Discovery,DNS-SD)

    mDNS 可以广播设备能够提供的网络服务的相关信息,每个服务会由以下属性构成:

    • instance_name:实例名(即易记的服务名),例如 Jhon's ESP32 Web Server。如果没有定义,会使用 default_instance
    • service_type:(必需)服务类型,以下划线为前缀,这里 列出了常见的类型。
    • proto:(必需)服务运行所依赖的协议,以下划线为前缀,例如 _tcp 或者 _udp
    • port:(必需)服务运行所用的端口号。
    • txt:形如 {var, val} 的字符串数组,用于定义服务的属性。

    例如:可以发布一个http服务:http://esp32-mdns.local:80/foobar

参考

1、multicast DNS

2、ESP mDNS 学习