利用 mDNS 协议来定位局域网主机

再也不用费劲去找树莓派的 IP 地址了!你的操作系统自带了“网络发现”功能。

在一个平常的局域网(开启了 DHCP)下面,获得一台主机的 IP 地址常常有这么几种方法:

  1. arp -a 命令查询 arp 缓存;
  2. 在目标主机上查找本机的 IP 地址,使用例如 ipconfig 命令;
  3. 浏览器登录路由器的后台,从连接的主机列表中查找;
  4. 给目标主机设置静态IP。

曾几何时,我一直用上面的方法来连接我的树莓派。树莓派没接显示器,没办法在主机上操作,每次要么使用手机热点(设备管理里面能显示 IP),要么直接改配置分配静态IP。这些方法都有局限性,网络环境一变化,又得配置半天。

现在,一个新的方法出现了!(其实一直就有)只要知道你的设备的主机名,你就可以得到你的设备的 IP 地址。例如,我的树莓派是 Raspbian 系统,它的默认主机名是 raspberrypi,就可以通过域名 raspberrypi.local 来连接,像这样:

1
ssh pi@raspberrypi.local

这就是 mDNS (Multicast DNS)协议给我们提供的便利。

偶然的发现

在研究 IGMP 协议的时候,我用 Wireshark 对无线网卡抓取 IGMP 数据包,想看看有没有什么服务是在使用组播(Multicast)地址的。

Wireshark软件截图

之后我看到了局域网内的电脑基本上都会加入这几个组播地址:224.0.0.251224.0.0.252239.255.255.250

经过查找,这三个组播地址分别是协议 mDNS,LLMNR 和 SSDP 协议的。mDNS实现了类似 DNS 的功能,使得主机在局域网内能够加入和通信;LLMNR 的功能和 mDNS 类似;SSDP 是 UPnP 协议的一部分,也是用来实现设备和服务的发现的。

在一段 mDNS 报文中,我看到了这样的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Multicast Domain Name System (response)
[Request In: 172]
[Time: 0.205589000 seconds]
Transaction ID: 0x0000
Flags: 0x8400 Standard query response, No error
Questions: 0
Answer RRs: 4
Authority RRs: 0
Additional RRs: 0
Answers
9.c.4.3.e.b.a.3.5.6.5.3.2.8.e.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa: type PTR, class IN, cache flush, raspberrypi.local
raspberrypi.local: type A, class IN, cache flush, addr 192.168.10.108
108.10.168.192.in-addr.arpa: type PTR, class IN, cache flush, raspberrypi.local
raspberrypi.local: type AAAA, class IN, cache flush, addr fe80::e82:3565:3abe:34c9

来源是 192.168.10.108,目的是 224.0.0.251。 返回的记录中有我的树莓派的 IPv4 和 IPv6 地址,以及一个相同的字段 raspberrypi.local

难道说 raspberrypi.local 就是我树莓派的域名?我立马 ping 了一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PS C:\WINDOWS\system32> ping raspberrypi.local

正在 Ping raspberrypi.local [fe80::e82:3565:3abe:34c9%13] 具有 32 字节的数据:
来自 fe80::e82:3565:3abe:34c9%13 的回复: 时间=5ms
来自 fe80::e82:3565:3abe:34c9%13 的回复: 时间=2ms
来自 fe80::e82:3565:3abe:34c9%13 的回复: 时间=4ms
来自 fe80::e82:3565:3abe:34c9%13 的回复: 时间=5ms

fe80::e82:3565:3abe:34c9%13 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 2ms,最长 = 5ms,平均 = 4ms
PS C:\WINDOWS\system32> ping raspberrypi.local -4

正在 Ping raspberrypi [192.168.10.109] 具有 32 字节的数据:
来自 192.168.10.109 的回复: 字节=32 时间=2ms TTL=64
来自 192.168.10.109 的回复: 字节=32 时间=1ms TTL=64
来自 192.168.10.109 的回复: 字节=32 时间=2ms TTL=64
来自 192.168.10.109 的回复: 字节=32 时间=3ms TTL=64

192.168.10.109 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 1ms,最长 = 3ms,平均 = 2ms

确实可以。我流下了激动的泪水:这么好的东西怎么没早发现呢!

mDNS 查询过程

通过分析抓包数据,mDNS 协议在局域网内的 IP 地址查询过程大致是这样的:

A:发起查询的主机

B:被查询的主机

  1. A,B 在加入网络时都会发 IGMP 报文加入组 224.0.0.251
  2. A 向组 224.0.0.251 发送 mDNS 组播报文查询 B 的主机名,所有在组内的主机都会收到这个查询请求;
  3. B 向组 224.0.0.251 发送回应 mDNS 报文,内容包括自己的主机名和 IP 地址;
  4. A 得到 B 的 IP 地址。

其中,mDNS 报文是包装在 UDP 组播报文中的,使用 5353 端口。

主机名的查询和修改

Windows 系统的主机名在环境变量 COMPUTERNAME 查看,可以在系统设置中修改。

1
echo %COMPUTERNAME%
Windows更改主机名

Linux 系统主机名存在 /etc/hostname 中。使用 hostnamectl 可以查询和修改。/etc/hosts 里也有主机名的记录,也要一并修改。

1
2
3
4
5
6
7
8
#查询主机名
hostname

#临时更改,重启失效
sudo hostname <newhostname>

#hostnamectl只更改/etc/hostname,记得改/etc/hosts
sudo hostnamectl set-hostname <newhostname>

我的系统支持吗

Windows 10 以后,系统就能够支持 mDNS 协议了。Windows 10 以下的系统怎么办?你可以:

  1. 给 Windows 安装苹果公司的 Bonjour,只有 5 Mb 大小,推荐。

  2. 使用 NetBIOS 协议,这是微软的网络发现服务。Linux 系统上安装 samba,它的 nmbd 服务使得能被 Windows 主机发现。使用方法:

    • Windows上:主机名即为域名。主机名是 raspberrypi,直接 ping raspberrypi
    • Linux上:默认不解析 NetBIOS 主机名,使用 nmblookup 主机名 来得到 Windows 主机 IP。如果要实现解析 NetBIOS 主机名,需要修改配置文件,没折腾了,看这篇文章 -> 在 Linux 解析 Netbios 名稱

Linux 一般是安装了 avahi-daemon 服务。使用指令 systemctl status avahi-daemon.service 来查看服务运行状态。

avahi-daemon

安卓系统似乎不支持,我的华为手机没有。

还可以查询 UDP 5353 端口。

Windows:

1
netstat -ano | findstr 5353

Linux:

1
sudo netstat -nap | grep 5353

其实最简单的就是 ping 一下 主机名.local 看看有没有就知道了。

参见: