dig DNS - 2
- Published on
关于 DNS,我本只是想知道我的电脑(Ubuntu 22.04)究竟是怎么处理这个问题的,具体的,如何指定本机所有 DNS 查询请求去到我设定的上游 DNS 服务器,对应 之前 DNS 查询流程的步骤 1 和 2。
结果事到如今,发现这是一个远比我以为的复杂得多的问题,DNS 甚至都不是 the whole picture,整个问题域现在看下来至少要扩大到 名称解析(name resolution) 这一更大的范畴,而 DNS 只是其中一种重要的技术手段,其他还有如 LLMNR、mDNS、DNSSEC 验证等。此外,即使只聚焦于 DNS,问题也有很多,后面会触及到。
因为 Ubuntu、Fedora、Arch 等 Linux 发行包都默认使用 systemd-resolved
来为本地应用提供名称解析相关的系统服务,所以本篇内容基本就是以它为中心,关于它是什么、怎么工作、怎么设置、以及如何避开它使用其他技术。
不同于前一篇,这篇会涉及很多技术性的概念,没有办法,只有基于这些更底层的系统相关的细节,才能摸清其中的道道,进而实现灵活且有把握的控制。
目录
systemd, D-Bus 和 glibc
systemd
单从名称上看就知道 systemd-resolved
和 systemd
肯定是有关系的。
systemd
是 Ubuntu 等 Linux 系统采用的系统和服务管理器(System and Service Manager),是开机后 init
的一种具体实现,是用户空间第一个被启动的进程(PID 1)。systemd-resolved
是它的组成部分之一,主要负责名称解析。这里 可以看到 Ubuntu 版 systemd
源文件的构成。
D-Bus
D-Bus
即桌面总线,主要用于进程间通信(IPC,interprocess communication)。靠近用户端的不同桌面程序可以通过它相互沟通,更底层的 systemd
和 NetworkManager
也都使用它通信。因此,systemd-resolved
为本机其他需要域名解析服务的应用提供的首要的 API 就是 D-Bus API。官方文档有清晰的说明,推荐所有应用首选 systemd-resolved
D-Bus API。关于 NetworkManager
,这里先简单说一下,很明显它是用于网络配置管理的,主要面向桌面用户,用户通过它配置网络,它再用相关信息对更底层、远离用户端的 systemd-resolved
进行配置。此外,说是用户配置,其实多数时候都是软件自动完成的,除非用户出于某些需要想要做一些特定的调整。
glibc
glibc
即 GNU 版的 C 标准库。
之所以要提及它,是因为很多时候,域名解析都是应用通过调用 glibc
函数实现的,如曾经的 gethostbyname()
,现在的 getaddrinfo()
、getnameinfo()
。于是,为了继承和兼容支持调用 glibc
域名解析函数的应用,systemd-resolved
同时提供 glibc
API,这部分尤其纠缠,后面再重点梳理。
参考 1
systemd
主开发写的介绍文档Notes on D-Bus: D-Bus is the IPC mechanism of choice of NetworkManager
从
NetworkManager
视角介绍 D-Bus
glibc nss-dns
最早期甚至还没有 DNS 查询的时候,/etc/hosts
这个用户手写的文档就存在了,用于本机本地名称解析。现在各种复杂的技术都出现了,这个文档依然存在,而且,非常重要的是,各个应用实现自己名称解析的手段可能五花八门随心所欲,但是它们 全都尊重 /etc/hosts
的设定!
慢慢地网络出现了,NSS (Name Service Switch)就来了,主要是为了实现同一网络内不同机器之间动态地分享配置信息,glibc
中有一个专门的模块叫作 NSS 就负责这部分功能的实现,名称与对应 IP 也是其中一组配置信息。
关于 nss 的配置,可以查看 /etc/nsswitch.conf
文件:
如上图所示,hosts
这一行信息就规定了当应用调用 glibc nss
做域名解析时(如曾经的 gethostbyname()
或现在的 getaddrinfo()
),它的行动步骤:
files
表示会首先查询/etc/hosts
文件中保存记录的域名和 IP 信息mdns4_minimal
表示 多播 DNS(Multicast DNS, mdns),只负责解析.local
域名。多播就是在局域网内广播域名,同一网内的设备如果听到自己的域名被呼喊,就会立即报告 IP。后面紧跟的[NOTFOUND=return]
表示如果前面的mdns_minimal
没有找到域名对应 IP,就立即返回错误报告,不要再使用后面的技术继续解析,即不必要把本地的.local
域名丢给外部的 DNS 服务器解析。dns
,就是发送请求给外部的 DNS 服务器去查询了。至于访问外部的哪个 DNS 服务器,则是/etc/resolv.conf
文件规定的。
这个 /etc/resolv.conf
文件也是复杂又纠缠,后面再细说。这里只说一点,某些应用可能不会调用 glibc nss-dns
来做域名解析,它们可能会直接读取 /etc/resolv.conf
内设定的 DNS 服务器 IP 信息(通常会是路由器默认使用的网络服务商的 DNS 服务器),自己独立完成域名解析工作。这些无疑都增加了整个名称解析问题的复杂性。
以上即 systemd-resolved
入场之前,通常的,使用或绕过 glibc nss-dns
如何实现域名解析。
参考 2
What Does It Take To Resolve A Hostname
关于名称解析问题的复杂性,本文是很好的说明,特别是从历史的角度,做了更具体的梳理。但可能是演讲稿转化的缘故,虽然给出了重要线索和方向还有历史溯源,很多地方的表达我感觉不是很清晰,在对文中概念不熟悉的情况下,上来就看或许会很懵,当然技术背景强的人可能一看就懂。
这篇文章主要是在批评基于
glibc
调用getaddrinfo(3)
解析域名是不可靠的,建议软件开发时使用专门的名称解析软件包。
systemd-resolved
本文的主角终于正式上场了!
前面已提及,systemd-resolved
是 systemd
的构成部分,是 Ubuntu 等 Linux 系统自带的 Network Name Resolution 管理器,目的是为本机软件提供域名解析相关的服务,它会以 守护进程(daemon) 的方式在后台持续运行,可以承担缓存、DNS/DNSSEC 解析器、以及 LLMNR 与 MulticastDNS 解析器等工作。
三种接口
我当前的理解是,它试图通过提供多种接口把本机所有应用的名称解析请求全都聚拢到它这里来,由它自己统一负责。具体地:
官方文档首推的 D-Bus API,功能齐全,异步,且支持 DNSSEC 验证
针对那些习惯调用
glibc
getaddrinfo(3)
,即,使用glibc nss-dns
的应用,通过提供glibc nss-resolve
这样一个插件,实现那些应用和systemd-resolved
的通信,从而由后者接手负责名称解析;非异步,不支持 DNSSEC 验证针对那些无论以上 1 和 2 都不使用而自己直接向外发送 DNS 查询请求的应用,
systemd-resolved
另外在本地127.0.0.53#53
和127.0.0.54
回环接口上布置了 DNS stub listener,试图以此将那些应用连接上systemd-resolved
,仍由后者负责 DNS 查询。
补充说明:
可能是因为没有软件开发的实操经验,我在 2 这个点上原地打转了好久才终于明白过来,以防有人和我类似,所以再啰嗦几句。我的理解就是,那些调用 glibc
函数的应用,看起来是在利用 glibc
原有的、它自己的 DNS 查询和解析功能(即 nss-dns
),但其实 systemd-resolved
通过 glibc nss-resolve
这个插件,将 DNS 查询和解析这项工作给中途拦下并自己接手了!
3 也是类似道理。前面说过,有些“任性”的应用就是要自己负责 DNS 查询,所以它们会直接从 /etc/resolv.conf
文档中读走外部 DNS 服务器的 IP 地址。于是,systemd-resolved
就在 127.0.0.53#53
布置一个本地 resolver 监听着 53 端口,同时把本地这个 IP 地址写入 /etc/resolv.conf
文档,这样那些“任性”的应用发出的查询请求不就又掉到它怀里去了嘛!
总之就是你们不要自己搞,全都交给我来统一搞定!(也是煞费苦心有点可爱...
nss-resolve vs nss-dns
既然 systemd-resolved
如此积极抢工作,那它到底有啥好处呢,特别是相比通常使用的 glibc nss-dns
nss-resolve
支持根据不同的网络接口(如无线网卡接口、以太网接口等),分别设置 DNS 远程服务器,实现 DNS 分流;而nss-dns
,只支持设定一个全局的 DNS 服务器列表(最多设定 3 个)nss-dns
会被调用加载到每个应用自己的进程中,各个进程分别各自访问远程 DNS 服务器。而nss-resolve
被调用进入进程后,会通过 D-Bus 接口联系systemd-resolved
守护进程,统一让后者去完成访问远程 DNS 服务器的工作。因为它都统一接下了,于是在自己内部可以创建一个大家共用的 缓存使用 D-Bus 通信,
nss-resolve
的状态是动态变化的,可以和其他守护进程或图形界面有更强的整合,例如NetworkManager
说明:从前面 /etc/nsswitch.conf
文件 hosts
部分截图可以看出,里面当前没有 resolve
,说明我本机还没有启用这个 nss-resolve
插件。
DNS 服务器设定
以下这几种方式都可以设定 systemd-resolved
使用的 DNS 服务器
全局设定遵从
/etc/systemd/resolved.conf
文档的内容。注意,不同于前面出现的/etc/resolv.conf
文档除全局设定外,支持 per-link 静态文件设定
Ubuntu 服务器版使用
systemd-networkd
进行网络配置管理,依据不同的网络接口,对应的静态文件是/etc/systemd/network/*.network
Ubuntu 桌面版使用
NetworkManager
,因为 Ubuntu 现在用netplan
作为NetworkManager
和systemd-networkd
的前端,对应文件是/etc/netplan/*.yaml
per-link DHCP 动态设定:
NetworkManager
自动地通过D-Bus
将网络接口信息推送给systemd-resolved
(它俩在系统内有很好的整合)resolvectl
是systemd-resolved
D-Bus API 的 wrapper,在终端内调用这个命令可以实现多种功能,如域名解析、清理缓存、设定 DNS 服务器等,后面命令部分有具体示例其他服务的设定。后面会讲到,其实我本机因为安装了 AdGuard Home,实际上回避掉了
systemd-resolved
提供的服务,AdGuard Home 通过/etc/systemd/resolved.conf.d
drop-in 的方式设定了它自己作为 DNS 服务器。(装它是稀里糊涂装的,道理是后来才懂的,后面再细说。)
总之,关键就是 /etc/systemd/resolved.conf
的设定以及 per-link DHCP 动态设定。一般情况下,连接路由器后,它们会自动配置好网络以及对应文件保存的设定。只有当用户因为某些个性化的需求要做特别调整时,可能才会去查看相关信息,并手动修改。
使用 resolvectl status
命令查看,我系统内当前的 DNS 服务器设定如下,127.0.0.1#53
是 AdGuard Home 运行在我本机的 DNS 服务器:
/etc/resolv.conf
前面已提及,当 systemd-resolved
出场想要全面接手本机所有 DNS 查询请求时,它一方面提供自己的 D-Bus API,另一方面还要兼容支持那些习惯性调用 glibc nss-dns
、以及谁也不用就要自己独立解析域名的应用。
为了支持(也可以说是拦截)那些要自己独立发送 DNS 查询请求的应用,可以将
/etc/resolv.conf
实际设置为一个指向/run/systemd/resolve/stub-resolv.conf
的 软链接(symbolic link),将127.0.0.53
作为唯一的 DNS 服务器写入其中,对应 三种接口 的第三种。由此,应用还是会通过systemd-resolved
进行域名解析。也可以将
/etc/resolv.conf
设置为指向静态文件/usr/lib/systemd/resolv.conf
的软链接,类似上面,这个文件也将127.0.0.53
作为唯一的 DNS 服务器,区别在于这个文件是静态的,上面那个/run/systemd/resolve/stub-resolv.conf
是实时动态变化的,且包含 搜索域名(search domain)此外,还可以将
/etc/resolv.conf
设为指向/run/systemd/resolve/resolv.conf
文件的软链接,后面这个文件也是动态变化的,保存的是systemd-resolved
当前已知的系统全局性 DNS 服务器(非 per-link)。这个厉害了,这种模式下,读取/etc/resolv.conf
数据的应用,就终于可以完全避开systemd-resolved
,自己直接向远程上游 DNS 服务器发送查询请求了!最后一种情况,其他软件接管控制了
/etc/resolv.conf
,这种模式下,systemd-resolved
也只好乖乖读取其中的配置信息,听从安排了
如下图所示,在我当前的系统中,/etc/resolv.conf
作为软链接指向 /run/systemd/resolve/resolv.conf
,属于上面第三种模式:
AdGuard Home 也的确通过在我本地设置 DNS 服务器 127.0.0.1#53
,避开了 systemd-resolved
:
参考 3
前面两篇可以相互参照补充着看,写得非常好。内容都是关于 Fedora 启用 systemd-resolved
后,针对 nss-resolve
相比 nss-dns
的优点,特别是支持 per-link DNS 服务器设定这一特点,在 VPN 配置上的应用。
⭐systemd-resolved: introduction to split DNS
很晚才看到这篇,但它其实非常舒适贴切地解答了我好几个关键的疑惑点。对
search domain
和routing domain
、nss-dns
vsnss-resolve
、以及systemd-resolved
与NetworkManager
的关系,有非常清晰的解释。⭐Understanding systemd-resolved, Split DNS, and VPN Configuration
在 VPN 和 split DNS 配置背景下,对
systemd-resolved
的工作机制有很清晰的讲解。据作者讲,文章写作时(2020 年) Ubuntu 还未采纳nss-resolve
,但现在也已支持了。systemd-resolved
官方文档官方文档,示例如何使用 D-Bus API 写应用
网友回复中有厘清
systemd-networkd
与NetworkManager
的关系,以及对应的配置文件所在位置Is systemd-resolved required when running NetworkManager or dhcpcd or systemd-networkd?
重点是直接看回答中,网友对标题罗列的这几个概念做的区分性解释与关系梳理
A name resolution issue with systemd-resolved we found in the wild
避开 systemd-resolved
回到我最开始关心的问题:如何操作可以强制本机所有 DNS 查询全都发往唯一/二自己指定的上游 DNS 服务器,下面这篇帖子的作者问了相似的问题,答案见得分最高的回答:
AdGuard Home 和 Pi-hole
我则是通过稀里糊涂地安装 AdGuard Home 做到的,相当于在本机另外装了一个 DNS 解析器,然后在控制界面手动设置了 DNS 服务器,后来通过 Wireshark 抓包,发现 UDP 数据包确实都发给了指定的上游 DNS 服务器。
安装过程中报了下面的错误,因为 AdGuard Home 的 DNS resolver 要和 systemd-resolved
的 127.0.0.53#53
stub resolver 争用 53 端口,于是要禁用 DNSStubListener
,如文中给出的解决办法所示。可以看出,改动的就是 /ect/systemd/resolved.conf
的配置(通过 drop in 方式),设定 127.0.0.1#53
为全局 DNS 服务器;此外,应用通过调用 nss-dns
或是自己直接访问 /etc/resolv.conf
(/run/systemd/resolve/resolv.conf
的软链接)获取 DNS IP,如前面讲过的,避开了 systemd-resolved
。
注:关于 Wireshark 的安装,中间会弹出一个窗口需要对权限做一个选择,默认建议的是选否,实际应该选 Yes,不然后面没有权限访问网卡数据。此外,还会报一个"couldn't run /usr/bin/dumpcap in child process" 的错,针对这两个问题解决方法分别是:
sudo dpkg-reconfigure wireshark-common #修改权限,选 yes
sudo chmod +x /usr/bin/dumpcap #改为可执行文件,解决报错
关于我的稀里糊涂,一是不懂其中的机制,二是明明有更可爱的 Pi-hole 可选,但我当时不知道!叫 Pi 的怎么都这么可爱,全是可爱派...
BIND
至于 BIND
,它是常用的构建 DNS 服务器的软件,从我安装 AdGuard Home 报的错来看,它应该也用了 BIND
。具体地,可以参考下面的教程,这部分又会呼应上一篇 DNS 服务器的类型问题:
How To Configure Bind as a Caching or Forwarding DNS Server on Ubuntu 14.04
How To Configure BIND as a Private Network DNS Server on Ubuntu 18.04
附 1:命令
dig
和 nslookup
都可以做域名解析,默认使用 /etc/resolv.conf
的设定服务器,在我本机当前的配置下,用的都是 AdGuard Home 中我指定的。也可以以参数的方式在 nslookup
命令中指定某个具体的 DNS 服务器。dig +trace
可以看到 DNS 查询经过的各层级服务器。
dig +trace snownstone.com
参考:
注:顺带记一笔,ping
与它们不同,用的是 ICMP 协议
resolvectl
resolvectl query archlinux.org #域名解析
resolvectl status #查看 global 和 per-link DNS 设定
systemctl
systemctl is-active systemd-resolved #查看系统某服务是否在运行
systemctl status system-resolved.service
netstat
与 lsof
netstat
这个命令可以查看本机当前网络连接相关的数据,如协议类型、端口号、IP、状态等;lsof
则可以列出正在使用或监听端口的程序。
附 2:网络配置
路由器设置
在浏览器中直接输入路由器在局域网内的 IP
192.168.31.1
默认的是启用 DHCP 服务,动态分配 IP 给同一局域网内接入的其他设备,也可以手动设置给机器一个固定的 IP 地址
可以手动设置 DNS 服务器 IP
在路由器中做的配置修改会自动更新其他接入设备内的相关网络设置
Ubuntu 网络配置
服务器版系统用的是 Systemd-networkd
,桌面版用的是 NetworkManager
,同时新的版本开始用 Netplan
(作为前端) 生成 yaml 格式的网络配置文件。
桌面版所有连接过的网络设置信息都保存在 /etc/NetworkManager/system-connections
路径下。
NetworkManager 默认的是 DHCP
配置,由路由器给接入网络的机器动态分配 IP 地址,DNS 服务器默认用的也是路由器的默认设置,通常就是网络运营商的 DNS 服务器。如果在路由器的配置页面(192.168.31.1
)对 DNS 做手动修改,重启生效后,电脑的设置也会自动跟着修改。
AdGuard Home 将我的全局 DNS 服务器设置为了它自己(即 localhost:127.0.0.1#53
),结果就是即使不对路由器的默认配置做手动修改,DNS 查询当前去的也是 AdGuard Home 中指定的服务器。