前段时间蜜罐捕获到了一个攻击事件,黑客修改了蜜罐的 /etc/resolv.conf 文件中的内容以此操控相关的 DNS 解析行为,在 Linux 系统下,DNS 的解析也是一个复杂的过程。结合上次的事件,本文就简单介绍一下 Linux 系统下的 DNS 解析过程(以 CentOS 系统为例),如有错误,欢迎指正。

此处不讨论浏览器、递归解析等因素。

相关文件介绍

在 Linux 系统下,一个域名的解析,是受到多个文件、多个流程去完成的,也是多个文件的配合过程。在系统中,有这几个关键文件: /etc/nsswitch.conf/etc/hosts/etc/resolv.conf/etc/host.conf ;实际情况下,网卡的配置文件 /etc/sysconfig/network-scripts/ifcfg-xxxx 中的 DNS 字段可能也会对 DNS 的解析过程有一些影响,下面就简单分析一下上述几个文件的作用。

nsswitch.conf 文件

在Linux系统下, /etc/nsswitch.conf 文件,即(name service switch configuration,名字服务切换配置)规定通过哪些途径以及按照什么顺序通过这些途径来查找特定类型的信息。还可以指定某个方法奏效或失效时系统将采取什么动作。

需要提供 nsswitch.conf 文件所描述的信息的时候,系统将检查含有适当 info 字段的配置行。它按照从左向右的顺序开始执行配置行中指定的方法。在默认情况下,如果找到期望的信息,系统将停止搜索。如果没有指定 action,那么当某个方法未能返回结果时,系统就会尝试下一个动作。有可能搜索结束都没有找到想要的信息。

在这个文件里,我们主要关注是如下 hosts 的一行:

…………

#hosts:     db files nisplus nis dns
hosts:      files dns myhostname

…………

对于上述信息 hosts 字段的配置行,下面列出了该文件控制搜索信息类型的方法,对于每一种信息类型,都可以指定下面的一种或多种方法:

Info字段方法说明
files搜索本地文件,如/etc/passwd/etc/hosts等。
nis搜索NIS数据库,nis还有一个别名,即yp。
dns查询DNS(只查询主机)。
compatpasswd、group和shadow文件中的±语法。

根据上述表格,可知 nsswitch.conf 文件说明了 DNS 查找将首先调查 /etc/hosts 文件来解析域名,然后查看 /etc/resolv.conf 文件配置来解析主机名。这也说明了文件 /etc/hosts 优先级大于 /etc/resolv.conf 的。

请注意:DNS 查找指令(如 nslookup 和 dig )将忽略 /etc/nsswitch.conf 文件,并始终使用 /etc/resolv.conf 引用DNS服务器。下面会说明。

关于最后一个 myhostname,它一般出现在该文件 hosts: 字段的末尾,以确保优先使用传统的基于 DNS 与 /etc/hosts 文件的解析方法。只有当前面两个都解析不通时,myhostname 就起作用了,它可以把 hostname,解析成自己范围内的 IP 地址。

我们可以通过下面命令查看 hostname 对应的 IP 地址:

[root@localhost ~]# getent ahosts `hostname`
::1             STREAM localhost
::1             DGRAM  
::1             RAW    
127.0.0.1       STREAM 
127.0.0.1       DGRAM  
127.0.0.1       RAW    
[root@localhost ~]# 
注:修改 nsswitch.conf 中的选项顺序不影响已运行的程序的解析顺序。

hosts 文件

这个文件想必大家都很熟悉,它和 Windows 系统目录的 C:\Windows\System32\drivers\etc\hosts 文件相类似,也是最早用于实现“域名”这个概念的文件。

它是一个没有扩展名的系统文件,其基本作用就是将一些常用的主机名、主机名别名或域名与其对应的 IP 地址建立一个关联“数据库”。它的配置也很简单,直接在里面写上名称与 IP 的对应映射列表即可,比如我们最熟悉的 localhost 在该文件中就被映射指向 127.0.0.1 回本地址:

