加密 DNS 主机名:dns.23-4.cn| DoH 地址:https://dns.23-4.cn/dns-query| 配置教程 | 实时战报
用了加密 DNS,查询内容确实没人能看了。但你有没有想过:你的解析器把什么信息交给了下一跳?
这里有个不太被讨论的机制叫 ECS(EDNS Client Subnet)。简单说:你的解析器在向上游权威服务器查询时,可能附带上你的 IP 网段(通常是 /24,IPv6 是 /56),好让 CDN 返回离你最近的节点。
它不是泄露——因为接收方是你的解析器指定的上游,不是运营商。但它确实把你的位置信息往下游传了一层,而且大多数人不知道自己用的 DNS 传不传。
这篇给一套可执行的判定方法,以及一个反直觉的结论:加密不等于不带 ECS,带了 ECS 也不等于不安全。下面所有输出都是我在一台 Ubuntu 20.04(dig 9.18.30)上实际跑出来的。
一、先搞清楚 ECS 想解决什么问题
不带 ECS 时,权威服务器只能看到递归解析器的出口 IP。假设你的解析器在 A 地,你在 B 地,那么:
- 权威服务器(比如某个 CDN)认为「请求来自 A 地」;
- 于是返回了 A 地附近的节点;
- 你实际在 B 地,被调度到了一个绕远的节点。
这就是「用公共 DNS 后访问变慢」的经典原因之一。
带 ECS 时,解析器会告诉权威服务器:「请求来自 1.2.3.0/24 这个网段」。权威服务器就能返回那个网段附近的节点。
代价:你的网段信息被传下去了。
二、怎么测一个 DNS 到底传不传 ECS
dig 有一个 +subnet 参数,可以携带 ECS 选项。如果服务端回显了这个选项,说明它处理了 ECS;如果不回显,说明它忽略了。
测本站的加密 DNS(DoT)
dig +tls +subnet=1.2.3.0/24 @dns.23-4.cn example.com | grep -iE "CLIENT-SUBNET|EDNS"实测输出:
; EDNS: version: 0, flags:; udp: 1232只有 EDNS 段,没有 CLIENT-SUBNET 行。 说明这个服务没有回显 ECS——它把我的 /24 丢掉了。
作为对照,换一个会回显的公共 DNS:
dig +subnet=1.2.3.0/24 @223.5.5.5 example.com | grep -iE "CLIENT-SUBNET|EDNS"实测:
; EDNS: version: 0, flags:; udp: 1232
; CLIENT-SUBNET: 1.2.3.0/24/24多了一行 CLIENT-SUBNET。
这一行值得拆开看:1.2.3.0/24/24 是三个字段:
| 字段 | 值 | 含义 |
|---|---|---|
| 第 1 段 | 1.2.3.0 | 网段地址(我传进去的) |
| 第 2 段 | /24 | 源前缀长度(我传进去的) |
| 第 3 段 | /24 | 作用域前缀长度(解析器决定往下传多少) |
第三个数字最关键——它才是解析器「愿意暴露多少」的实际值。你传 /24,它可能只传 /24、也可能截短到 /16,甚至传 /0(等于不带)。
这个第三个数字,我实测出了它的真实作用
故意传一个更长的前缀,看它会不会被截短:
dig +subnet=1.2.3.4/32 @223.5.5.5 example.com | grep CLIENT-SUBNET实测:
; CLIENT-SUBNET: 1.2.3.4/32/24我传的是 /32(精确到单个 IP),但回显的第三个字段是 /24。
这说明解析器把我的 /32 截短到了 /24 才往下传。 它在保护我——即使客户端(或某个上游)想暴露精确 IP,它也只放行到网段级别。
再传一个更短的,看会不会被拉长:
dig +subnet=1.2.0.0/16 @223.5.5.5 example.com | grep CLIENT-SUBNET实测:
; CLIENT-SUBNET: 1.2.0.0/16/24我传 /16,回显是 /16/24。第三个字段还是 /24——但注意:第二个字段(我传的)是 /16,第三个(作用域)是 /24。
这两个数字的含义不同:
- 第 2 段
/16= 「我告诉你我在1.2.0.0/16里」; - 第 3 段
/24= 「你可以按/24的粒度来对待我」。
作用域比源前缀更长(/24 > /16)是合法的——意思是「你可以假设我更精确到这个程度」。真正防止暴露的是源前缀:我传 /16,权威服务器就只知道 /16。
⇒ 关键结论:/32 会被截到 /24,但 /16 不会被拉到 /24。所以「传更短的」是安全的,「传更长的」要看解析器是否截断。
IPv6 同理:
dig +subnet=2001:db8::/56 @223.5.5.5 example.com | grep CLIENT-SUBNET实测:
; CLIENT-SUBNET: 2001:db8::/56/64传 /56,作用域是 /64。这说明 /56 是「源前缀」,/64 是「作用域」——和 IPv4 的 /24 是同一个位置的角色。
这就是判定 ECS 的可执行判据:同一个参数打两个 DNS,一个回显 CLIENT-SUBNET 一个不回显,差别一目了然,比看任何文档都直接。
三、/24 和 /56 分别暴露了什么
IPv4 的 /24 是 256 个地址;IPv6 的 /56 是 2^(128-56) = 2^72 个地址。
看起来 IPv6 的 /56 大得离谱,但实际上:
| 协议 | 常见 ECS 长度 | 相当于 | 能定位到 |
|---|---|---|---|
| IPv4 | /24 | 256 个地址 | 一个小区/一栋楼 |
| IPv4 | /32 | 1 个地址 | 你自己 |
| IPv6 | /56 | 2^72 个地址 | 一个运营商的一个片区 |
| IPv6 | /64 | 2^64 个地址 | 一个子网(通常一户) |
| IPv6 | /0 | — | 不暴露(等于关闭) |
关键点:/24 和 /56 不是「一样安全」的两种选择,它们定位精度差得很远。
- IPv4
/24→ 256 个地址,能定位到一个接入点; - IPv6
/56→ 虽然地址数巨大,但 IPv6 的分配策略通常按「一个片区一个 /56」,所以它同样能定位到一个片区。
所以规范(RFC 7871)给的建议是:ECS 应该使用 /24(IPv4)和 /56(IPv6)作为默认上限,不要传更长的前缀。但现实中也有解析器传 /32——那就是把你的确切 IP 交出去了。
想主动关掉,用 /0:
dig +tls +subnet=0.0.0.0/0 @dns.23-4.cn example.com | grep -iE "CLIENT-SUBNET|EDNS"实测:
; EDNS: version: 0, flags:; udp: 1232/0 的语义是「我的网段是 0.0.0.0/0」,等于告诉解析器「别带我的任何前缀」。
四、三个常见误解
误解 1:「用了加密 DNS 就没有 ECS 了」
不成立。 加密和 ECS 是两个正交的维度:
| 不带 ECS | 带 ECS | |
|---|---|---|
| 明文 DNS | 运营商能看到全部查询 | 运营商能看到查询 + 你的网段 |
| 加密 DNS | 运营商看不到查询 | 运营商看不到查询,但上游能看到你的网段 |
加密解决的是「链路上谁能看到」,ECS 决定的是「下游知道多少」。两者可以任意组合。
上面的实测就是证据:dns.23-4.cn 是加密的(DoT),且不带 ECS;223.5.5.5 是明文的,且带 ECS。但也存在加密且带 ECS 的服务,只是我没在这台机器上遇到。
误解 2:「ECS 是泄露」
不是。 泄露(leak)的定义是「查询到了你没授权的解析器」。ECS 里的接收方是你自己选的解析器的上游,是授权链路的一部分。
真正的区别是告知范围:
- 不带 ECS → 上游只知道「某个解析器问了我」;
- 带 ECS → 上游知道「某个解析器替
1.2.3.0/24问我」。
这是精度问题,不是授权问题。把它叫「泄露」会让讨论失焦。
误解 3:「关掉 ECS 一定更好」
不一定。 关掉 ECS 的代价是调度精度下降:
- 用 CDN 的站点,你可能被调度到远端节点,延迟上升;
- 某些按地域做负载均衡的服务,会给你返回错误的节点。
所以选择是:
| 你的优先级 | 建议 |
|---|---|
| 隐私优先(不想暴露任何位置) | 关 ECS(/0),接受可能的调度偏差 |
| 速度优先(CDN 命中率重要) | 保留 /24 / /56,这是规范推荐的上限 |
| 折中 | 只在特定域名上带 ECS,其余关掉(需要解析器支持按域名配置) |
⛔ 绝对不要用 /32 或 /128 ——那等于把你的确切 IP 交出去,没有任何性能收益能抵消这个代价。
五、自己验证的三条命令
# 1. 你的加密 DNS 传不传 ECS(回显 CLIENT-SUBNET 就是传)
dig +tls +subnet=1.2.3.0/24 @dns.23-4.cn example.com | grep -iE "CLIENT-SUBNET|EDNS"
# 2. 对照组(一个已知会回显的公共 DNS)
dig +subnet=1.2.3.0/24 @223.5.5.5 example.com | grep -iE "CLIENT-SUBNET|EDNS"
# 3. 主动关闭(/0)
dig +tls +subnet=0.0.0.0/0 @dns.23-4.cn example.com | grep -iE "CLIENT-SUBNET|EDNS"怎么读结果:
- 出现
CLIENT-SUBNET: x.x.x.0/NN/NN→ 服务处理 ECS,看第三个数字判断实际传多长; - 只有
EDNS没有CLIENT-SUBNET→ 服务忽略 ECS(你的网段没被传下去); - 报错或超时 → 该服务不支持 ECS 选项,或者你打错了端口。
验证做对了没有:第 1 条和第 2 条的结果必须不同。如果两条输出一模一样,说明你的 dig +subnet 根本没生效(老版本不支持这个参数),换个版本或者用 kdig。
六、为什么这件事值得单独讲
大部分人配加密 DNS 时只关心一件事:查询内容有没有被看到。这当然重要,但它只是隐私的一个维度。
完整的图景是三层:
- 链路层:谁能看到你的查询 → 由加密(DoT/DoH/DoQ)解决;
- 内容层:查询内容被记录多久 → 由服务商的日志政策决定;
- 元数据层:你的位置/网段信息传了多远 → 由 ECS 决定。
三层里最容易忽略的是第三层,因为它不体现在任何一个「安全」字样上,也没有任何界面会提示你。但它恰恰是唯一一条能直接把「一个 IP」关联到「一个地理片区」的信息。
而它的判定方法简单到只有一条 dig +subnet 命令——这就是为什么值得单独写一篇。