通常情况下,在 Linux 中可以用来配置 DNS 地址的文件有两个:
- 解析配置文件
/etc/resolv.conf
,文件中的注释行可以采用#
或者;
开头 - 网卡
ifcfg
配置文件/etc/sysconfig/network-scripts/ifcfg-eth*
resolv.conf 的配置参数
通过查看 man手册页,会看到有很多相关的参数,这里只说比较常用的几个
nameserver
这个参数会指定系统使用的 DNS 的 IP 地址,在生产环境中,经常会看到 /etc/resolv.conf
中配置了一个或多个 nameserver
。配置了多个 nameserver
后,系统使用这些 DNS 的顺序并未进行轮询、随机、或者均衡调度,并且系统总是使用第一个 nameserver
对应的地址来进行 DNS 的查询。
1 | [root@bogon ~]# cat /etc/resolv.conf |
解析的同时,使用 tcpdump
命令分析通信数据,从结果中可以看到,使用 dig 对三个域名的解析只用到了第一个 nameserver
的地址,即 8.8.8.8
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
换成一次解析一个域名
1 | [root@bogon ~]# dig +short +tries=1 www.baidu.com |
结果同上,也是只用到了第一个 nameserver
的地址,即 8.8.8.8
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
如果第一个 nameserver
对应的地址是不可用的 IP ,域名的解析均是从第一个 nameserver
对用的地址进行查询的,系统会按照 nameserver
设定的顺序从上往下进行顺序尝试,当无法获取结果后将尝试使用下一个,并且系统不会记录任何一个 nameserver
的工作状态。
1 | [root@bogon ~]# cat /etc/resolv.conf |
1 | [root@bogon ~]# dig +short +tries=1 www.redhat.com www.centos.org www.kernel.org |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
如果有多个 nameserver
不可用时,对于 dig
和 ping
命令而言生效的只有前三个,当前三个 nameserver
都不可用时,不会再向其余的 nameserver
请求解析。对于 curl
命令来说生效的是全部,依次从上向下轮询,直到找到能有所响应的为止。
1 | [root@bogon ~]# cat /etc/resolv.conf |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
从上面的结果可以看到,dig
在请求解析每个域名时,都会向前三个 nameserver
请求
1 | [root@bogon ~]# cat /etc/resolv.conf |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth1 udp port 53 |
domain
1 | domain <Local domain name> |
这个参数用于系统在进行 DNS 查询时,如果请求的域名无法正常解析时自动补充 domain
的参数值。
当请求解析的内容中含有域时,即无论系统请求哪一个主机名时,系统默认都会先加上根域 .
,但是如果不包含域,则会将 domain
中设定的域填充进去。这是官方文档中的一段描述:
1 | the domain part is taken to be everything after the first '.'. |
当请求解析的内容不包含有域时,不管请求的内容是不是合法的,系统都直接填充 domain
参数。
1 | [root@bogon ~]# cat /etc/resolv.conf |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
1 | [root@bogon ~]# ping -c 1 baidu |
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
请求解析的内容包含有域时,不管请求的内容是不是合法的,系统都会先直接解析而不是直接填充 domain
参数
1 | [root@bogon ~]# cat /etc/resolv.conf |
- 请求解析的内容包含有域,且该域有对应的 A 记录
1 | [root@bogon ~]# ping -c1 baidu.com |
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
- 请求解析的内容包含有域,但该域不合法或者没有对应的A记录
1 | [root@bogon ~]# ping -c1 baidu123.com |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
search
1 | search <Search list for host-name lookup> |
指定一组域名(用空格分割),当仅写出主机名时,就开始依次进行这些域名的匹配,如果第一个域名没有匹配到,那么用第二个进行匹配,以此类推,直到匹配完所有列出的域名,给出最终的结果。
1 | [root@bogon ~]# cat /etc/resolv.conf |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
1 | [root@bogon ~]# ping -c 1 -W 1 -q x1y2z3 |
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
options
用来设定一些选项,实现不同的功能
timeout
可选选项,系统一次 DNS 解析 timeout 的时间值,单位为秒。系统默认值为 5,最大可以设定的值是 30
1 | timeout:n |
下面对于 timeout 的实验我们都在 /etc/resolv.conf
中配置无效的 DNS
在查看了 dig
命令的帮助手册之后发现它也有个 timeout 参数 +time=T
,在不指定该参数的情况下默认为 5 秒,那么我们 不在 resolv.conf
中配置 timeout
来进行测试看一下:
1 | [root@bogon ~]# cat /etc/resolv.conf |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
为了便于区分,上述内容人工填充了空行。从上面可以看到
dig
向三个nameserver
分别请求解析,向每个nameserver
请求解析的时间间隔为 1 秒而不是 2 秒dig
在五次对文件中的nameserver
轮询,每次轮询之间的时间间隔为 2 秒
如果我们在 /etc/resolv.conf
中配置了 timeout
,到底是以 dig
的为准还是以 /etc/resolv.conf
中的为准?
- 当
dig
的 timeout 大于resolv.conf
中的 timeout 时
1 | [root@bogon ~]# cat /etc/resolv.conf |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
dig 没有指定 timeout 参数则使用默认值 5 秒
1 | [root@bogon ~]# dig +tries=5 +short www.baidu.com |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
- 当
dig
的 timeout 小于resolv.conf
中的 timeout 时
1 | [root@bogon ~]# cat /etc/resolv.conf |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
多次测试后发现:
- 对于
dig
来说,自身的 timeout 和/etc/resolv.conf
二者的 timeout 都指的是每次轮询完文件nameserver
的超时时间,而不是每个nameserver
的超时时间 - 而对于
ping
来说,自身的 timeout 指的是每个包的超时时间,/etc/resolv.conf
的 timeout 指的是每个nameserver
的超时时间,不是每次轮询的时间 - 只要
/etc/resolv.conf
配置了 timeout 就直接忽略dig
的 timeout ,以/etc/resolv.conf
中配置的为准;否则就以dig
设定的(未设定则使用默认值)为准
attempts
可选选项,系统尝试解析的次数,如果未设定则默认值是 2。系统允许最大值是 5,即当系统请求解析失败多少次之后,才会给系统或者程序返回解析失败的结果。对于 dig
命令来说,文件中配置的 attempts
如果是一个小数,则可能会提示语法错误,如果是 0 则不会进行解析。
1 | attempts:n |
因为 dig
命令也有个尝试次数的参数 +tries=T
,默认是请求三次。不管 /etc/resolv.conf
有没有配置 attempts
,在使用 dig
的时候都会以 dig
的为准。这里我们要对 /etc/resolv.conf
文件中的配置做实验,就使用 ping
来测试。
1 | [root@bogon ~]# cat /etc/resolv.conf |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
上面的抓包结果看上去有些费解。我们先算一下时间,已知 /etc/resolv.conf
中的 timeout
对于 ping
来说指的是每个 nameserver
的超时时间,我们设置了 3 个无效的 DNS ,超时时间为 3 ,因此尝试(轮询)一次的时间为 3 x 3 =9
, 两次尝试应该是 18 s 才对,为什么看到的却是 36 呢?这是因为 ping
在进行一次解析的时候会做两个不同的记录的询问请求,一个是 A 记录,另一个是 PTR 记录。当然 PTR 记录是得到 A 记录之后再做的,如果没有得到 A 记录就会再请求一次 A 记录。
我们不妨设置两个 DNS,第一个无效,第二个设为有效的来看看:
1 | [root@bogon ~]# cat /etc/resolv.conf |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth0 icmp or udp port 53 |
分析:
1 | 18:11:43.480899 IP 124.160.121.68.60040 > 123.45.67.1.53: 29894+ A? www.baidu.com. (31) |
向第一个 DNS
开始第一次尝试,做第一件事:询问 A 记录
1 | 18:11:46.483955 IP 124.160.121.68.57979 > 8.8.8.8.53: 29894+ A? www.baidu.com. (31) |
第一个 DNS
无效,在等待超时时间 options timeout:3
也就是 3 秒之后,向第二个 DNS 第一次尝试,做第一件事:询问 A 记录
1 | 18:11:46.579031 IP 8.8.8.8.53 > 124.160.121.68.57979: 29894 3/0/0 CNAME www.a.shifen.com., A 61.135.169.125, A 61.135.169.121 (90) |
第二个 DNS
有效,并返回了 A 记录 ,立刻发送了一个 ICMP 包并且得到了响应
1 | 18:11:46.607865 IP 124.160.121.68.36510 > 123.45.67.1.53: 16127+ PTR? 125.169.135.61.in-addr.arpa. (45) |
得到百度的响应之后并没有继续发送剩余的 ICMP 报文,而是又向第一个 DNS 请求了 PTR 记录的解析,注意这里并不是进行了第二次尝试,而是在做第一次尝试中的第二件事情:询问 PTR 记录
1 | 18:11:49.610912 IP 124.160.121.68.53473 > 8.8.8.8.53: 16127+ PTR? 125.169.135.61.in-addr.arpa. (45) |
第一个 DNS
无效,在等待超时时间 options timeout:3
也就是 3 秒之后,向第二个DNS 请求做第一次尝试中的第二件事情:询问 PTR 记录
1 | 18:11:49.662472 IP 124.160.121.68 > 61.135.169.125: ICMP echo request, id 14864, seq 2, length 64 |
发送剩余的两个 ICMP 报文
我们再算一下时间:
两个 DNS ,每个 DNS 都进行了一次尝试,做了两个不同的事情(询问A记录,再询问 PTR 记录),第一个 DNS 尝试一次的过程中超时两次时间是 6 秒,第二个 DNS 未超时,总共加起来是 7 秒左右
那么有没有可能是 PTR 记录是 ping 进行了第二次错误解析尝试呢(一次错误等待时长为 3 s,所以 attemps 设置为2,等待时长是 3s * 2 = 6s,正好与 time ping 的执行结果相吻合)
为了更有说服力,我们把尝试次数改为一次,前两个 DNS 设置为无效,第三个 DNS 有效,再做一次实验
1 | [root@bogon ~]# cat /etc/resolv.conf |
我们假设 PTR 记录是 ping 进行了第二次错误解析尝试,而文件 /etc/resolv.conf
中我们只允许尝试一次,那么根据假设,等待的时间应该是 第一个 DNS 超时时间 3s
加上 第二个 DNS 超时时间 3s
,总共为 6s
,是不是这样呢?
1 | [root@bogon ~]# time ping -c 1 www.baidu.com |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth0 icmp or udp port 53 |
从上述结果可以看到,ping
再做一次尝试的过程中存在了两种不同记录的查询,A 记录和 PTR 记录,因此 PTR 记录是 ping 进行了第二次错误解析尝试
的假设不成立。因此对于 ping
来说一次尝试的超时时间应该是 (PTR的超时时间 + A 记录的超时时间) * 无法解析的 nameserver 的个数
即 6 x 2 =12
如果是下面的配置,使用 ping 的话超时时间就应该是 一次尝试:6 x 3 =18
,两次尝试 36s
,第一个实验中令人费解的时间此时已经有了来路
1 | [root@bogon ~]# cat /etc/resolv.conf |
rotate
为了避免 DNS 查询每次都从第一个 nameserver
开始,来均衡各个 nameserver
的压力。如果第一个 nameserver
失效时,使用这个选项就可以提高解析的效率。另外 rotate
可能会优先使用可用的 DNS。要注意很多云主机上都有自己分配好的 DNS ,如果此时用了 rotate
将会在一定程度上影响效率,因为对于云主机来说,自家的 DNS 肯定比公共的响应要快。
我们取消options rotate 参数。这样既保证了 aws 上使用自己的快速 DNS,也保证了在他自己平台上一旦DNS 失效,咱们全部的 AWS 业务可以使用 公共 DNS 进行解析。
参数如下:
options timeout:1
options attempts:2
options single-request-reopen
1 | [root@bogon ~]# cat /etc/resolv.conf |
抓包结果
1 | [root@bogon ~]# tcpdump -nn -i eth0 udp port 53 |
single-request-reopen
1 | options single-request-reopen |
自从 CentOS6 之后,准确的说是 glibc >= 2.9 之后,Linux 系统 DNS 解析器(resolver)会使用相同的 socket 去请求 A(for IPv4) & AAAA(for IPv6) 记录解析。比如在存在防火墙等机制的网络环境中,同样源目的 ip,同样源目的 port,同样的第4层协议的连接,会被防火墙看成是同一个会话,因此会存在返回包被丢弃现象,过程如下:
默认的 DNS 解析过程是这样的:
- 主机从一个随机的源端口(图中为 12345 ),请求 DNS 的 A 记录
- 主机从同一个源端口,请求 DNS 的 AAAA 记录
- 主机先收到 DNS 返回的 AAAA 记录(也可能先收到 A 记录)
- 防火墙(可能是其他硬件设备,官方给出的描述是: Some hardware )认为本次交互通信已经完成,关闭连接
- 剩下的 DNS 服务器返回的 A 记录响应包被防火墙丢弃
如果我们启用参数 single-request-reopen
(默认未启用),一旦出现同一个 socket 发送的两次请求处理,解析端发送第一次请求后会关闭 socket,并在发送第二次请求前打开新的 socket 对 DNS server 端进行发送解析请求
生产环境中发现在阿里云的主机上可能会出现上述问题,因此如果主机使用了 IPv6 建议启用该参数。
edns0
1 | options edns0 |
EDNS0:Extension Mechanisms for DNS Version 0,是 DNS 在 rfc1035 基础上对 DNS 协议的扩展。
修改原来的 DNS 协议,让其可以传输超过 512 字节的报文限制,但是必须客户端和服务端同时支持 edns0 才能使用该协议。glibc 2.6 及以上可以支持。
1 | [root@bogon ~]# dig @8.8.8.8 www.baidu.com |
总结和建议
当第一个 nameser
解析超过指定的超时后会向第二个 nameserver
请求解析,此时第一个 nameserver
的 socket
已经关闭,于是不存在这种情况:虽然第一个 nameserver 已经超时了,系统在向第二个 nameserver
请求解析时,就会有可能这时第一个 nameserver
将解析结果返回给系统了
文件 /etc/resolv.conf
中参数的有效性,准确的说应该视系统命令或应用程序而定的。例如:
- 对于
ping
和dig
来说他们会最多轮询三个nameserver
,但是curl
则是轮询所有的nameserver
- 当
attempts
为小数时,nslookup
和dig
会提示该文件语法错误 - 当
attmepts
为 0 时,对于ping
来说则不会进行 DNS 解析请求,但对于dig
和nslookup
来说依然发送 DNS 解析请求 - 一旦设定
timeout
参数值,系统会直接忽略掉dig
自身的timeout
为了避免单个 DNS 不可用时导致解析瘫痪,最好配置两个或多个 nameserver
,一般最好配置三个,适当调整 timeout
和 attempts
提高解析效率。
配置示例
1 | options timeout:1 |
什么会修改 resolv.conf
在维护的主机数量较多的时候,系统中 DNS 的配置我们应该做到统一化,随意修改或更新会造成管理混乱。比如统一只靠人为干预的方式来修改 /etc/resolv.conf
,并将其他可能会更新该文件的服务或配置禁用掉。因此我们需要找到可能会更新 /etc/resolv.conf
的来源:
- 用户:这个不必多说了,有权限的用户都可以配置该文件
- 服务或程序
网卡配置文件中的 DNS
网卡配置参考文档
在 Redhat 官方文档中可以找到相关说明
1 | DNS{1,2}=address |
where address is a name server address to be placed in /etc/resolv.conf provided that the PEERDNS directive is not set to no.
1 | PEERDNS=<answer> |
where <answer>
is one of the following:
yes
— Modify/etc/resolv.conf
if the DNS directive is set. If usingDHCP
, then yes is the default.no
— Do not modify/etc/resolv.conf
.
To configure an interface to use particular DNS servers, add the following lines to the ifcfg file:
1 | PEERDNS=no |
where ip-address is the address of a DNS server. This will cause the network service to update /etc/resolv.conf
with the specified DNS servers specified. Only one DNS server address is necessary, the other is optional.
By default, NetworkManager calls the DHCP
client, dhclient, when a profile has been set to obtain addresses automatically by setting BOOTPROTO
to dhcp
in an interface configuration file. If DHCP
is required, an instance of dhclient is started for every Internet protocol, IPv4 and IPv6, on an interface. If NetworkManager is not running, or is not managing an interface, then the legacy network service will call instances of dhclient as required.
Important
In order to apply the configuration, you need to enter the nmcli c reload command.
PEERDNS=yes
:如果网卡配置文件中设置了 DNS 就会修改 /etc/resolv.conf
。在网卡配置文件中如果启动方式配置了 DHCP
之后,PEERDNS
默认为 yes
我们分 DHCP 和 静态方式 来测试
DHCP方式
单独配置 PEERDNS
我们先人为给系统配置公共的DNS,并且网卡配置自动获取
1 | [root@bogon ~]# cat /etc/resolv.conf |
启动方式配置了 DHCP
之后我们知道 PEERDNS
默认为 yes
,当重启了网卡之后,发现/etc/resolv.conf
确实被修改了,我们再加一条配置:PEERDNS=no
来看看
1 | [root@bogon ~]# cat /etc/resolv.conf |
此时发现虽然配置了 PEERDNS=no
,当重启了网卡之后/etc/resolv.conf
还是被修改了,但是少了一个 DNS 并且两次实验后文件中都会有个注释:# Generated by NetworkManager
,查看进程后发现系统运行了一个服务 NetworkManager
为了排除其因素我们将此服务停止再次测试
1 | systemctl stop NetworkManager |
先不配置 PEERDNS
选项,即默认PEERDNS=yes
1 | [root@bogon ~]# systemctl stop NetworkManager |
配置 PEERDNS
选项,即PEERDNS=no
1 | [root@bogon ~]# cat /etc/resolv.conf |
结论:
- 在
PEERDNS=yes
的情况下,会生成一个nameserver 192.168.127.2
的配置,会更改/etc/resolv.conf
- 在
PEERDNS=no
的情况下,不会生成一个nameserver 192.168.127.2
的配置,不会更改/etc/resolv.conf
那么在 PEERDNS=yes
的情况下生成的 nameserver 192.168.127.2
到底是哪的呢?在查看了 DHCP 服务器的配置后发现获得的这个地址是 DHCP 服务器分配给客户端的 DNS 地址
PEERDNS & DNS{1,2}=address
为了排除掉 NetworkManager
的因素我们将此服务停止
PEERDNS=yes
& DNS{1,2}=address
1 | [root@bogon ~]# cat /etc/resolv.conf |
PEERDNS=no
& DNS{1,2}=address
1 | [root@bogon ~]# cat /etc/resolv.conf |
静态指定方式
单独配置 PEERDNS
PEERDNS=yes
1 | [[root@bogon ~]# cat /etc/resolv.conf |
PEERDNS=no
1 | [root@bogon ~]# cat /etc/resolv.conf |
PEERDNS & DNS{1,2}=address
PEERDNS=yes
& DNS{1,2}=address
1 | [root@bogon ~]# cat /etc/resolv.conf |
PEERDNS=no
& DNS{1,2}=address
1 | [root@bogon ~]# cat /etc/resolv.conf |
总结
BOOTPROTO="dhcp"
的情况:
PEERDNS=yes
:网卡配置文件中配置了DNS{1,2}=address
,直接将address
更新到/etc/resolv.conf
,不会去从 DHCP 服务器获取 DNS,如果没有配置DNS{1,2}=address
就会尝试从 DHCP 服务器获得 DNS 并更新到/etc/resolv.conf
PEERDNS=no
:无论网卡配置文件中是否配置DNS{1,2}=address
,都不会更改/etc/resolv.conf
,也不会去从 DHCP 服务器获取 DNS
BOOTPROTO="static"
的情况:
PEERDNS=yes
:网卡配置文件中配置了DNS{1,2}=address
,直接将address
更新到/etc/resolv.conf
,不会去从 DHCP 服务器获取 DNS,如果没有配置DNS{1,2}=address
则不更改/etc/resolv.conf
PEERDNS=no
:无论网卡配置文件中是否配置DNS{1,2}=address
,都不会更改/etc/resolv.conf
,也不会去从 DHCP 服务器获取 DNS
NetworkManager 中的 DNS
NetworkManager 是用于便携式计算机和其他可移动计算机的理想解决方案。提供了完善而直观的用户界面,可使用户轻松地切换其网络环境。也就是说 NetworkManager 更适用于桌面环境的 PC 电脑,服务器上一般都是命令行界面,我们完全可以不需要使用 NetworkManager ,而且 NetworkManager 服务和 network 服务有可能会起冲突,因此我们可以将此服务禁掉。
我们先假设由于某种依赖关系的原因必须启用 NetworkManager
服务,测试看看它是否会更改系统的 DNS 。
为了排除其他因素,我们在网卡配置文件中删除 DNS{1,2}=address
相关的配置,并添加 PEERDNS=no
,然后启用 NetworkManager 服务
1 | systemctl start NetworkManager |
BOOTPROTO="dhcp"
1 | [root@bogon ~]# cat /etc/resolv.conf |
BOOTPROTO="static"
1 | [root@bogon ~]# cat /etc/resolv.conf |
BOOTPROTO="dhcp"
+ DNS{1,2}=address
1 | [root@bogon ~]# cat /etc/resolv.conf |
BOOTPROTO="static"
+ DNS{1,2}=address
1 | [root@bogon ~]# cat /etc/resolv.conf |
通过测试发现,默认情况下 NetworkManager
服务运行起来,如果改了网络配置就会更新系统的 /etc/resolv.conf
,如果我们的目的只是修改一下 IP 地址,重启完网卡后却更新了系统的 DNS ,这显然不符合我们的预期。那么有没有什么办法可以避免这种问题发生呢?
查看了 NetworkManager
的服务配置文件 /etc/NetworkManager/NetworkManager.conf
之后,给出了 See "man 5 NetworkManager.conf" for details.
的提示,通过查看 man 手册发现了与 DNS 相关的配置:
1 | dns |
那么我们将处理模式改为 none
并重启 NetworkManager
服务再次进行测试
1 | [root@bogon ~]# cat /etc/NetworkManager/NetworkManager.conf |
修改了模式之后重启服务仍然会修改系统 DNS,是配置的参数没生效吗?我们将系统的 DNS 改回去再试一次
1 | [root@bogon ~]# cat /etc/resolv.conf |
这说明配置的参数可以阻止 NetworkManager
服务修改 /etc/resolv.conf
DNS配置优化
为了避免其他来源覆盖本机的 /etc/resoolv.conf
,可以从以下几处来进行优化:
- 网卡配置文件中删除
DNS{1,2}=address
相关的配置,并添加PEERDNS=no
- 网卡配置文件中添加
NM_CONTROLLED=no
来避免network.service
和NetworkManager.service
冲突 - 修改
NetworkManager
的处理模式,在/etc/NetworkManager/NetworkManager.conf
中添加dns=none
- 如果没有必要的话,则停止
NetworkManager.service
,并禁止开机自启动