[root@localhost ~]# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
[root@localhost ~]# 
注:修改此文件立即生效。运行的程序也会受此影响,长连接除外。

resolv.conf 文件

这是 Linux 操作系统中的另一个重要文件,是 resolver 类库使用的配置文件, 它帮助计算机将域名转换为 IP 地址的信息。每当一个程序需要通过域名来访问 internet 上面的其它主机时,需要利用该类库将域名转换成对应的 IP,然后才可进行访问。

它的格式很简单,每行以一个关键字开头,后接一个或多个由空格隔开的参数。它的关键字主要有四个,说明如下:

  • nameserver:它的作用即定义 DNS 服务器的 IP 地址,可以配置多个 nameserver 指定多个 DNS 地址。
  • search:定义域名的搜索列表,中间用空格或 tab 键隔开。当要查询没有域名的主机,主机将在由 search 声明的域中分别查找。

    这么说可能不太理解,通过如下的一个示例演示一下:

    [root@localhost ~]# ping www
    ping: www: Name or service not known             # 此处找不到www域
    [root@localhost ~]#
    [root@localhost ~]# vim /etc/resolv.conf 
    [root@localhost ~]# cat /etc/resolv.conf 
    # Generated by NetworkManager
    search baidu.com                                 # 新增了一个搜索域
    nameserver 119.29.29.29
    nameserver 223.5.5.5
    [root@localhost ~]#
    [root@localhost ~]# ping -c 1 www                # www在搜索域中查找,其查找方式就变成了www.baidu.com
    PING www.a.shifen.com (36.152.44.96) 56(84) bytes of data.
    64 bytes from 36.152.44.96 (36.152.44.96): icmp_seq=1 ttl=49 time=22.8 ms
    
    --- www.a.shifen.com ping statistics ---
    1 packets transmitted, 1 received, 0% packet loss, time 0ms
    rtt min/avg/max/mdev = 22.876/22.876/22.876/0.000 ms
    [root@localhost ~]#
    [root@localhost ~]# ping -c 1 www.baidu.com      # 对照上述结果
    PING www.a.shifen.com (36.152.44.96) 56(84) bytes of data.
    64 bytes from 36.152.44.96 (36.152.44.96): icmp_seq=1 ttl=49 time=21.5 ms
    
    --- www.a.shifen.com ping statistics ---
    1 packets transmitted, 1 received, 0% packet loss, time 0ms
    rtt min/avg/max/mdev = 21.542/21.542/21.542/0.000 ms
    [root@localhost ~]# 

    原来,当访问的域名不能被 DNS 解析时,resolver 会将该域名加上 search 指定的参数,重新请求 DNS,直到被正确解析或试完 search 指定的列表为止。

  • domain:定义本地域名。当为没有域名的主机进行 DNS 查询时,也要用到。如果没有域名,主机名将被使用,删除所有在第一个点( .)前面的内容。

    [root@localhost ~]# hostname
    localhost.localdomain
    [root@localhost ~]# 

    domain 和 search 不能共存,如果同时存在,后面出现的关键字将会被使用。

  • sortlist:允许将得到域名结果进行特定的排序。它的参数为网络/掩码对,允许任意的排列顺序。

其中,上述最主要是 nameserver 关键字,如果没指定 nameserver 就找不到 DNS 服务器,其它关键字是可选的

nameserver 表示解析域名时使用该地址指定的主机为域名服务器。其中域名服务器是按照文件中出现的顺序来查询的,且只有当第一个 nameserver 没有反应时才查询下面的 nameserver。

Red Hat 中没有提供缺省的 /etc/resolv.conf 文件,它的内容是根据在安装时给出的选项动态创建的。下面会说明。

host.conf 文件

这是解析器查询顺序配置文件。功能和 /etc/nsswitch.conf 文件中的 hosts 字段类似,指定相关主机名的查找方法。

