Linux DNS 查询剖析（第二部分）
在第二部分中，我将介绍 resolv.conf 的更新机制、systemctl restart networking 命令的运行机制 ，以及 dhclient 是如何参与其中。
DNS
在第二部分中，我将介绍 resolv.conf 的更新机制、systemctl restart networking 命令的运行机制 ，以及 dhclient 是如何参与其中。
![](/data/attachment/album/201808/24/232313rb2ozk8zbbb29bb6.jpeg)


在 [Linux DNS 查询剖析 - 第一部分](/article-9943-1.html) 中，我介绍了：


* `nsswitch`
* `/etc/hosts`
* `/etc/resolv.conf`
* `ping` 与 `host` 查询方式对比


并且发现大多数程序选择要查询的 DNS 服务器时会参考 `/etc/resolv.conf` 配置文件。


这种方式在 Linux 上比较普遍<sup id="fnref1"> <a href="#fn1" rel="footnote">  1 </a></sup>。虽然我使用了特定的发行版 Ubuntu，但背后的原理与 Debian 甚至是那些基于 CentOS 的发行版有相通的地方；当然，与更低或更高的 Ubuntu 版本相比，差异还是存在的。


也就是说，接下来，你主机上的行为很可能与我描述的不一致。


在第二部分中，我将介绍 `resolv.conf` 的更新机制、`systemctl restart networking` 命令的运行机制 ，以及 `dhclient` 是如何参与其中。


### 1) 手动更新 /etc/resolv.conf


我们知道 `/etc/resolv.conf` （有极大的可能性）被用到，故你自然可以通过该文件增加一个 `nameserver`，那么主机也将会（与已有的 `nameserver` 一起）使用新加入的 `nameserver` 吧？


你可以尝试如下：



```
$ echo nameserver 10.10.10.10 >> /etc/resolv.conf
```

看上去新的 `nameserver` 已经加入：



```
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 10.0.2.3
search home
nameserver 10.10.10.10
```

但主机网络服务重启后问题出现了：



```
$ systemctl restart networking
$ cat /etc/resolv.conf
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 10.0.2.3
search home
```

我们的 `10.10.10.10` 的 `nameserver` 不见了！


在上一篇文章中我们忽略了这一点，本文进行补充说明。


### 2) resolvconf


你在 `/etc/resolv.conf` 文件中看到 `generated by resolvconf` 词组了吧？这就是我们的线索。


如果深入研究 `systemctl restart networking` 命令，你会发现它做了很多事情，结束时调用了 `/etc/network/if-up.d/000resolvconf` 脚本。在该脚本中，可以发现一次对 `resolvconf` 命令的调用：



```
/sbin/resolvconf -a "${IFACE}.${ADDRFAM}"
```

稍微研究一下 man 手册，发现`-a` 参数允许我们：



```
Add or overwrite the record IFACE.PROG then run the update scripts
if updating is enabled.
```

（增加或覆盖 IFACE.PROG 记录，如果开启更新选项，则运行更新脚本）


故而也许我们可以直接调用该命令增加 `namserver`：



```
echo 'nameserver 10.10.10.10' | /sbin/resolvconf -a enp0s8.inet
```

测试表明确实可以！



```
$ cat /etc/resolv.conf&nbsp; | grep nameserver
nameserver 10.0.2.3
nameserver 10.10.10.10
```

是否已经找到答案，这就是 `/etc/resolv.conf` 更新的逻辑？调用 `resolvconf` 将 `nameserver` 添加到某个地方的数据库，然后（“如果配置了更新”，先不管具体什么含义）更新 `resolv.conf` 文件。


并非如此。



```
$ systemctl restart networking
root@linuxdns1:/etc# cat /etc/resolv.conf 
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 10.0.2.3
search home
```

呃！（网络服务重启后）新增的 `nameserver` 再次消失了。


可见，`systemctl restart networking` 不仅仅运行了 `resolvconf`，还在其它地方获取 `nameserver` 信息。具体是哪里呢？


### 3) ifup/ifdown


继续深入研究 `systemctl restart networking`，发现它完成了一系列工作：



```
cat /lib/systemd/system/networking.service
[...]
[Service]
Type=oneshot
EnvironmentFile=-/etc/default/networking
ExecStartPre=-/bin/sh -c '[ "$CONFIGURE_INTERFACES" != "no" ] && [ -n "$(ifquery --read-environment --list --exclude=lo)" ] && udevadm settle'
ExecStart=/sbin/ifup -a --read-environment
ExecStop=/sbin/ifdown -a --read-environment --exclude=lo
[...]
```

首先，网络服务的重启实质是运行一个<ruby> 单触发 <rt>  oneshot </rt></ruby>的脚本，脚本包含如下命令：



```
/sbin/ifdown -a --read-environment --exclude=lo
/bin/sh -c '[ "$CONFIGURE_INTERFACES" != "no" ] && [ -n "$(ifquery --read-environment --list --exclude=lo)" ] && udevadm settle'
/sbin/ifup -a --read-environment
```

第一行使用 `ifdown` 关闭全部的网络接口，但<ruby> 本地回环 <rt>  local, lo </rt></ruby>接口除外。<sup id="fnref2"> <a href="#fn2" rel="footnote">  2 </a></sup>


