<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>UDP · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/udp/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/udp/index.xml" rel="self" type="application/rss+xml"/><item><title>UDP 保留数据报边界，恢复和时效由应用负责</title><link>https://www.xbcatm.com/zh/posts/udp-trades-reliability-for-latency/</link><pubDate>Wed, 11 Nov 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/udp-trades-reliability-for-latency/</guid><description>观察空数据报，识别过小接收缓冲区造成的截断，并通过本地中继复现缺失、重复与乱序。</description><content:encoded>&lt;p&gt;UDP 在传输端点之间携带独立数据报，八字节首部包含来源端口、目的端口、长度和校验和字段。它不会加入 TCP 的可靠有序字节流或拥塞控制行为，让应用可以选择自己的交付策略，但不承诺所有负载下延迟都更低。应用确实需要某条内容时，丢失的数据报可能比建立连接的开销更昂贵，必须按完整请求过程评估。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;发送成功不证明目标应用已经收到或处理消息。UDP 没有内建确认、重传、去重或顺序保证，因此超时不能告诉支付客户端操作是否发生。允许重试时，应用需要能够处理重复请求和结果不确定的协议，不能把没有响应直接解释成业务失败。消息是否到达、动作是否执行、结果是否持久化，也应具有不同含义。&lt;/p&gt;
&lt;p&gt;即使本地路径健康，接收缓冲区也能制造失败。数据报大于本次调用提供的缓冲区时，内容可能截断，缺失后缀不会像字节流那样留给下一次读取。Linux 的 recvmsg 可以通过 MSG_TRUNC 报告这一情况。如果把缩短的字节当成完整命令或测量值，就可能在没有明显进程错误的情况下改变业务含义，因此必须检测协议要求的完整性。&lt;/p&gt;
&lt;p&gt;UDP 没有内建拥塞控制，意味着应用承担责任，并不意味着可以无限制发送。需要根据部署选择发送节奏、拥塞行为、消息大小和截止时间。分片可能降低大数据报的交付稳健性，过了截止时间的数据也可能已经没有价值。QUIC 等协议能够在 UDP 之上提供可靠流，说明底层载体本身不能决定全部应用可靠性。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;将脚本保存为 &lt;code&gt;check-udp.py&lt;/code&gt;，准备 Docker 后运行 &lt;code&gt;python3 check-udp.py&lt;/code&gt;。固定 Python 3.11.16 Linux 容器没有外部网络路径和宿主端口发布。第一部分检查真实套接字接收语义；第二部分使用刻意编写的中继扣留、重复和重排消息，不声称测量自然发生的网络丢包。实验的每个故障输入都在代码中明确可见：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nn"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nn"&gt;uuid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;python@sha256:528257d48c1da0dcecc2e725d1ae34498d60c965f1241e39cd6a85a8859bdf84&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;import json,socket
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;with socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as sender,socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as receiver:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; receiver.bind((&amp;#39;127.0.0.1&amp;#39;,0));receiver.settimeout(.25);sender.settimeout(1);target=receiver.getsockname()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; sender.connect(target)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; for body in [b&amp;#39;A&amp;#39;,b&amp;#39;BC&amp;#39;,b&amp;#39;&amp;#39;]:sender.send(body)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; boundaries=[receiver.recvfrom(16)[0] for _ in range(3)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert boundaries==[b&amp;#39;A&amp;#39;,b&amp;#39;BC&amp;#39;,b&amp;#39;&amp;#39;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; sender.send(b&amp;#39;abcdefgh&amp;#39;);data,ancillary,flags,peer=receiver.recvmsg(3)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert data==b&amp;#39;abc&amp;#39; and flags&amp;amp;socket.MSG_TRUNC
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; try:receiver.recvfrom(16);raise AssertionError(&amp;#39;truncated suffix survived&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; except TimeoutError:tail=&amp;#39;discarded&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; sender.send(b&amp;#39;next&amp;#39;);assert receiver.recvfrom(16)[0]==b&amp;#39;next&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; # A deliberate application relay changes delivery; this is not measured network loss.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; received=[];applied=[];seen=set();forwarded=[]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; with socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as relay,socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as output:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; relay.bind((&amp;#39;127.0.0.1&amp;#39;,0));relay.settimeout(1);output.settimeout(1)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; sender.connect(relay.getsockname())
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; for seq in [1,2,3]:sender.send(str(seq).encode())
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; inputs=[relay.recvfrom(16)[0] for _ in range(3)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; for index in [2,0,0]:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; output.sendto(inputs[index],target);forwarded.append(int(inputs[index]))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; value=int(receiver.recvfrom(16)[0]);received.append(value)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if value not in seen:seen.add(value);applied.append(value)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert received==[3,1,1] and applied==[3,1] and 2 not in seen
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; try:receiver.recvfrom(16);raise AssertionError(&amp;#39;missing message arrived by itself&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; except TimeoutError:no_retransmit=True
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; print(json.dumps({&amp;#39;datagram_lengths&amp;#39;:[len(x) for x in boundaries],&amp;#39;empty_datagram_is_message&amp;#39;:boundaries[2]==b&amp;#39;&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;short_buffer&amp;#39;:data.decode(),&amp;#39;truncation_flag&amp;#39;:bool(flags&amp;amp;socket.MSG_TRUNC),&amp;#39;unread_tail&amp;#39;:tail,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;relay_inputs&amp;#39;:[int(x) for x in inputs],&amp;#39;relay_outputs&amp;#39;:forwarded,&amp;#39;receiver&amp;#39;:received,&amp;#39;deduplicated&amp;#39;:applied,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;missing_sequence&amp;#39;:2,&amp;#39;no_automatic_retransmission_during_deadline&amp;#39;:no_retransmit}))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;xbcatm-socket-&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;docker&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;run&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--rm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-i&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--network&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;none&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;--memory&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;128m&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--pids-limit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;64&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;python3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;ensure_ascii&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;docker&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-f&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;三次发送分别包含 &lt;code&gt;A&lt;/code&gt;、&lt;code&gt;BC&lt;/code&gt; 和空载荷，三次接收得到长度一、二和零。每次读取消费一条数据报，没有把相邻发送合并成字节流。空结果是合法零长度消息，不代表发送方关闭连接。发送套接字使用 UDP connect 选定对端，这个接口调用不会把 UDP 变成 TCP，也不会凭空增加确认和重传机制。&lt;/p&gt;
&lt;p&gt;接着发送八字节 &lt;code&gt;abcdefgh&lt;/code&gt;，接收方只提供三字节缓冲区，得到 &lt;code&gt;abc&lt;/code&gt; 和 MSG_TRUNC。之后读取超时，没有拿到 &lt;code&gt;defgh&lt;/code&gt;；再发送新的 &lt;code&gt;next&lt;/code&gt; 数据报，接收方能够取得完整新消息。这个正向对照说明旧尾部已经被丢弃，而不是接收套接字在第一次调用后完全失效。截断与后续新消息属于不同数据报生命周期。&lt;/p&gt;
&lt;p&gt;中继先收到序号一、二、三，再有意转发三、一、一，省略二。下游真实套接字收到这个选定序列。应用用一个小集合过滤重复值，最终执行序号三和一，却没有恢复原始顺序，也没有重建缺失消息。去重只解决其中一项问题，不能把它当成完整可靠传输或业务事务协议。所需语义必须逐项明确。&lt;/p&gt;
&lt;p&gt;之后接收方等待下一条数据报，直到达到截止时间，UDP 没有自动重传序号二。这里缺失由中继制造，不能把结果写成某条网络的可靠性统计。它提供可复现的输入模式，用来观察应用面对传输契约没有排除的交付情况时如何反应。程序行为、故障注入与实际网络测量，需要在结果解释中保持区分。&lt;/p&gt;
&lt;h2 id="有意识地补上所需语义"&gt;有意识地补上所需语义&lt;/h2&gt;
&lt;p&gt;实时测量可能丢弃旧样本并显示最新序号，文件传输则需要恢复缺失内容。应用确认可以表示已经收到，也可以表示操作结果已经持久化，但含义必须由协议定义。对有副作用动作增加重试前，应明确标识符范围、重试截止时间、去重保留期和进程重启行为。本例没有实现这些更完整的事务机制，不能直接承担相应承诺。&lt;/p&gt;
&lt;p&gt;同时规划接收能力和单条数据报大小，并把截断、应用发现的缺失与格式错误分别记录。增加套接字队列，无法修复单次接收缓冲区太小的问题；提高发送速率也可能加重拥塞，而不是改善信息新鲜度。应测量多少有用消息在截止时间前到达，而不只统计本地发送调用接受了多少字节。两种数字反映的结果不同。&lt;/p&gt;
&lt;p&gt;当消息模型符合所建协议，且缺失的保证都有明确负责机制时，UDP 才是合理选择。以真实端到端时延、失败恢复和资源消耗判断性能，而不是仅凭省略握手就宣称更快。保留边界能简化部分解析，但完整数据报始终不到达、重复到达或到得太迟时，应用仍须具有定义清楚且经过验证的行为。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc768.html"&gt;RFC 768 User Datagram Protocol&lt;/a&gt;，UDP 首部与交付属性，已核验 2026-09-12。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8085.html"&gt;RFC 8085 UDP usage guidelines&lt;/a&gt;，拥塞控制、消息大小及应用责任，已核验 2026-09-12。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man7/udp.7.html"&gt;Linux udp(7)&lt;/a&gt;，数据报接收、截断及已连接 UDP 套接字，已核验 2026-09-12。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>EDNS 宣告 UDP 容量，不保证沿途网络交付</title><link>https://www.xbcatm.com/zh/posts/edns0-suits-larger-dns-answers/</link><pubDate>Wed, 28 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/edns0-suits-larger-dns-answers/</guid><description>比较传统 DNS 与 EDNS，验证服务器载荷上限，检查 BADVERS，并区分 DO 标志与已验证数据。</description><content:encoded>&lt;p&gt;EDNS 通过 OPT 伪记录扩展 DNS，其中一项作用是让请求发送方宣告自己准备接收的 UDP 载荷大小，从而允许回答超过传统五百一十二字节 DNS 限制。这是 DNS 交换中的容量宣告，不是网络路径 MTU 发现协议，也不承诺所有中间设备都会交付相同大小的数据报。理解这一区别，才能准确解释大回答为何仍可能失败。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;客户端宣告四千零九十六字节，不会强迫服务器发送同样大小的响应。服务器可以实施更小的发送上限；需要的回答放不下时，可以返回 TC，客户端仍需可工作的 TCP 路径。增加客户端数字，不能修复服务器有意采用较小预算的策略。应查看两端实际配置与返回内容，而不是把请求里出现的大数值当成双方已承诺一定使用的大小。&lt;/p&gt;
&lt;p&gt;反过来，即使回答没有超过宣告容量，也可能在途中丢失。分片、隧道、包过滤和本地网络约束，与 DNS 参与者宣告的能力是不同因素。客户端若根本没有收到响应，也就可能看不到 TC 提示。载荷配置需要按互通性验证，不能当作越大越好的统一性能参数。一次超时无法仅凭宣告值就归因于某一台服务器或某个固定网络环节。&lt;/p&gt;
&lt;p&gt;EDNS 还承载不能当作普通缓存记录解释的信息。OPT 是描述本次交换的伪记录，其字段编码载荷容量、版本、扩展响应码和标志；外形对应 TTL 的字段，并不是普通记录寿命。DNSSEC OK 标志 DO 用于请求相关 DNSSEC 数据，却不证明任何一方完成了验证。诊断工具若丢弃这些元信息，可能把不同故障简化成相同空回答。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;将脚本保存为 &lt;code&gt;check-edns.py&lt;/code&gt;，准备 Docker 后执行 &lt;code&gt;python3 check-edns.py&lt;/code&gt;。固定 BIND 9.18.49 权威运行在隔离回环网络，明确把 &lt;code&gt;edns-udp-size&lt;/code&gt; 和 &lt;code&gt;max-udp-size&lt;/code&gt; 都设为一千二百三十二。区域中分别准备四条和八条 TXT 的记录集，以便独立观察容量与截断。实验不依赖公网默认值，也不修改现有解析器的载荷策略：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;tempfile&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TemporaryDirectory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nn"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nn"&gt;uuid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;FROM python@sha256:528257d48c1da0dcecc2e725d1ae34498d60c965f1241e39cd6a85a8859bdf84&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;RUN apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends bind9=1:9.18.49-1~deb12u2 bind9-dnsutils=1:9.18.49-1~deb12u2 dnsmasq-base=2.90-4~deb12u2 python3-dnspython=2.3.0-1 &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;CMD [&amp;#34;/usr/bin/python3&amp;#34;]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;from contextlib import ExitStack,contextmanager
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;from pathlib import Path
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;from tempfile import TemporaryDirectory
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;import json,re,signal,subprocess,time
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;import dns.exception,dns.message,dns.query,dns.rcode,dns.rdatatype
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def run(*args,check=True):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; result=subprocess.run(args,capture_output=True,text=True,timeout=15)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if check:assert result.returncode==0,(args,result.returncode,result.stdout,result.stderr)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return result
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def query(name,kind=&amp;#39;A&amp;#39;,port=1053):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return dns.query.udp(dns.message.make_query(name,kind),&amp;#39;127.0.0.1&amp;#39;,port=port,timeout=1)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def values(response,kind=&amp;#39;A&amp;#39;):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; wanted=dns.rdatatype.from_text(kind)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return [item.to_text() for rrset in response.answer if rrset.rdtype==wanted for item in rrset]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def show(response):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; def section(records):return [{&amp;#39;name&amp;#39;:str(rr.name),&amp;#39;type&amp;#39;:dns.rdatatype.to_text(rr.rdtype),&amp;#39;ttl&amp;#39;:rr.ttl,&amp;#39;data&amp;#39;:[item.to_text() for item in rr]} for rr in records]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return {&amp;#39;rcode&amp;#39;:dns.rcode.to_text(response.rcode()),&amp;#39;answer&amp;#39;:section(response.answer),&amp;#39;authority&amp;#39;:section(response.authority)}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def wait(predicate,timeout=15):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; deadline=time.monotonic()+timeout
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; while time.monotonic()&amp;lt;deadline:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; try:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if predicate():return
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; except (OSError,dns.exception.Timeout):pass
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; time.sleep(.05)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; raise AssertionError(&amp;#39;DNS fixture condition did not become true&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def stop(process):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if process.poll() is None:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; process.terminate()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; try:process.wait(timeout=5)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; except subprocess.TimeoutExpired:process.kill();process.wait(timeout=5)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;@contextmanager
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def lab(records,soa_ttl=8,minimum=4,extra=&amp;#39;&amp;#39;):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; with TemporaryDirectory(prefix=&amp;#39;xbcatm-dns-&amp;#39;) as directory,ExitStack() as stack:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; root=Path(directory);zone=root/&amp;#39;zone&amp;#39;;conf=root/&amp;#39;named.conf&amp;#39;;log=root/&amp;#39;named.log&amp;#39;;serial=1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; def save(body):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; zone.write_text(f&amp;#39;$ORIGIN lab.test.\n$TTL 30\n@ &lt;/span&gt;&lt;span class="si"&gt;{soa_ttl}&lt;/span&gt;&lt;span class="s2"&gt; IN SOA ns.lab.test. hostmaster.lab.test. (&lt;/span&gt;&lt;span class="si"&gt;{serial}&lt;/span&gt;&lt;span class="s2"&gt; 60 60 3600 &lt;/span&gt;&lt;span class="si"&gt;{minimum}&lt;/span&gt;&lt;span class="s2"&gt;)\n@ IN NS ns.lab.test.\nns IN A 127.0.0.1\n&amp;#39;+body+&amp;#39;\n&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; run(&amp;#39;named-checkzone&amp;#39;,&amp;#39;lab.test&amp;#39;,str(zone))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; save(records)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; conf.write_text(f&amp;#39;&amp;#39;&amp;#39;options {{ directory &amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{root}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;; listen-on port 1053 {{127.0.0.1;}}; listen-on-v6 {{none;}};
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;recursion no; dnssec-validation no; notify no; querylog yes; max-cache-size 8m; pid-file &amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{root}&lt;/span&gt;&lt;span class="s2"&gt;/named.pid&amp;#34;; session-keyfile none;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="si"&gt;{extra}&lt;/span&gt;&lt;span class="s2"&gt; }};
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;controls {{}};
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;zone &amp;#34;lab.test&amp;#34; {{ type primary; file &amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{zone}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;; }};
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#39;&amp;#39;&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; run(&amp;#39;named-checkconf&amp;#39;,str(conf))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; output=stack.enter_context(log.open(&amp;#39;w&amp;#39;))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; authority=subprocess.Popen([&amp;#39;named&amp;#39;,&amp;#39;-g&amp;#39;,&amp;#39;-c&amp;#39;,str(conf),&amp;#39;-n&amp;#39;,&amp;#39;1&amp;#39;,&amp;#39;-U&amp;#39;,&amp;#39;1&amp;#39;],stdout=output,stderr=subprocess.STDOUT)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; stack.callback(stop,authority)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; def ready():
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert authority.poll() is None,log.read_text()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; response=query(&amp;#39;lab.test.&amp;#39;,&amp;#39;SOA&amp;#39;);return response.rcode()==0 and response.answer[0][0].serial==serial
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; wait(ready)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; def update(body):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; nonlocal serial
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; serial+=1;save(body);authority.send_signal(signal.SIGHUP);wait(ready)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; def cache(port=1054,upstream=1053,size=150,options=()):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; logfile=root/(&amp;#39;cache-&amp;#39;+str(port)+&amp;#39;.log&amp;#39;);stream=stack.enter_context(logfile.open(&amp;#39;w&amp;#39;))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; command=[&amp;#39;dnsmasq&amp;#39;,&amp;#39;--no-daemon&amp;#39;,&amp;#39;--conf-file=/dev/null&amp;#39;,&amp;#39;--no-resolv&amp;#39;,&amp;#39;--no-hosts&amp;#39;,&amp;#39;--bind-interfaces&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;--listen-address=127.0.0.1&amp;#39;,&amp;#39;--port=&amp;#39;+str(port),&amp;#39;--server=127.0.0.1#&amp;#39;+str(upstream),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;--cache-size=&amp;#39;+str(size),&amp;#39;--log-queries&amp;#39;,&amp;#39;--log-facility=-&amp;#39;,*options]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; process=subprocess.Popen(command,stdout=stream,stderr=subprocess.STDOUT);stack.callback(stop,process)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; def cache_ready():
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert process.poll() is None,logfile.read_text()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return query(&amp;#39;lab.test.&amp;#39;,&amp;#39;SOA&amp;#39;,port).rcode()==0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; wait(cache_ready);return process,logfile
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; def queries(name,kind=&amp;#39;A&amp;#39;):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return sum(bool(re.search(r&amp;#39;query: &amp;#39;+re.escape(name.rstrip(&amp;#39;.&amp;#39;))+r&amp;#39; IN &amp;#39;+kind+r&amp;#39;\b&amp;#39;,line)) for line in log.read_text().splitlines())
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; yield root,update,cache,queries
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;import dns.flags
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;records=&amp;#39;\n&amp;#39;.join(f&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{name}&lt;/span&gt;&lt;span class="s2"&gt; 30 IN TXT &amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{i}&lt;/span&gt;&lt;span class="s2"&gt;-&amp;#39;+(&amp;#39;x&amp;#39;*200)+&amp;#39;&amp;#34;&amp;#39; for name,count in [(&amp;#39;medium&amp;#39;,4),(&amp;#39;large&amp;#39;,8)] for i in range(count))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def ask(name,payload=None,version=0,flags=0):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; q=dns.message.make_query(name+&amp;#39;.lab.test.&amp;#39;,&amp;#39;TXT&amp;#39;,use_edns=False)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if payload is not None:q.use_edns(edns=version,ednsflags=flags,payload=payload)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return dns.query.udp(q,&amp;#39;127.0.0.1&amp;#39;,port=1053,timeout=2)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def description(response):return {&amp;#39;wire_bytes&amp;#39;:len(response.to_wire()),&amp;#39;TC&amp;#39;:bool(response.flags&amp;amp;dns.flags.TC),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;rcode&amp;#39;:dns.rcode.to_text(response.rcode()),&amp;#39;EDNS_version&amp;#39;:response.edns,&amp;#39;advertised_payload&amp;#39;:response.payload,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;DO&amp;#39;:bool(response.ednsflags&amp;amp;dns.flags.DO),&amp;#39;AD&amp;#39;:bool(response.flags&amp;amp;dns.flags.AD),&amp;#39;answer_records&amp;#39;:sum(len(rr) for rr in response.answer)}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;with lab(records,extra=&amp;#39;edns-udp-size 1232; max-udp-size 1232;&amp;#39;) as (_,update,cache,queries):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; classic=ask(&amp;#39;medium&amp;#39;);extended=ask(&amp;#39;medium&amp;#39;,1232);capped=ask(&amp;#39;large&amp;#39;,4096);do=ask(&amp;#39;medium&amp;#39;,1232,flags=dns.flags.DO);version=ask(&amp;#39;medium&amp;#39;,1232,version=1)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert classic.flags&amp;amp;dns.flags.TC and len(classic.to_wire())&amp;lt;=512 and classic.edns==-1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert not extended.flags&amp;amp;dns.flags.TC and 512&amp;lt;len(extended.to_wire())&amp;lt;=1232 and len(values(extended,&amp;#39;TXT&amp;#39;))==4
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert extended.edns==0 and extended.opt is not None
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert capped.flags&amp;amp;dns.flags.TC and len(capped.to_wire())&amp;lt;=1232
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert do.ednsflags&amp;amp;dns.flags.DO and not do.flags&amp;amp;dns.flags.AD
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert not any(rr.rdtype==dns.rdatatype.RRSIG for rr in do.answer)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert version.rcode()==dns.rcode.BADVERS and version.edns==0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; full=dns.query.tcp(dns.message.make_query(&amp;#39;large.lab.test.&amp;#39;,&amp;#39;TXT&amp;#39;),&amp;#39;127.0.0.1&amp;#39;,port=1053,timeout=2)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert len(values(full,&amp;#39;TXT&amp;#39;))==8 and len(full.to_wire())&amp;gt;1232
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; print(json.dumps({&amp;#39;without_EDNS&amp;#39;:description(classic),&amp;#39;EDNS_1232&amp;#39;:description(extended),&amp;#39;client_4096_server_1232&amp;#39;:description(capped),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;DO_on_unsigned_zone&amp;#39;:description(do),&amp;#39;unsupported_version&amp;#39;:description(version),&amp;#39;TCP_full&amp;#39;:description(full)},sort_keys=True))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;xbcatm-dns-&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;docker&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;TemporaryDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;);(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Dockerfile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;built&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;build&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-t&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;built&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;built&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;run&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--rm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-i&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--network&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;none&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--memory&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;384m&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--pids-limit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;128&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;/usr/bin/python3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-f&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;image&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不使用 EDNS 时，中等集合查询得到四百六十三字节的截断回答，只包含两条 TXT；宣告一千二百三十二字节后，四条完整记录以九百零四字节返回，TC 清除。脚本检查记录数量、消息长度和 EDNS 存在，证明这个具体回答跨过传统限制，同时仍在所选 UDP 预算内。它不是只看查询成功，就假定完整集合已经到达。&lt;/p&gt;
&lt;p&gt;对于更大集合，客户端宣告四千零九十六，但服务器仍限制在一千二百三十二。实测响应只有四十三字节，设置 TC，没有回答记录；随后 TCP 查询取得全部八条，共一千七百五十二字节。截断回答不一定填满允许的缓冲区，记录分组和响应构造可能产生很短的“还需要完整交换”信号。因此响应短也不能直接等同于完整数据很少。&lt;/p&gt;
&lt;p&gt;程序还对这个未签名区域发出带 DO 的查询。回答保留 DO，却没有 RRSIG，也没有表示认证数据的 AD 标志。这符合预期：请求 DNSSEC 数据不会凭空产生签名或信任链。实验分别记录各标志，不把出现 DO 的交换称为验证成功。需要判断认证状态时，应检查实际验证路径，而不是只找到了某个听起来与安全有关的位。&lt;/p&gt;
&lt;p&gt;最后，查询宣告 EDNS 版本一，服务器返回 BADVERS，并指示支持版本零。库从完整消息中解码扩展响应码。这个失败对照将不支持的扩展版本，与名称不存在或普通成功空回答区分开，也说明解释扩展相关错误时应保留 OPT。仅查看基础响应头的一部分，可能无法还原服务器真正表达的错误种类。&lt;/p&gt;
&lt;h2 id="测量实际交换不假定统一默认值"&gt;测量实际交换，不假定统一默认值&lt;/h2&gt;
&lt;p&gt;记录客户端真实宣告、服务器配置、响应大小、标志和传输方式。不同软件版本和运维策略可能采用不同载荷值，一千二百三十二或四千零九十六都不是标准保证的统一默认。本例的较小值是明确实验配置。回环结果没有测量分片、远端路径 MTU 或公共解析性能，不能把局部可交付性直接外推到所有客户端网络。&lt;/p&gt;
&lt;p&gt;诊断大回答失败时，通过同一预期端点比较小查询、较大 EDNS 查询和完整 TCP 查询。区分错误类别：TC 表示还需取得完整数据，BADVERS 表示扩展版本不一致，超时则不能直接指出哪一环丢包。保留足够响应信息，避免把这些不同故障压成“DNS 挂了”。只有分类准确，下一步检查的配置和网络路径才有明确对象。&lt;/p&gt;
&lt;p&gt;使用部署实现支持的大小与重试策略，再从重要客户端网络验证。即使大多数观察到的回答都能放入 UDP，也应保留 TCP 访问。EDNS 改善扩展能力，并可能减少部分截断重试，但完整交付仍依赖传输行为、服务器限制和客户端对实际回答的处理。选择值之后，应通过真实结果确认效果，而不是只以配置项存在作为完成依据。&lt;/p&gt;
&lt;h2 id="延伸阅读"&gt;延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/zh/notes/caa-records-should-authorize-the-issuer-you-actually-use/"&gt;CAA 记录应授权你实际使用的签发者&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/zh/notes/dns-changes-need-a-cache-aware-rollback-window/"&gt;DNS 变更需要一个感知缓存的回滚窗口&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6891.html"&gt;RFC 6891 extension mechanisms for DNS&lt;/a&gt;，OPT、载荷容量宣告、扩展错误与 EDNS 版本处理，已核验 2026-09-12。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7766.html"&gt;RFC 7766 DNS over TCP&lt;/a&gt;，TCP 仍然是必需的 DNS 传输方式，已核验 2026-09-12。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bind9.readthedocs.io/en/v9.18.49/reference.html"&gt;BIND 9.18.49 reference&lt;/a&gt;，edns-udp-size 与 max-udp-size 配置，已核验 2026-09-12。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>