它每行含一个配置关键字,其后跟着合适的配置信息,其系统识别的关键字如下:

  • order:这个关键字确定了主机查询是如何执行的。它后面应该跟随一个或者更多的查询方式,这些查询方式用逗号分隔,有效的方式有:bindhostsnis
  • trim:这个关键字可以多次出现。每次出现其后应该跟随单个的以句点开头的域名。如果设置了它,resolv+ 库会自动截去任何通过 DNS 解析出来的主机名后面的域名。这个选项用于本地主机和域。(相关信息: trim 对于通过 NIS 或者 hosts 文件获取的主机名无效。需要注意的是要确保在 hosts 文件中的每条记录的第一个主机名是全名或者非全,以适合于本地安装。)
  • multi:有效的值为: on 和 off。如果设置为 on,resolv+ 库会返回一台主机在 /etc/hosts 文件中出现的的所有有效地址,而不只是第一个。这个标志对 DNS 或 NIS 请求是没有作用的。
  • nospoof:有效的值为:on 和 off。如果设置为 on,resolv+ 库会尝试阻止主机名欺骗以提高使用 rlogin 和 rsh 的安全性。它是如下这样工作的:DNS 通过使用 in-addr.arpa 域,允许你找到属于一个IP地址的主机名。名字服务器提供一个假主机名的企图被称为“哄骗( spoofing )”。为了防止这个做法,解析器在执行了一个主机地址的查询之后,resolv+ 会对该地址执行一次主机名的查询。如果两者不匹配,查询即失败。
  • spoofalert:如果该选项设为 on 同时也设置了 nospoof 选项,resolv+ 会通过 syslog 设施记录错误报警信息。
  • reorder:有效的值为:on 和 off。如果设置为 on, resolv+ 会试图重新排列主机地址,以便执行 gethostbyname(3) 时,首先列出本地地址(即在同一子网中的地址)。重新排序适合于所有查询方式。

    [root@localhost ~]# cat /etc/host.conf 
    multi on
    [root@localhost ~]# 

ifcfg-xxxx 文件

这个里面的文件就比较特殊,它存放的是网卡配置相关的文件。例如众所周知的 ifcfg-eth0,则默认表示第一个网卡。以 ifcfg-eth0 文件配置为例,它存放了网卡的相关行为信息,除了可以设置网卡的 IP 信息外,还能设置 DNS 服务器,如下所示:

……
DEVICE="eth0"
ONBOOT="yes"
IPADDR="192.168.0.189"
PREFIX="24"
GATEWAY="192.168.0.1"
DNS1="119.29.29.29"
DNS2="223.5.5.5"
……

网卡的配置,也间接影响了 /etc/resolv.conf 文件,当在 eth接口 启用 DHCP 后,本地 /etc/resolv.conf 文件将被修改,此时 /etc/resolv.conf 文件中的 DNS 地址将被改为从 DHCP 获取到的地址。这种从 DHCP 获得的 DNS 又称 Peer DNS。即使这个时候你修改了 /etc/resolv.conf 文件中的 nameserver 字段,可能在下次网络重启就又恢复成了原样。

这也说明了上述在介绍 /etc/resolv.conf 文件时的结尾处,Red Hat中没有提供缺省的 /etc/resolv.conf 文件,它的内容是在系统安装时根据网卡的配置动态创建的。

也就是在系统安装过程中,若你的网络启用的为 DHCP(默认),那么 /etc/resolv.conf 文件中的 nameserver 即 DHCP 提供的 DNS 地址;若你使用静态的 IP 地址,那么此时则需要配置 DNS 解析地址,这时候配置的 DNS 解析地址就会被写入到 /etc/resolv.conf 文件的 nameserver 字段中,配置了多少个 DNS 就有多少个 nameserver。

这儿若使用 DHCP 服务,且不让 /etc/resolv.conf 文件中自定义的 nameserver 字段恢复默认,可在网卡配置文件中增加一条 PEERDNS=no 参数,然后重启网络即可。

