DNS三两事: 寻找最佳DNS及正确设置DNS服务器(Ubuntu)

烧掉书的方法不止一种,而且世界上充满了揣着火柴到处跑的人。

——《華氏451度》美/雷·道格拉斯·布萊伯利

虽然中国大陆地区的整体网络建设在全球还未处于领先地位,不过我们现在家用网络的速度已经足以满足我们的日常生活所需了.但是,你有没有遇到过这样的情况:同一网络覆盖下不同设备(系统)延迟却有所不同?

如果有过这样的问题出现,你或许应该考虑寻找并更换一个最佳DNS作为你的首选DNS服务器了.

127.0.0.53

如果你运行nslookup查看DNS服务器地址的时候看见了127.0.0.53,那么恭喜你!

这是一个魔幻的IP,代表你的Linux DNS是被systemd-resolvd服务自动修改的,每次重新启动nameserver都会被改回127.0.0.53,即便你修改/etc/resolv.conf明确制定DNS服务器地址也无能为力,此时你有两种解决办法:

  1. 使用unbound替代systemd-resolvd服务

    1
    2
    3
    4
    5
    sudo apt install unbound
    sudo systemctl stop systemd-resolvd
    sudo systemctl disable systemd-resolvd
    sudo rm -rf /etc/resolv.conf
    sudo vim /etc/NetworkManager/NetworkManager.conf

    main 下面添加 dns=unbound 将dns服务替换为 unbound .重启电脑后 nameserver 自动配置并Generated by NetworkManager.

    此后在图形界面中对nameserver做出的修改就可以在 /etc/resolv.conf 中保存下来.

  2. 修改systemd-resolv的设置

    打开并修改/etc/systemd/resolved.conf.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [Resolve]
    DNS=223.5.5.5
    #FallbackDNS=
    #Domains=
    LLMNR=no
    #MulticastDNS=no
    #DNSSEC=no
    #Cache=yes
    #DNSStubListener=yes

    DNS=设置的是域名解析服务器的IP地址

    LLMNR=设置的是禁止运行LLMNR(Link-Local Multicast Name Resolution),否则systemd-resolve服务会监听5535端口。

    然后重新启动服务.

    1
    2
    sudo systemctl restart systemd-resolved
    `

最佳DNS的实践

名称 首选 DNS 服务器 备用 DNS 服务器
114 DNS 114.114.114.114 114.114.115.115
阿里 AliDNS 223.5.5.5 223.6.6.6
百度 BaiduDNS 180.76.76.76 -
DNSPod DNS+ 119.29.29.29 182.254.116.116
CNNIC SDNS 1.2.4.8 210.2.4.8
oneDNS 117.50.11.11 52.80.66.66
DNS 派 电信/移动/铁通 101.226.4.6 218.30.118.6
DNS 派 联通 123.125.81.6 140.207.198.6
Cloudflare DNS 1.1.1.1 1.0.0.1
Google DNS 8.8.8.8 8.8.4.4
IBM Quad9 9.9.9.9 -
DNS.SB 185.222.222.222 185.184.222.222
OpenDNS 208.67.222.222 208.67.220.220
V2EX DNS 199.91.73.222 178.79.131.110

踩坑: 在大陆地区Google,Cloudflare的连接异常,你懂得.114声名狼藉,劫持和广告问题频出,BAT的DNS都很不错,结合CDN分发全国覆盖都很不错,美中不足是协议中没有声明访问数据的使用授权,换句话说,商业使用和二次查询都在法律允许范围内.

如果你在高校和科研院所,使用本地DNS会提供更多的功能.比如说清华DNS(101.6.6.6)会提供对抗DNS污染等功能,让你网络访问更加流畅.(当然前提是你在内网环境中).

最佳实践需要我们多动手,mtr工具用起来.

  • mtr看你当前链路和DNS服务器之间的通信距离和稳定性,链接不上的当然不能使用
  • 更改到一个DNS之后,使用nslookup工具查看DNS服务器中对常用网站的解析.解析多的自然会避免你进一步访问上级递归DNS服务器增加延迟
  • 更改到一个DNS后,使用mtr工具或tracert工具查看链路情况,一个好的DNS服务器提供一个优秀的链路往往有利于改进网络性能

systemd-resolved.service

既然提到了”万恶的”systemd-resolved.service,通过资料简单说说systemd-resolved.service是什么.

名称

systemd-resolved.service, systemd-resolved — 网络名字解析服务

描述

systemd-resolved 为本地应用程序提供了网络名字解析服务。 它不但提供了传统的 DNS/DNSSEC 解析与本地缓存功能,还提供了 LLMNR 与 MulticastDNS 的解析(resolver)与应答(responder)的功能。 本地应用程序可以通过三种方式提交网络名字解析请求:

  • 第一种,通过D-Bus总线上的本地全功能API systemd-resolved (详见 API Documentation)。 这是首选方法,因为它是异步的并且功能最全。 此种方式可以正确返回 DNSSEC 的有效状态,以及支持 link-local 网络所必需的地址的网口范围(interface scope)。

  • 第二种,通过 glibc 的 getaddrinfo(3), gethostbyname(3) 等相关API(RFC3493)。 这些API受到了广泛的支持(包括非Linux平台)。此种方法不能检查 DNSSEC 的有效状态,并且是同步的。 此种方法由 glibc Name Service Switch (nss(5)) 支持。 必须使用 glibc NSS 模块 nss-resolve(8) 才能让 glibc NSS 使用 systemd-resolved 提供的名字解析功能。

  • 第三种,通过 systemd-resolved 在本地回环网口 127.0.0.53 上提供的本地DNS服务器。 应用程序可以直接向 127.0.0.53 发送DNS请求,从而直接使用 systemd-resolved 提供的解析服务。 除非确实无法使用前面的 glibc NSS 或 D-Bus API 两种方法, 否则应该尽量避免使用此种方式, 因为无法将各种网络解析功能(例如 link-local 地址或 LLMNR Unicode 域名)全部映射到 单播DNS协议中。

DNS服务器来自于 全局配置文件(/etc/systemd/resolved.conf)、 针对单个连接的静态配置文件(/etc/systemd/network/*.network)(当使用 systemd-networkd.service(8) 管理网络时)、 针对单个连接的动态配置(从DHCP服务器、resolvectl(1)、其他系统服务得到的DNS服务器)。参见 resolved.conf(5) 与 systemd.network(5) 以了解 systemd 自身的DNS服务器配置。为了提高兼容性, 仅在 /etc/resolv.conf 不是一个 指向 /run/systemd/resolve/stub-resolv.conf, /usr/lib/systemd/resolv.conf, /run/systemd/resolve/resolv.conf 之一的软连接 的情况下,才会从 /etc/resolv.conf 读取 全局DNS服务器。

合成记录(Synthetic Record)

systemd-resolved 会为下列特殊域名合成DNS资源记录(RR):

  • 本机的主机名将按如下规则解析: 如果配置了本机IP地址, 那么将被解析为所有本机IP地址(按范围排序)。 如果不存在本机IP地址, 那么将被解析为本地回环上的 127.0.0.2 与 ::1

  • “localhost” 与 “localhost.localdomain” 以及所有以 “.localhost” 或 “.localhost.localdomain” 结尾的主机名, 都将被解析为 127.0.0.1 与 ::1

  • “_gateway” 将被解析为所有当前路由表中的默认网关IP地址(按数字大小排序)。 通过给网关分配一个固定的名称, 方便了应用程序对网关地址的引用, 应用程序不再需要关心网络的具体配置。

  • 在 /etc/hosts 中定义的映射关系(正向解析与反向解析)。 不过需要注意的是, 这些映射关系对非IP地址类解析(例如MX记录)无效。

协议与路由

在将查询请求路由到DNS服务器、LLMNR 与 MulticastDNS 接口时, 遵守下面的规则:

  • 对 “localhost” 系列特殊域名的查询 永远不会路由到网络上去。

  • 单标签主机名(不含”.”的主机名)使用LLMNR协议路由到所有支持IP多播的本地接口。 对 IPv4 地址的查询只通过 LLMNR 在 IPv4 上查询;对 IPv6 地址的查询只通过 LLMNR 在 IPv6 上查询。 对本机的主机名、”_gateway” 以及 /etc/hosts 中定义的域名的查询 永远不会路由到 LLMNR 接口。

  • 带有 “.local” 后缀的多标签主机名(含有”.”的主机名)使用多播DNS(MulticastDNS)协议 路由到所有支持IP多播的本地接口。 与 LLMNR 一样,IPv4 地址只在 IPv4 上查询;IPv6 地址只在 IPv6 上查询。

  • 其他多标签主机名(含有”.”的主机名)使用DNS协议路由到所有配置了DNS服务器的本地接口。 如果配置了全局DNS服务器,那么还会路由到全局DNS服务器。从 link-local 地址范围发起的查询永远不会路由到DNS服务器。 默认情况下,除非 “.local” 被显式的指定为DNS服务器和网络接口的路由域或搜索域, 否则不会将带有 “.local” 后缀的主机名路由到DNS服务器。 这意味着,如果在特定网络环境中的DNS服务器中定义了 “.local” 域, 那么就必须显式的配置搜索域或路由域,以确保可以正常在此DNS域中进行查找。 注意,应该避免在DNS服务器中定义 “.local” 域, 因为 RFC6762 已将此域专门保留给 多播DNS(MulticastDNS)使用。

如果查询被发送到了多个接口, 那么将只返回第一个成功的应答。 如果所有接口上的查询全部失败, 那么将只返回最后一个失败的应答。

针对特定网络接口配置的域名以及其他一些设置都会影响对域名查询的路由。详见 systemd.network(5) 与 resolvectl(1) 手册。 单播DNS查询遵守如下路由逻辑:

  • 如果查询的名称匹配(等于或具有后缀) 任何链接上的域(搜索域或路由域)、或全局DNS配置中的域, 那么,匹配到最多标签的域(搜索域或路由域),将被视为”最佳匹配域”。 查询将被发送到与”最佳匹配域”关联的所有DNS服务器(包括来自针对每个链接的DNS服务器以及全局DNS服务器)。 注意,可能有多个连接都配置了”最佳匹配域”, 在这种情况下,查询将被同时发送给这些连接。

  • 如果查询的名称不匹配任何已配置的域(既不匹配任何链接上的域、也不匹配全局DNS配置中的域), 那么将会被发送到设置了”DNS默认路由”选项的连接(可能有多个这样的连接)上的DNS服务器、 以及全局配置的DNS服务器。

  • 如果既没有在任何连接上设置”DNS默认路由”选项,也没有配置全局DNS服务器, 那么将使用编译时内置的替补DNS服务器。

  • 否则查询失败,因为无法确定合适的DNS服务器。

“DNS默认路由”选项是一个布尔值,可以使用 resolvectl 配置, 也可在 .network 文件中配置。若未设置,则隐含的基于为链接配置的DNS域推断: 如果存在任何路由域(不匹配 “~.”),那么默认为 no , 否则默认为 yes

从效果上看,这意味着,为了更好的将未显式匹配任何域的DNS查询路由到指定的连接, 可以在该连接上配置一个 “.” 路由域。 这样就可以确保不会使用其他连接(除非也带有这样的路由域)。 为了确保仅在没有其他更合适连接的情况下,才会将所有此类DNS查询路由到指定的链接, 应该将该连接的”DNS默认路由”选项设为 yes , 并且确保那个连接上没有 “.” 路由域。 最后,为了确保特定的连接永远不会收到任何与其配置的域(搜索域与路由域)不匹配的DNS流量, 应该将该连接的”DNS默认路由”选项设为 no 。

参见 resolved D-Bus API Documentation 以了解 systemd-resolved 所提供的编程接口。

/etc/resolv.conf

有四种处理 /etc/resolv.conf 文件(参见 resolv.conf(5)) 的方式:

  • systemd-resolved 实时更新 /run/systemd/resolve/stub-resolv.conf 文件以确保兼容传统的 Linux 程序。 将软连接 /etc/resolv.conf 指向该文件。该文件将 127.0.0.53 设为唯一的 DNS 服务器,并包含 systemd-resolved 使用的搜索域列表。 搜索域列表将会始终保持实时更新。注意,应用程序不应该直接使用 /run/systemd/resolve/stub-resolv.conf 文件, 而应该继续使用 /etc/resolv.conf 文件(指向它的软连接)。 这样,未使用本地 D-Bus DNS API 的客户端,既可以与 systemd-resolved 通信、又可以正确使用搜索域。 这是推荐的首选方式。

  • 一个静态 /usr/lib/systemd/resolv.conf 文件, 此文件仅包含一个唯一的 127.0.0.53 DNS服务器。将软连接 /etc/resolv.conf 指向该静态文件。这样,未使用本地 D-Bus DNS API 的客户端,也可以与 systemd-resolved 通信。此文件不包含任何搜索域。

  • systemd-resolved 实时更新 /run/systemd/resolve/resolv.conf 文件以确保兼容传统的 Linux 程序。 将软连接 /etc/resolv.conf 指向该文件。 注意,此文件只包含所有已知的全局DNS服务器, 而不包含针对特定网络接口设置的DNS服务器。注意,应用程序不应该直接使用 /run/systemd/resolve/resolv.conf 文件, 而应该继续使用 /etc/resolv.conf 文件(指向它的软连接)。 这样,未使用本地 D-Bus DNS API 的客户端,也将同时绕开 systemd-resolved 服务, 直接与已知的全局DNS服务器通信。

  • 由其他软件包或系统管理员维护 /etc/resolv.conf 的内容。 在这种情况下, systemd-resolved 将会从中读取全局DNS配置。也就是说, systemd-resolved 只是一个 /etc/resolv.conf 文件的使用者, 而非此文件的提供者。

注意,上述四种处理方式是自动感知的(不需要特别的配置),完全取决于 /etc/resolv.conf 是否为软连接, 以及该软连接指向的目标。

信号

SIGUSR1

让 systemd-resolved 将所有的DNS资源记录缓存以及DNS服务器特性(例如是否支持DNSSCE) 转储到系统日志中。

SIGUSR2

让 systemd-resolved 刷新所有缓存。 因为 systemd-resolved 会在网络配置发生变化时自动刷新缓存,所以,除非出于调试目的,否则一般不需要发送此信号。 虽然发送此信号相当于执行 resolvectl --flush-caches 命令, 但是仍然建议使用命令而不是发送此信号,因为命令是以同步方式执行的。

SIGRTMIN+1

让 systemd-resolved 忘记所有已缓存的DNS服务器特性,特别是DNS服务器对于各种技术标准的支持。 这样,在执行下一次DNS查询的时候,将会重新检测DNS服务器的各项特性。 因为 systemd-resolved 会在DNS配置发生变化时自动刷新缓存的DNS服务器特性, 所以,除非出于调试目的,否则一般不需要发送此信号。虽然发送此信号相当于执行 resolvectl --reset-server-features 命令, 但是仍然建议使用命令而不是发送此信号,因为命令是以同步方式执行的。

EDNS0

在我们修改/etc/resolv.conf文件的时候可能会遇到options edns0,那么什么是EDNS0,为什么会有EDNS0?

随着业务的复杂化和多样化,RFC1035中定义的DNS消息格式和它支持的消息内容已经不足以满足一些DNS服务器的需求,于是,RFC2671中提出了一种扩展DNS机制EDNS(Extension Mechanisms for DNS),并在其中推荐了一种传递包大小的EDNS0。

RFC2671中指出EDNS被提出来的几个理由:

  • DNS协议头部的第二个16字节中都已经被用的差不多了,需要添加新的返回类型(RCODE)和标记(FLAGS)来支持其他需求;

  • 只为标示domain类型的标签分配了两位,现在已经用掉了两位(00标示字符串类型,11表示压缩类型),后面如果有更多的标签类型则无法支持;

  • 当初DNS协议中设计的用UDP包传输时包大小限制为512字节,现在很多主机已经具备重组大数据包的能力,所以要有一种机制来允许DNS请求方通知DNS服务器让其返回大包;

DNSSEC机制和edns-client-subnet机制等都需要有EDNS的支持。

Reference

  1. systemd-resolved.service 中文手册 - 译者:金步国
  2. Extension mechanisms for DNS - Wikipedia