（LCTT 译注：其实这是因为很快就又启动了接口，间隔的时间没有超过 TCP 连接的超时时间，有人在评论中也做了类似回复）


第二行用于确认系统已经完成关闭网络接口相关的全部工作，以便下一步使用 `ifup` 启动接口。这也让我们了解到，网络服务实质运行的就是 `ifdown` 和 `ifup`。


文档中没有找到 `--read-environment` 参数的说明，该参数为 `systemctl` 正常工作所需。很多人以文档不完善为由不喜欢 `systemctl`。


很好。那么 `ifup` （和其成对出现的 `ifdown`） 到底做了哪些工作呢？长话短说，它运行了 `/etc/network/if-pre-up.d/` 和 `/etc/network/if-up.d/` 目录下的全部脚本；期间，这些脚本也可能会调用另外的脚本，依此类推。


其中一件工作就是运行了 `dhclient`，但我还不完全确定具体的机理，也许 `udev` 参与其中。


### 4) dhclient


`dhclient` 是一个程序，用于与 DHCP 服务器协商对应网络接口应该使用的 IP 地址的详细信息。同时，它也可以获取可用的 DNS 服务器并将其替换到 `/etc/resolv.conf` 中。


让我们开始跟踪并模拟它的行为，但仅在我实验虚拟机的 `enp0s3` 接口上。事先已经删除 `/etc/resolv.conf` 文件中的 nameserver 配置：



```
$ sed -i '/nameserver.*/d' /run/resolvconf/resolv.conf
$ cat /etc/resolv.conf | grep nameserver
$ dhclient -r enp0s3 && dhclient -v enp0s3
Killed old client process
Internet Systems Consortium DHCP Client 4.3.3
Copyright 2004-2015 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/
Listening on LPF/enp0s8/08:00:27:1c:85:19
Sending on   LPF/enp0s8/08:00:27:1c:85:19
Sending on   Socket/fallback
DHCPDISCOVER on enp0s8 to 255.255.255.255 port 67 interval 3 (xid=0xf2f2513e)
DHCPREQUEST of 172.28.128.3 on enp0s8 to 255.255.255.255 port 67 (xid=0x3e51f2f2)
DHCPOFFER of 172.28.128.3 from 172.28.128.2
DHCPACK of 172.28.128.3 from 172.28.128.2
bound to 172.28.128.3 -- renewal in 519 seconds.

$ cat /etc/resolv.conf | grep nameserver
nameserver 10.0.2.3
```

可见这就是 `nameserver` 的来源。


但稍等一下，命令中的 `/run/resolvconf/resolv.conf` 是哪个文件，不应该是 `/etc/resolv.conf` 吗？


事实上，`/etc/resolv.conf` 并不一定只是一个普通文本文件。


在我的虚拟机上，它是一个软链接，指向位于 `/run/resolvconf` 目录下的“真实文件”。这也暗示了我们，该文件是在系统启动时生成的；同时，这也是该文件注释告诉我们不要直接修改该文件的原因。


（LCTT 译注：在 CentOS 7 中，没有 `resolvconf` 命令，`/etc/resolv.conf` 也不是软链接）


假如上面命令中 `sed` 命令直接处理 `/etc/resolv.conf` 文件，效果是不同的，会有警告消息告知待操作的文件不能是软链接（`sed -i` 无法很好的处理软链接，它只会创建一个新文件）。


（LCTT 译注：CentOS 7 测试时，`sed -i` 命令操作软链接并没有警告，但确实创建了新文件取代软链接）


如果你继续深入查看配置文件 `/etc/dhcp/dhclient.conf` 的 `supersede` 部分，你会发现 `dhclient` 可以覆盖 DHCP 提供的 DNS 服务器。


![linux-dns-2 (2)](/data/attachment/album/201808/24/232326asi1sbyjsylti4s7.png)


*（大致）准确的关系图*




---


### 第二部分的结束语


第二部分到此结束。信不信由你，这是一个某种程度上简化的流程版本，但我尽量保留重要和值得了解的部分，让你不会感到无趣。大部分内容都是围绕实际脚本的运行展开的。


但我们的工作还没有结束，在第三部分，我们会介绍这些之上的更多层次。


让我们简要列出我们已经介绍过的内容：


* `nsswitch`
* `/etc/hosts`
* `/etc/resolv.conf`
* `/run/resolvconf/resolv.conf`
* `systemd` 和网络服务
* `ifup` 和 `ifdown`
* `dhclient`
* `resolvconf`




---


1. 事实上，这是相对于 POSIX 标准的，故不限于 Linux （我从上一篇文章的一条极好的[回复](https://zwischenzugs.com/2018/06/08/anatomy-of-a-linux-dns-lookup-part-i/#comment-2312)中了解到这一点） [↩](#fnref1)
2. 我不明白为何这没有导致我例子中的 vagrant 会话中断 （有谁明白吗？）。 [↩](#fnref2)




---


via: <https://zwischenzugs.com/2018/06/18/anatomy-of-a-linux-dns-lookup-part-ii/>


作者：[ZWISCHENZUGS](https://zwischenzugs.com/)  
 译者：[pinewall](https://github.com/pinewall)  
 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出

