ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

一文搞懂DNS:协议原理、解析流程与实战排查

一文搞懂DNS:协议原理、解析流程与实战排查 我们每天都在用浏览器访问网站但很少人会想你在地址栏敲下example.com回车之后数据到底是怎么找到那台服务器的中间没有一个人为服务器记住这个域名对应的 IP 地址而这就是 DNS 协议在背后做的事。DNSDomain Name System是互联网最基础也最容易被忽略的协议之一它负责把人类可读的域名翻译成机器可读的 IP 地址是整个网络世界的“总机”。如果你是一名开发者、运维工程师或者只是经常被“DNS 解析失败”“网站打不开”折磨的普通用户这篇文章值得你花十分钟读完。我会从协议原理讲到报文格式从解析流程讲到实战排查最后再分享一些我踩过的 DNS 坑。你会发现搞懂 DNS 不仅能帮你快速定位问题还能让你在设计系统时少走很多弯路。1. DNS 是什么从“电话簿”到全局命名系统1.1 为什么需要 DNS简单说网络通信需要 IP 地址但人记不住一堆 192.0.2.1 这样的数字。DNS 就是那本把“名字”映射到“号码”的电话簿。没有它你访问网站就得手动记 IP一旦服务器迁移、负载均衡切换IP 变了用户就彻底找不到你了。不过 DNS 比电话簿复杂得多。它是一个分布式的、层级化的数据库全球有成千上万台 DNS 服务器协同工作。设计这种架构的原因很朴素单点服务器扛不住全网流量也不可能让一份数据同步到全世界所有机器上。所以 DNS 把命名空间划分为根域、顶级域、二级域、子域每一层都有对应的权威服务器来回答自己管辖范围内的查询。这里容易产生一个误解很多人以为“DNS 服务器”就是运营商给的 114.114.114.114 或 8.8.8.8。其实那只是递归解析器真正负责提供最终答案的是一整条链路里的不同角色。理解这一点是排查 DNS 问题的第一步。1.2 DNS 的层级结构与查询角色整个 DNS 体系可以分成三层角色客户端Stub Resolver就是你电脑上、手机里、浏览器内部那个负责发起解析请求的组件。递归解析器Recursive Resolver帮客户端去“跑腿”的服务器运营商、公共 DNS 服务商提供的都是这个角色。权威服务器Authoritative Server真正持有某个域名记录、能给出“最终答案”的服务器。举个例子当你请求www.example.com递归解析器会先问根服务器“.com该找谁”再问.com顶级域服务器“example.com该找谁”最后问example.com的权威服务器“www的 IP 是多少”。这整个过程对你来说是透明的但理解每个环节的职责后面排查“到底哪一步慢了、哪一步失败了”就非常有用。2. DNS 解析的完整旅程一次查询背后的复杂流程2.1 从浏览器到递归解析器本地缓存与 hosts用户访问网站时第一步不是直接发 DNS 请求而是先查本地缓存。这个缓存包括浏览器缓存、操作系统缓存和hosts文件。我之前遇到过一个问题改了 CDN 的解析记录但测试机器上始终访问旧 IP折腾半天才发现是hosts文件里残留了一条老记录。所以排查 DNS 时永远先看本地再查网络。浏览器缓存是最短的通常只有几十秒到几分钟操作系统缓存受 TTLTime To Live控制hosts文件则是人工硬编码的“最高优先级”。在 Linux 上/etc/hosts的优先级可以通过/etc/nsswitch.conf里的hosts:行调整macOS 则优先查缓存再查 hostsWindows 的优先级顺序也可以调整细节略有不同但核心思路一致先本地后远程。本地都没有命中客户端才会把查询发给配置好的递归解析器。这个请求通常是一个 UDP 包发到 53 端口如果数据包太大或需要安全传输就会切换到 TCP 或 DNS over TLS。2.2 递归查询与迭代查询的区别很多教程把递归和迭代混在一起讲导致新手越看越晕。其实区分很简单递归查询客户端对递归解析器做的事。客户端说“帮我查一下这个域名”递归器必须给客户端一个最终答案或错误码。中途它怎么跟别人沟通客户端不关心。迭代查询递归解析器对各个权威服务器做的事。每次只问“下一站是谁”然后自己再去找下一站反复多次直到拿到答案。用工作流程来类比你打电话问前台“王工的座机号是多少”前台自己不知道但她会去员工系统查查到告诉你这是递归如果你自己先问前台“王工在哪个部门”前台答“技术部”你再打技术部电话问“王工工位在哪”这是迭代。实际上递归解析器内部干的就是一连串迭代查询。那为什么不让客户端直接去问权威服务器一是因为客户端往往不具备递归能力二是如果每台电脑都去遍历根服务器根服务器的压力会大到无法承受。递归解析器作为“缓存大本营”能大幅减少全网重复查询量。2.3 根服务器、顶级域与权威服务器的协作全球有 13 组根服务器实际上背后是更多物理节点用 Anycast 广播它们的名字是a.root-servers.net到m.root-servers.net。根服务器不负责具体域名只负责告诉你“某个顶级域如.com、.org、.cn的权威服务器在哪里。接着是顶级域TLD服务器比如.com的服务器由 Verisign 运营.cn由 CNNIC 管理。它们也不负责单条记录只负责告诉你“下一个层级的权威服务器在哪里”。最后才是真正的权威服务器比如你域名托管在 Cloudflare、阿里云或自建机房那里才存着最终的 A、AAAA、CNAME 等记录。整个链路像一棵倒挂的树从根出发逐层向下查找。这里我想强调一个常见误区很多人以为“改 DNS 记录立即生效”其实不是。如果权威服务器上的 TTL 设置为 600 秒那么递归器会在 10 分钟内把旧答案缓存住不会重新来问你。这就是为什么你改了解析却感觉“没生效”的原因。2.4 缓存机制TTL 与缓存层级TTL 是 DNS 中最重要的参数之一。它告诉递归解析器这条记录可以缓存多久。比如dig example.com返回的7200 IN A意味着 7200 秒2小时内递归器可以直接用缓存回复客户端不需要再向上查询。TTL 设置不是越小越好也不是越大越好。太小会让权威服务器负载飙升每次查询都回源太大则导致解析变更时间过长故障切换时血亏。我见过生产环境把 TTL 设成 8640024小时结果流量切机房时相当一部分用户在一整天里持续访问旧 IP业务受损严重。后来我们总结的实践是稳定资源用较高 TTL如 600~3600需要频繁切换的资源用低 TTL如 60~300变更前提前降低 TTL变更后再慢慢升回去。递归器内部通常也会分“缓存命中”和“缓存未命中”两类统计。如果你用dig trace能看到完整查询路径用dig不带参数则能看到“本机解析器缓存结果”。判断反馈的来源看flags里的aaAuthoritative Answer字段就能区分权威服务器返回的答案是aa标记的缓存返回的则没有。3. DNS 核心协议细节报文格式与记录类型3.1 DNS 报文结构解析DNS 协议基于简单的请求/响应模型报文格式统一分为五个部分Header、Question、Answer、Authority、Additional。Header 始终固定 12 字节后面三部分是可变的资源记录列表。Header 里最关键的字段包括ID16 位的标识符用来匹配请求和响应。客户端发出请求时随机生成 ID响应必须带回相同 ID。这个字段后来也成为 DNS 安全或劫持的攻防焦点。Flags包含 QR0 是查询1 是响应、Opcode标准查询、反向查询等、AA权威回答、TC截断标志、RD期望递归、RA递归可用等。QDCOUNT / ANCOUNT / NSCOUNT / ARCOUNT分别表示问题、答案、权威区和附加记录的条目数。Question 部分包含查询名称、类型如 A、AAAA、MX和类绝大多数是 IN即 Internet。Answer 部分则是具体的资源记录每条记录由名字、类型、类、TTL、RDLength 和 RData 组成。有一次我排查一个奇怪的问题发现响应报文的TC位为 1。这说明应答超过了 UDP 512 字节的标准限制被截断了。这种情况下客户端应该改用 TCP 重试或者支持 EDNS0 扩展允许更大的 UDP 包。很多老程序不处理 TC 位就会表现出“查询超时”或“解析失败”但实际上是 UDP 包被截断导致的。3.2 常见记录类型与使用场景DNS 记录类型很多但日常最常见的就那几种类型全名作用示例场景AAddress Record域名指向 IPv4example.com. 3600 IN A 1.2.3.4AAAAIPv6 Address Record域名指向 IPv6example.com. 3600 IN AAAA 2001:db8::1CNAMECanonical Name别名指向另一个域名www.example.com. CNAME example.com.MXMail Exchange邮件服务器优先级与地址example.com. 300 IN MX 10 mail.example.com.NSName Server指定域名的权威 DNS 服务器example.com. 86400 IN NS ns1.example.com.TXTText Record任意文本常用于验证等_verify.example.com. TXT abc123SRVService Record指定服务的主机和端口_sip._tcp.example.com. SRV 10 60 5060 sip.example.com.CNAME 是把多重指向收敛到单点的利器但也有坑CNAME 不能与其他记录共存于同一个名字按照 RFC 1034 的规定所以www.example.com如果用了 CNAME就不能再配 MX、TXT 等其他记录。如果你需要在 CDN 域名下同时配置根域名的 SPF 记录就得注意别把根域名搞成 CNAME。TXT 记录现在已经很常见主要用于域名验证比如 SSL 证书的 ACME 挑战、邮箱的 SPF/DKIM/DMARC 验证、防止垃圾邮件。有一次给某个域名配置域验证看到 TXT 记录已经生效但校验方一直失败后来发现是我把整条记录值多加了引号——复制时带着了这类问题特别容易出现在手动界面配置的场合。3.3 域名压缩、指针与传输层选择DNS 报文里为了省空间设计了域名压缩机制相同后缀的域名可以只存一份其他位置用“指针”指向之前出现过的位置。用 Wireshark 抓包时你会看到 0xc0 开头的两个字节那就是一个 14 位的偏移指针。理解这点对做流量分析和写 DNS 解析器很重要但一般业务开发不用关心。传输层方面传统 DNS 默认走 UDP 53 端口重点在于快但 UDP 有 512 字节老限制新扩展EDNS0允许协商更大的包大小。当响应数据太大、UDP 不通或者需要 zone transfer区域传送时DNS 也会走上 TCP 53。所以很多防火墙只放行 UDP 53 就以为万事大吉结果 DNSSEC 的响应包大了、或递归器需要刷新区域时就会莫名其妙失败。我之前遇到过自建 DNS 权威服务器区域传送总是超时排查了半天发现是云安全组忘了放行 TCP 53 端口。记住这个教训DNS 不只是 UDPTCP 53 一样关键。4. 手把手实战搭建与调试 DNS 服务4.1 用 dig 和 nslookup 快速定位解析问题排查 DNS 最趁手的工具就是digLinux/macOS 自带Windows 可以用 nslookup 或装 dig。几个常用命令要学会# 查看默认递归器返回的解析结果 dig example.com # 查看具体类型 dig example.com AAAA # 从根开始完整追踪解析路径 dig trace example.com # 指定递归服务器查询 dig 8.8.8.8 example.com # 查看权威服务器上的记录需要开 norec 禁止递归 dig norec ns1.example.com example.com # 反向解析 IP 到域名 dig -x 8.8.8.8dig输出里的关键信息有statusNOERROR、NXDOMAIN、SERVFAIL、REFUSED 等、ANSWER SECTION、AUTHORITY SECTION、查询耗时、递归器 IP 等。如果状态是NXDOMAIN说明域名本身不存在或打错了如果是SERVFAIL通常是上游权威服务器出问题或 DNSSEC 验证失败如果是REFUSED多半是递归器拒绝为这个域名提供解析。nslookup相对简单适合快速验证但信息量不如 dig。实际工作中我习惯先用dig trace确定是链路哪一环的问题再用dig 具体服务器验证某一层的返回。比如我怀疑权威服务器挂了一个节点就分别对两台 NS 服务器发查询对比ANSWER SECTION的差异哪个不一致基本就能定位故障节点。4.2 搭建本地 DNS 缓存服务器dnsmasq自建一个递归缓存 DNS 并不难最常见的是用dnsmasq它轻量、配置文件友好适合局域网环境。我们在办公室搭建过一个给开发环境用的缓存服务效果不错。安装后核心配置就几行# /etc/dnsmasq.conf # 监听地址只监听内网网卡 listen-address192.168.1.53 # 只允许内网网段使用 interfaceeth1 # 不读 /etc/hosts 中某些记录按需 # no-hosts # 将某些域名直接指向内部服务器 address/dev.example.com/192.168.1.100 # 指定上游递归器 server223.5.5.5 server1.1.1.1启动后把客户端 DNS 指到 192.168.1.53就能享受到缓存加速和自定义开发域名映射。dnsmasq 最惊艳的一点是address/域名/IP可以快速指定任意域名解析到固定 IP省去了改 hosts 的麻烦尤其适合搭配 nginx 做本地联调。不过 dnsmasq 也有短板它主要是转发加缓存完整递归能力有限如果要做企业级 DNS 服务建议用unbound或PowerDNS Recursor。unbound 是真正的递归解析器支持 DNSSEC 验证、缓存调优、访问控制配置相对复杂但可控性好。4.3 配置权威 DNS 服务以 BIND 为例如果你拥有一个域名想自建权威 DNS 服务BINDBerkeley Internet Name Domain是历史最悠久、生态最完善的选择。这里只讲最简配置。首先安装并生成 key然后编辑主配置文件zone example.com { type master; file /etc/bind/zone.example.com; allow-transfer { 172.16.0.2; }; # 允许从服务器同步 };区域文件内容类似$TTL 3600 IN SOA ns1.example.com. admin.example.com. ( 2024010101 ; Serial 3600 ; Refresh 900 ; Retry 604800 ; Expire 86400 ) ; Negative Cache TTL IN NS ns1.example.com. IN NS ns2.example.com. ns1 IN A 192.0.2.1 ns2 IN A 192.0.2.2 IN A 192.0.2.10 www IN A 192.0.2.10配置完成之后用named-checkzone校验区域文件再rndc reload重载。这一步千万别偷懒语法错误会导致整个 zone 加载失败远端查询就会进入 SERVFAIL。这里特别要说一下 SOA 记录里的 Serial。区域文件变更时必须手动更新 Serial 数值一般是日期格式如 2024010101从服务器才会因为 Serial 变大而触发区域传送。很多人改了记录忘了改 Serial导致主从数据不一致从服务器永远不更新线上的“缓存从服务器旧数据”能让你排查到头秃。这是自建权威 DNS 最容易踩的坑。4.4 常见配置参数与缓存优化实践不管是递归还是缓存参数调优都直接影响性能和准确性。以 unbound 为例几个关键项:cache-min-ttl: 60 cache-max-ttl: 7200 prefetch: yes prefetch-key: yes num-threads: 4 msg-cache-size: 64m rrset-cache-size: 128mprefetch可以理解为“预加载”当缓存中的热门记录即将过期时unbound 会提前自动发起新一轮查询保证客户端请求时不用等递归。这个对高并发场景非常有用能有效降低平均延迟和回源率。但注意过大的 cache 并不一定好。缓存太多冷门域名反而占内存命中率不会有明显提升。实践中我喜欢用unbound-control stats查看缓存命中率再决定要不要调大缓存尺寸。如果命中率已经超过 90%再增大只是浪费内存。5. 常见问题与排查技巧实录5.1 解析慢或超时先分清是谁慢遇到“网页打不开”第一步用dig看查询耗时。如果本机解析耗时几十毫秒那问题不在 DNS如果耗时好几秒甚至出现timed out就要继续定位。常见原因有三个:上游递归器不通检查到递归器的网络连通性比如ping 8.8.8.8并非可靠因为很多公共 DNS 禁 ping改用dig 8.8.8.8 example.com更直接。根服务器或权威服务器响应慢用dig trace逐步计时锁定是哪一层慢。有些国外权威服务器在境内访问奇慢但响应正常需要考虑就近选路或使用国内 DNS 服务。UDP/EDNS 问题如果权威服务器不支持 EDNS或者中间设备丢弃了带 EDNS 的包可能导致大响应超时。可以用dig noedns对比测试。还有一种隐藏很深的场景递归器对某个域名的递归没有开启 RD flag或者防火墙把响应报文给丢了。遇到这种情况用tcpdump -i eth0 port 53抓包看有没有响应“有请求没响应”基本就是链路过滤问题。5.2 缓存污染与 TTL 陷阱DNS 缓存污染是个大话题。简单说递归器收到了伪造的响应把错误记录缓存下来再返回给用户。现代公共 DNS 大多采用随机端口、随机 ID、DNSSEC 等方式防御但自建递归器裸奔的话风险很高。另一种“污染”则来自故意或误操作导致的错误结果。比如公司内部 DNS 把某个外部域名解析到了内网 IP或者递归器收到错误的 NXDOMAIN否定缓存。对于否定缓存RFC 2308 建议记录一个 SOA 的 TTL但很多 DNS 实现的默认值偏大导致“域名明明刚添加解析却要等几分钟才能访问”。我的经验是遇到解析异常先在公共递归器如 223.5.5.5上验证域名本身是否正常dig 223.5.5.5 example.com dig 8.8.8.8 example.com如果公共 DNS 结果正确而内部递归器结果错误那基本可以断定是内部缓存或策略问题。此时清缓存最直接systemd 环境用systemd-resolve --flush-cachesdnsmasq 用SIGHUP重启unbound 用unbound-control flush example.com。别动不动重启精确 flush 才是专业操作。5.3 域名劫持与安全加固思路公共网络环境中的 DNS 劫持大多发生在路由器或者运营商侧。特征是你访问一个不存在的域名时路由器返回一个 SEO 推广页或者访问某些网站时页面被插入广告。这本质上是网络设备篡改了 DNS 响应。个人用户最简单的加固方式在系统里配置 DoHDNS over HTTPS或 DoTDNS over TLS把 DNS 查询加密起来。使用值得信任的公共 DNS同时本地开启 DNSSEC 验证。检查路由器是否开启了内置 DNS 代理或拦截尽量改为透传。如果自己运营 DNS 服务保障措施包括限制递归范围不对外随意开放递归启用源端口随机化部署 DNSSEC 签名监控 DNS 响应时间和异常流量。DNS 一旦出了问题影响面可能在几秒内辐射到整个公司必须有指标告警。5.4 分域解析Split-Horizon配置注意事项很多企业为了内部业务希望同一域名在办公网解析到内网 IP在外网解析到公网 IP。这通常用 split-horizon 实现即 DNS 权威服务器根据来源 IP 或递归器网段返回不同结果。BIND 提供view配置dnsmasq 可以简单用server/domain/内部服务器加address实现。但这里有一个极大的坑内部路由和外部路由的递归器如果都去同一个权威服务器而权威服务器的 view 规则没有区分清楚决策就会混乱。有一次我们把内部域名ops.example.com同时放在内网 DNS 和外网权威区里结果内网用户解析这个域名时被递归器带到了外网权威然后返回公网 IP流量绕了一圈既慢又可能被防火墙阻断。正确做法是让内部递归器优先命中内部 zone或者用内部域名后缀区分内外部域名比如内网统一用internal.example.com。还有一点需要注意split-horizon 时TXT 与 MX 等记录可能同步返回到外部查询者。如果内部记录的 TXT 包含敏感信息或者 SRV 暴露内部服务架构外部人员用公共 DNS 也可能查到如果权威服务没有严格 view 隔离。所以把内网记录和对外记录严格分开是底线。6. 从协议到工程DNS 的扩展与未来6.1 DNSSEC给 DNS 应答加上数字签名DNS 本身是明文、无认证的。攻击者可以在中间伪造响应这就是 DNS 欺骗。DNSSEC 通过给资源记录做数字签名来解决这个问题权威服务器拿着私钥对记录签名递归器用信任锚的公钥验证签名。部署 DNSSEC 并不只是在域名服务商面板里“开启”这么简单你需要生成 KSKKey Signing Key和 ZSKZone Signing Key。在父域如.com的 DNS发布 DS 记录。定期轮换密钥。签名并更新 zone file。如果配置不对后果非常严重递归器验证失败后直接返回SERVFAIL导致域名完全不可用。所以生产上 DNSSEC 的变更一定要先在测试环境演练同时留意递归器日志。常见错误是 DS 记录未及时更新或者密钥轮换后 old key 还留在里面。6.2 DoH / DoT加密 DNS 查询的新标准DNS 报文裸奔在网络上ISP 可以看到你查询的所有域名中间人也能篡改。DoTDNS over TLS853 端口和 DoHDNS over HTTPS443 端口应运而生本质是把 DNS 查询包在加密隧道里保证机密性和完整性。浏览器和系统层面都已经支持 DoH。Chrome 默认可以使用“安全 DNS”macOS 和 iOS 也支持 DoH 配置。对普通用户来说配置 DoH 能显著降低局域网内 DNS 劫持风险。对开发者来说注意 DoH 请求头里的Accept: application/dns-message以及 POST 请求的二进制 body 格式用 curl 手工调试也比较方便。部署企业级 DoH 服务时建议在前面加一层负载均衡和访问控制因为 DoH 流量与 HTTPS 混合在 443 端口如果不做 SNI 过滤或端口隔离很难和普通 Web 服务区分。自建 DoT 则要正确配置 SSL 证书否则客户端证书校验失败直接拒绝连接。6.3 DNS 在云原生时代的角色Kubernetes 与 CoreDNS在 Kubernetes 里DNS 不是可选项而是集群的基础组件。ClusterIP服务通过 DNS 提供稳定的服务发现Pod 里的/etc/resolv.conf通常指向集群里的 CoreDNS Service。CoreDNS 是一个模块化的 DNS 服务器用 Go 编写插件架构。我们日常调的最多的是kubernetes插件监听 Service 和 Pod 的变更动态生成 DNS 记录。prometheus插件暴露 metrics便于监控。forward/proxy插件将集群外部的域名转发给上游 DNS。容器场景里最经典的坑是resolv.conf的ndots参数。默认ndots:5意味着如果域名里“点”的数量少于 5先尝试在 search 域里搜索然后再去请求原始域名。很多服务调用外部接口时明明域名存在但每次 DNS 查询都超了几秒就是因为 search 域候选列表太长导致额外多发了 N 次查询。可以根据业务调整 Pod 的dnsConfigdnsConfig: options: - name: ndots value: 1如果集群需要自定义域名解析比如内网私域域名可以用 CoreDNS 的hosts插件或配置file插件加载 zone 文件。不过要注意最好不要在 CoreDNS 里放太多高复杂度的业务逻辑它本质上是集群的“服务发现经纪”不是公司级的权威 DNS 系统两者职责应该分开。从最初那个 512 字节的小报文到如今整个互联网的心脏DNS 的生命力在于“小而美”的设计。它不复杂但正因为简单才容易出各种“玄学”问题——缓存、TTL、劫持、轮询、DNSSEC每个点都能让你排查到怀疑人生。我个人在实际操作中的体会是排障时永远先确认“这一刻的解析结果是什么”再问“它为什么是这样”。每次遇到 DNS 相关的线上故障先用 dig 把结果打出来再顺着链路一步步验证而不是上来就改配置重启服务很多时候所谓的“灵异事件”只是缓存没清、search 域干扰、或某个节点配置失效而已。希望这篇梳理能帮你把 DNS 这套协议从“玄学”变成“科学”少踩几个坑多省几根头发。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进