nsswitch.conf 与 host.conf 区别

根据上述相关文件的介绍,我们可以看出 /etc/nsswitch.conf/etc/host.conf 文件在域解析过程中都存在可以指定解析顺序方式,若两者指定的解析顺序不同,系统又如何去解析域呢?或者系统该使用哪个文件去解析域呢?

其实,在有关 /etc/host.conf 的帮助文档中也说明了,/etc/nsswitch.conf 是控制主机查找顺序的现代方式。如下图:

image-20220906212031999

同时,若 host.conf 配置了 order 行字段,那么该 order 行仅由旧版本的 C 库使用,且现在的主流 Linux 上,host.conf 的配置也基本为 multi on 字段。

在域解析的过程中,Linux 上并没有一个单独的方法去完成域查询工作,没有一个有这样的明确接口的核心,即 Linux 并没有名为“DNS 查询”的系统调用,那么 Linux 系统又是如何完成域解析的?

Linux 处理域解析

在 Linux 系统中,绝大多数程序依赖系统库函数来完成域解析,整个解析过程包含多个操作,有些操作信息在启动程序时确定,有些操作信息则在程序运行时确认,Linux 也提供了一些网络函数(由 glibc 提供)来控制这些操作,下图所示为一个比较典型的应用程序,域名解析及域名服务器之间的关系图:

dns_que1

程序在运行后通过 glibc 提供的网络函数( gethostbynamegetaddrinfo 等)来调用解析器( resolver code,比如 nsswitch 等), 解析器则读取一些配置文件(比如 /etc/nsswitch.conf/etc/hosts/etc/resolv.conf)来决定使用什么域名服务器(nameserver)以什么选项(超时时间,重试次数),什么优先级(先 hosts 还是先 dns)对指定的域名进行处理。当然也可以使用 dns 缓存类的工具加速请求的处理,这些工具可以是 bind(当缓存用),nscd(由 glibc 提供),systemd-resolved(由 systemd 提供)。

函数说明

glibc 的解析器(revolver code) 提供了 gethostbynamegetaddrinfo 两个函数实现域名称到 IP 地址的解析。gethostbyname 函数以同步阻塞的方式提供服务,没有超时等选项,仅提供 IPv4 的解析。getaddrinfo 则没有这些限制,同时支持 IPv4、IPv6,也支持 IPv4 到 IPv6 的映射选项。包含 Linux 在内的很多系统都已废弃 gethostbyname 函数,使用 getaddrinfo 函数代替,但也不是所有程序或应用都使用该函数!

在 Linux 手册中,对上述两个函数进行查找发现,gethostbyname 函数会依赖 nsswitch.conf 文件(不是使用了这个文件就是该函数在使用),如下图:

image-20220920093533366

getaddrinfo 函数说明如下:

image-20220920094037325

下面通过常用的两个网络命令来对自己的域解析一下:

  • Ping:
[root@localhost ~]# ping -c 1 www.isisy.com
PING nm.aicdn.com (36.248.208.247) 56(84) bytes of data.
64 bytes from 36.248.208.247 (36.248.208.247): icmp_seq=1 ttl=128 time=24.9 ms

--- nm.aicdn.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 24.945/24.945/24.945/0.000 ms
[root@localhost ~]# 
  • host:
[root@localhost ~]# host www.isisy.com
www.isisy.com is an alias for blog-yjs.b0.aicdn.com.
blog-yjs.b0.aicdn.com is an alias for nm.aicdn.com.
nm.aicdn.com has address 36.248.208.247
[root@localhost ~]# 

由上可见,对于同一个域名,两个命令得到的 IP 地址是相同的;那么它们是使用同样的方式得到结果吗?

调用跟踪

上述两个不同的命令得到的 IP 是相同的,此处可能会认为他们是通过相同的方法得到的 IP,然而事实却并非如此,例如我们使用 strace 命令跟踪一下这两个命令的系统调用,先来看下 ping 命令的 DNS 相关的系统调用:

