DEV Community

Michael
Michael

Posted on • Edited on

DNS到底怎么运作的

背景

上文讲到Ubuntu中热点遇到的一些问题,涉及到了Ubuntu环境中网络管理的各个组件,总是很模糊。本文主要通过梳理DNS管理来整理Ubuntu中网络管理组件分工,以及DNS管理细节,本文不会涉及DNS协议细节。

环境

system:Ubuntu24.04
libc: glibc2.39
Enter fullscreen mode Exit fullscreen mode

Ubuntu24.04网络管理

Linux环境中有多个网络管理组件,比如Network Manager, systemd-networkd等,不同组件有不同的上下文概念和配置方式,这对于网络管理和配置来说很有挑战性。后面推出了netplan,作为网络管理抽象层,使用YAML文件配置,使用上面列到的组建作为渲染器,一份配置文件适配多种渲染器,目前支持两种渲染器:Network Managersystemd-networkd

Ubuntu上面默认使用前者作为网络管理工具,可以查看man 5 NetworkManager.conf查看相关配置,配置文件位于/etc/NetworkManager/NetworkManager.conf

网络有很多部分,比如无线,有线,DNS, 蓝牙等,NetworkManager使用不同的软件管理这些组件。无线部分使用wpa_supplicant(见wifi.backend), dhcp client端也有多种选择,默认使用internal。

其中DNS配置很有意思,根据manpage文档,DNS管理使用systemd-resolved(/etc/resolv.conf文件是软连接至/run/systemd/resolve/stub-resolv.conf), 也可以设置使用dnsmasq

Ubuntu24.04 DNS管理

DNS基础知识 Cloudflare

DNS基础知识 ruanyifeng

resolvectl作为systemd-resolved的命令行管理工具管理包括DNS等。

systemd-resolved会在本地53端口起个DNS服务,并且将nameserver 127.0.0.53写入/etc/resolv.conf文件,那其他软件DNS查找这个文件,都是访问本地53端口服务(这个成为stub resolver),但是真正的DNS服务server会藏在systemd-resolved的相关配置文件中,可以使用如下命令查看:

resolvectl status
Enter fullscreen mode Exit fullscreen mode

它会列出所有网络设备的resolve信息,比如无线网卡的信息

    Link 3 (wlp4s0)
        Current Scopes: DNS
             Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
    Current DNS Server: 192.168.0.1
           DNS Servers: 192.168.0.1
            DNS Domain: lan
Enter fullscreen mode Exit fullscreen mode

上述的192.168.0.1作为DNS server一般是DHCP分配的,局域网内部的DNS server, 会去请求ISP的DNS server(DNS的迭代模式(Iterate)), 最后会进入递归模式,不断从ROOT(.), TLD nameserver(Top Level Domain)(.com), Authoritative nameserver(baidu.com)获取DNS信息,返回信息有各种所谓的Record(上述是全流程,当然可能存在缓存就不用走这些流程了,不然每次请求都消耗很多时间,另外服务器负载也太大了), 下面是常见的Record

    A - IPV4
    AAAA - IPV6
    NS - Authoritative Name Server
    CNAME - 别名,比如www.baidu.com -> www.shifen.com
    MX - Mail
    TXT - Human readable document
    PTR - reverse DNS lookup, 由ip查域名
Enter fullscreen mode Exit fullscreen mode

查看systemd-resolved的DNS缓存

使用sudo resolvectl statistic可以查看缓存情况,比如缓存个数,命中个数等。如果想查看缓存内容,使用sudo pkill -USR1 systemd-resolve, 然后在syslog中查看 sudo journalctl -u systemd-resolved > ~/resolved.txt注意上面打印的数据是unique的数据,记录中可能有多个相同域名指向多个IP

清空systemd-resolved的DNS缓存

清空所有cache

sudo pkill -USR2 systemd-resolve
# OR
sudo resolvectl flush-caches
sudo resolvectl statistic
Enter fullscreen mode Exit fullscreen mode

glibc的DNS服务nsswitch和ncsd

为什么提到了glibc的DNS服务呢?因为在程序中使用比如函数getaddrinfo进行域名解析时候就使用了glibc提供的域名解析服务。下面梳理glibc的DNS相关流程。

glibc提供了各类名称解析服务,比如用户名->USER ID, 组名->GROUP ID, 域名->IP等,其中使用的模块是NSS(Name Service Switch), 这个不要跟Firefox的NSS(Network Security Services)模块弄混淆了

NSSwitch

可以使用getent database key从数据库中获取NSS支持类型的存储数据, 比如

getent ahosts # get A record host
getent group # get group resolve record
man 5 nss
man 5 nsswitch.conf
Enter fullscreen mode Exit fullscreen mode

getent ahost先使用缓存没有就使用getaddrinfo开始网络请求,详情见man getent

nsswitch的配置文件见/etc/nsswitch.conf,我们关心DNS相关的是hosts

