DNS 最早规定 UDP 响应不得超过 512 字节,今天的响应(DNSSEC 签名、一大串 TXT)早就装不下了。这篇看两种办法:EDNS0 让客户端告诉服务器自己能收多大的 UDP 包;或者干脆改用 TCP。再看 TCP 比 UDP 贵多少,以及公共解析器怎么避免产生 IP 分片。
UDP(无 EDNS0):响应 17 字节,标志 QR TC RD RA,回答 0 条;TC 位 = True(被截断:服务器放不下就只回头部和问题,让客户端改用 TCP) TCP 重问:响应 842 字节,标志 QR RD RA,回答 3 条(DNSKEY);建立连接 270 ms + 查询往返 269 ms
不带 EDNS0 的 UDP 响应超过 512 字节时,服务器只回头部和问题并置 TC,客户端应改用 TCP。
查询 40 字节;末尾 11 字节就是 OPT:00 00 29 04 d0 00 00 80 00 00 00
00 = 名字(根);41 = 类型 OPT(41);1232 = 通告的 UDP 载荷大小;00 = 扩展 RCODE,00 = EDNS 版本,8000 = 标志(最高位 DO = True);0 = RDLENGTH(选项总长)
响应 179 字节;响应里的 OPT:有,服务器通告的 UDP 载荷大小 = 1232,DO 位回显 = True,选项 []
通告 payload=512:响应 72 字节,服务器回的 payload = 1232
通告 payload=1232:响应 72 字节,服务器回的 payload = 1232
通告 payload=4096:响应 72 字节,服务器回的 payload = 1232EDNS0 的 OPT 伪记录借用“类”字段放 UDP 载荷大小,借用“TTL”字段放扩展 RCODE、版本和 DO 标志,1.1.1.1 总是通告 1232。
TCP 上发送的字节:0028 + 40 字节报文(前两个字节 0x0028 = 40 就是后面报文的长度) UDP :[272, 290, 288, 277, 282, 294] ms,中位 285 ms TCP 新连接(连接 + 查询) :[599, 1630, 492, 576, 625, 2618] ms,中位 612 ms TCP 复用连接的第 2 问 :[1038, 294, 312, 216, 295, 282] ms,中位 295 ms 到 1.1.1.1 的网络往返约 285 ms:UDP 一个往返;TCP 新连接多一个握手往返(约 2 倍);复用连接的后续查询回到一个往返
TCP 消息前加 2 字节长度;新建连接要多一个往返,约 2 倍耗时;复用同一连接则几乎没有额外代价。
UDP:4 个 IP 包,共 272 字节(IP 层);逐包(→ 发出 / ← 收到,数字是 IP 包长):['←52T', '→52T', '→68U', '←100U'] TCP 新连接:10 个 IP 包,共 656 字节(IP 层);逐包:['→64T', '←60T', '→52T', '→94T', '←52T', '←126T', '→52T', '→52T', '←52T', '→52T'] 再重复 UDP 3 次的包数:[2, 2, 2](逐包 [['→68U', '←100U'], ['→68U', '←100U'], ['→68U', '←100U']])
一次 UDP 问答 2 包 / 168 字节;一次新建 TCP 连接的问答 10 包 / 656 字节。
cloudflare.com TXT,通告 payload=4096:应用层收到 43 字节,TC = True,回答 0 条;线上来自 1.1.1.1 的 IP 包 1 个:[('偏移0', '末片', 71)]
cloudflare.com TXT,通告 payload=1232:应用层收到 43 字节,TC = True,回答 0 条;线上来自 1.1.1.1 的 IP 包 1 个:[('偏移0', '末片', 71)]
microsoft.com TXT,通告 payload=4096:应用层收到 42 字节,TC = True,回答 0 条;线上来自 1.1.1.1 的 IP 包 1 个:[('偏移0', '末片', 70)]
2020 年起的 “DNS flag day” 建议把 EDNS0 载荷大小设成 1232 左右(= IPv6 最小 MTU 1280 − 40 − 8),既避免分片,又足够容纳大多数响应;更大的交给 TCP1.1.1.1 不管客户端通告多大都只发不超过 1232 字节的 UDP 响应,更大的置 TC,让客户端改走 TCP。