[root@localhost ~]# strace -e trace=open -f ping -c 1 www.isisy.com
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libidn.so.11", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcrypto.so.10", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libresolv.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libz.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/etc/pki/tls/legacy-settings", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 4
open("/etc/host.conf", O_RDONLY|O_CLOEXEC) = 4
open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 4
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 4
open("/lib64/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 4
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 4
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 4
open("/lib64/libnss_dns.so.2", O_RDONLY|O_CLOEXEC) = 4
PING nm.aicdn.com (36.248.208.247) 56(84) bytes of data.
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 4
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 4
open("/lib64/libnss_myhostname.so.2", O_RDONLY|O_CLOEXEC) = 4
open("/lib64/librt.so.1", O_RDONLY|O_CLOEXEC) = 4
open("/lib64/libdw.so.1", O_RDONLY|O_CLOEXEC) = 4
open("/lib64/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 4
open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 4
open("/usr/lib64/elfutils/tls/x86_64/libelf.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/usr/lib64/elfutils/tls/libelf.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/usr/lib64/elfutils/x86_64/libelf.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/usr/lib64/elfutils/libelf.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib64/libelf.so.1", O_RDONLY|O_CLOEXEC) = 4
open("/usr/lib64/elfutils/liblzma.so.5", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib64/liblzma.so.5", O_RDONLY|O_CLOEXEC) = 4
open("/usr/lib64/elfutils/libbz2.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib64/libbz2.so.1", O_RDONLY|O_CLOEXEC) = 4
64 bytes from 36.248.208.247 (36.248.208.247): icmp_seq=1 ttl=128 time=23.3 ms

--- nm.aicdn.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 23.329/23.329/23.329/0.000 ms
+++ exited with 0 +++
[root@localhost ~]# 

可以看到整个过程依次调用了 libresolv 等动态库,再访问 nsswitch.conf 以确定解析是先访问 hosts 文件还是先通过 dns server。而 host.conf 包含解析器的配置,系统中默认为 multi on,表示 libresolv 会返回解析到的所有 IP(单域名可能对应多条 A 记录)。resolv.conf 包含指定的 nameserver,解析器会连接这些 nameserver 获取域名对应的 A 记录。后面的 /etc/hosts/lib64/libnss_dns.so 则表示先访问 hosts 文件,然后再发起 DNS 到 nameserver 的请求。如果我们绑定了域名到 /etc/hosts,则会忽略 /lib64/libnss_dns.so 的步骤;如果 nsswitch.conf 中 DNS 优先,则先通过 /lib64/libnss_dns.so 来请求,找到记录则忽略 /etc/hosts 步骤。

由上面的 open("/lib64/libnss_dns.so.2", O_RDONLY|O_CLOEXEC) = 4 下面的一行给出了 PING 结果可知,最终是通过连接 resolv.conf 指定的 dns server 获取到了域名对应的 A 记录 36.248.2xx.xx7,接下来程序再通过该 A 记录再发起进行功能性的操作请求。综上,由此可知 ping 命令是通过配置文件 /etc/nsswitch.conf 的相关指定来完成选择。

那么 host 命令也是如此吗?下面我们依旧来跟踪一下看看:

[root@localhost ~]# strace -e trace=open -f host www.isisy.com
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/tls/x86_64/libdns.so.1102", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib64/tls/libdns.so.1102", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib64/x86_64/libdns.so.1102", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib64/libdns.so.1102", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/tls/liblwres.so.160", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib64/liblwres.so.160", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/tls/libbind9.so.160", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib64/libbind9.so.160", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/tls/libisccfg.so.160", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib64/libisccfg.so.160", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/tls/libisc.so.169", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib64/libisc.so.169", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libgssapi_krb5.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libkrb5.so.3", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libk5crypto.so.3", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcom_err.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcrypto.so.10", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libidn.so.11", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libGeoIP.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libxml2.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libz.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libkrb5support.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libkeyutils.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libresolv.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/liblzma.so.5", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpcre.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/proc/filesystems", O_RDONLY)     = 3
open("/etc/pki/tls/legacy-settings", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en_US.UTF-8/libdns.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en_US.UTF-8/LC_MESSAGES/libdns.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en/libdns.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en/LC_MESSAGES/libdns.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en_US.UTF-8/libisc.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en_US.UTF-8/LC_MESSAGES/libisc.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en/libisc.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en/LC_MESSAGES/libisc.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
strace: Process 8474 attached
[pid  8473] open("/proc/self/task/8474/comm", O_RDWR) = 3
strace: Process 8475 attached
[pid  8473] open("/proc/self/task/8475/comm", O_RDWR) = 3
strace: Process 8476 attached
[pid  8473] open("/proc/self/task/8476/comm", O_RDWR) = 6
[pid  8473] open("/usr/share/locale/en_US.UTF-8/libdst.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid  8473] open("/usr/share/locale/en_US.UTF-8/LC_MESSAGES/libdst.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid  8473] open("/usr/share/locale/en/libdst.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid  8473] open("/usr/share/locale/en/LC_MESSAGES/libdst.cat", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid  8473] open("/etc/pki/tls/openssl.cnf", O_RDONLY) = 6
[pid  8473] open("/etc/resolv.conf", O_RDONLY) = 6
[pid  8473] open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 6
[pid  8474] open("/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 7
[pid  8474] open("/usr/share/locale/en_US.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid  8474] open("/usr/share/locale/en_US.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid  8474] open("/usr/share/locale/en_US/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid  8474] open("/usr/share/locale/en.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid  8474] open("/usr/share/locale/en.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid  8474] open("/usr/share/locale/en/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
www.isisy.com is an alias for blog-yjs.b0.aicdn.com.
blog-yjs.b0.aicdn.com is an alias for nm.aicdn.com.
nm.aicdn.com has address 36.248.208.247
[pid  8473] --- SIGTERM {si_signo=SIGTERM, si_code=SI_TKILL, si_pid=8473, si_uid=0} ---
[pid  8474] +++ exited with 0 +++
[pid  8476] +++ exited with 0 +++
[pid  8475] +++ exited with 0 +++
+++ exited with 0 +++
[root@localhost ~]# 

这个过程和上述的 ping 命令又不相同了,可见它一开始依次调用了 libdns.solibbind9.so 等库,但是该命令的调用过程中并没有去访问 nsswitch.conf,而是去访问 resolv.conf 以确定解析,最终通过 resolv.conf 包含指定的
nameserver 完成获取域名对应的 A 记录并进行接下来的功能性操作。可见 host 命令是通过配置文件 /etc/resolv.conf 的指定来完成相关选择。

而上述在 /etc/nsswitch.conf 配置文件介绍中说明的DNS查找指令(nglookup 和 dig)将忽略 /etc/nsswitch.conf 文件,并始终使用 /etc/resolv.conf 引用 DNS 服务器,也即是该情况。若有兴趣的话,读者可自主跟踪下这些命令的调用观察一下。

对比修改 .conf 文件

由于上述两个命令的跟踪调用过程中都使用到了 /etc/resolv.conf 文件,ping 命令使用到了 /etc/nsswitch.conf 文件,那么我们对这两个文件进行修改来对比操作。

修改 nsswitch.conf,hosts 行仅保留 files

[root@localhost ~]# vim /etc/nsswitch
[root@localhost ~]# cat /etc/nsswitch.conf
……
hosts:      files
……
[root@localhost ~]#

此时我们使用两个命令观察一下结果:

  • Ping:
[root@localhost ~]# ping -c 1 www.isisy.com
ping: www.isisy.com: Name or service not known
[root@localhost ~]# 
[root@localhost ~]# ping -c 1 localhost
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.024 ms

--- localhost ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.024/0.024/0.024/0.000 ms
[root@localhost ~]# 
  • host
[root@localhost ~]# host www.isisy.com
www.isisy.com is an alias for blog-yjs.b0.aicdn.com.
blog-yjs.b0.aicdn.com is an alias for nm.aicdn.com.
nm.aicdn.com has address 36.248.208.247
[root@localhost ~]# 

此时, ping 无法获取 www.isisy.com 对应的 IP 地址,但 localhost 的解析不受影响;此外,host 命令则是正常返回,这也和上述所分析,host 命令不受 nsswitch.conf 影响。

修改 nsswitch.conf,hosts 行仅保留 dns

[root@localhost ~]# vim /etc/nsswitch
[root@localhost ~]# cat /etc/nsswitch.conf
……
hosts:      dns
……
[root@localhost ~]#

此时我们使用两个命令观察一下结果:

  • Ping
[root@localhost ~]# ping -c 1 www.isisy.com
PING nm.aicdn.com (36.248.208.247) 56(84) bytes of data.
64 bytes from 36.248.208.247 (36.248.208.247): icmp_seq=1 ttl=128 time=24.1 ms

--- nm.aicdn.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 24.155/24.155/24.155/0.000 ms
[root@localhost ~]# 
[root@localhost ~]# ping -c 1 localhost
ping: localhost: Name or service not known
[root@localhost ~]# 
  • host
[root@localhost ~]# host www.isisy.com
www.isisy.com is an alias for blog-yjs.b0.aicdn.com.
blog-yjs.b0.aicdn.com is an alias for nm.aicdn.com.
nm.aicdn.com has address 36.248.208.247
[root@localhost ~]# 

此时, ping 无法获取 localhost 对应的 IP 地址,但 www.isisy.com 的解析不受影响;此外,host 命令则还是正常返回。

此时,我们将 /etc/nsswitch.conf 文件恢复成默认状态,对 /etc/resolv.conf 文件进行修改。

修改 resolv.conf,删除所有 nameserver

[root@localhost ~]# vim /etc/resolv.conf 
[root@localhost ~]# cat /etc/resolv.conf 
# Generated by NetworkManager
#nameserver 223.5.5.5
#nameserver 119.29.29.29
[root@localhost ~]# 

此时我们使用两个命令观察一下结果:

  • Ping:
[root@localhost ~]# ping -c 1 www.isisy.com
ping: www.isisy.com: Name or service not known
[root@localhost ~]# 
[root@localhost ~]# ping -c 1 localhost
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.021 ms

--- localhost ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.021/0.021/0.021/0.000 ms
[root@localhost ~]# 
  • host:
[root@localhost ~]# host www.isisy.com
;; connection timed out; no servers could be reached
[root@localhost ~]# 

此时,ping 命令无法获取 www.isisy.com 的 IP,但 localhost 的解析依旧不受影响;反观 host 命令,则无法获取 www.isisy.com 的 IP 了。

总结

由上述相关文件介绍以及 Ping 命令和 host 命令的跟踪调用,说明 Linux 系统中并不存在“DNS 查询”这个系统调用,而是依赖系统库函数来完成解析,且不同程序可能采用不同的策略获取域对应的 IP 地址,例如 ping 使用了 nsswitchhost使用 /etc/resolv.conf 得到解析结果,且绝大部分程序都依赖并使用到了 /etc/resolv.conf 文件。

下面对 NSSwitchhosts 行对应的查询逻辑总结如图:

NSSwitch

参考文章

Linux DNS 查询剖析(第一部分)

linux-系统如何处理名称解析

nsswitch.conf versus host.conf

gethostbyname(3) — Linux manual page

getaddrinfo(3) — Linux manual page

以及 Linux 相关文献,Red hat 相关文献和互联网其它资源借鉴。

End

本文标题:Linux下的DNS解析流程

本文链接:https://www.isisy.com/1332.html

除非另有说明,本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

声明:转载请注明文章来源。

最后修改:2022 年 09 月 21 日
如果觉得我的文章对你有用,请随意赞赏