hosts:          files mdns4_minimal [NOTFOUND=return] dns
Enter fullscreen mode Exit fullscreen mode

其中hosts值规定使用DNS查询的顺序,files代表/etc/hosts; mdns4\_minimal使用系统提供的multicast DNS解析服务,如果有这个服务,调用成功但是没有我们要的结果,就返回,如果没有这个服务,就使用系统提供的DNS服务查询(/etc/resolv.conf)。
使用nsswitch查询DNS整个过程,可以通过strace查看到上述描述细节

sudo strace -e trace=open,openat,connect -f ping -c1 www.baidu.com
     openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
     openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
     openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libidn2.so.0", O_RDONLY|O_CLOEXEC) = 3
     openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
     openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libunistring.so.5", O_RDONLY|O_CLOEXEC) = 3
     openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
     openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache", O_RDONLY|O_CLOEXEC) = 5
# nscd缓存
     connect(5, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (没有那个文件或目录)
     connect(5, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (没有那个文件或目录)
# 使用了getaddrinfo, 所以会调用nsswitch查询dns
     openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 5
# hosts中配置files第一顺序
     openat(AT_FDCWD, "/etc/host.conf", O_RDONLY|O_CLOEXEC) = 5
     openat(AT_FDCWD, "/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 5
     openat(AT_FDCWD, "/etc/hosts", O_RDONLY|O_CLOEXEC) = 5
     openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 5
# 使用multicast DNS本地局域网所有example.local机器咨询DNS, 没有这个机器服务,下一个dns系统查询
     openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libnss_mdns4_minimal.so.2", O_RDONLY|O_CLOEXEC) = 5
# hosts配置中的dns, 使用系统提供的dns服务, 即systemd-resolved
     connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 0
     openat(AT_FDCWD, "/etc/gai.conf", O_RDONLY|O_CLOEXEC) = 5
     ...
     connect(5, {sa_family=AF_INET, sin_port=htons(1025), sin_addr=inet_addr("183.2.172.42")}, 16) = 0
     PING www.a.shifen.com (183.2.172.42) 56(84) bytes of data.
     openat(AT_FDCWD, "/etc/hosts", O_RDONLY|O_CLOEXEC) = 5
     connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 0
     64 bytes from 183.2.172.42: icmp_seq=1 ttl=51 time=25.3 ms

     --- www.a.shifen.com ping statistics ---
     1 packets transmitted, 1 received, 0% packet loss, time 0ms
     rtt min/avg/max/mdev = 25.305/25.305/25.305/0.000 ms
     +++ exited with 0 +++
Enter fullscreen mode Exit fullscreen mode

其中可以修改/etc/nsswitch.conf中的hosts: file dns就不会有multicast DNS请求了, 修改是自动刷新的

NOTICE:上述strace监听openat系统调用,open可能已经没有用了

ncsd

其中nscd(Name Service Cache Daemon)是全局各种解析服务的缓存,数据库中存储了各种解析类型映射的缓存。nscd manpage


网上看到这篇文章总结的不错。
注意,/etc/nscd.conf文件注释必须是首个字符为#,不然会服务可能启动失败。
主要提下调试,有两种方式,一种是使用日志文件,一种是在终端打印日志查看

  1. 使用日志文件 修改/etc/nscd.conf中日志相关项, logfile(/var/log/nscd.log)debug-file(0-5, 可以选择5), 然后重启nscd, 就可以查看(tail -f /var/log/nscd.log), 当ping或者getent hosts就会更新日志。
  2. 终端查看 先暂停sudo systemctl stop nscd, 然后sudo nscd -g, 就会在终端打印日志了,但是也要设置日志等级。

总结

使用getent, ping这些命令都会走glibc提供的DNS服务,底层使用getaddrinfo函数; 其他命令如host(man 1 host), dig, nslookup走的是全局DNS解析服务,但是也可以自己配置DNS服务器直接网络请求。查看manpage, 会发现都是BIND 9提供的命令,在找BIND 9你就会发现他们为什么不走glibc的了 :)

编程语言DNS服务

目前有很多库提供DNS服务,比如libevent, c-ares

很多语言如果底层使用使用glibc的函数也是走NSS那套,比如python,java等,但是也有的语言提供独立的DNS服务,比如自举后的golang。

NodeJS底层使用c-ares提供DNS服务。
libcurl可以使用自己提供DNS server,但是需要编译时候添加c-ares库; 默认走glibc那套。

Nodejs

Nodejs文档真心不错,最后说明了两种方式的特点和注意点。
默认情况(使用dns.lookup()),使用getaddrinfo走系统那套查询方式; 也可以使用 dns.resolve()dns.resolve*(), and dns.reverse(),底层使用c-ares,直接网络请求DNS服务器获取解析结果。

小结

又整理了一篇无用知识,发现strace真是个好工具,解决问题都要想到她。

Top comments (0)