有关GFW阻断的个人观察
前言
本文是对GFW的个人观测结果,受限于个人测试环境和信息获取渠道,仅作为个人观点
作为一个计算机学生,对于科学上网是既必须又无奈的需求。不得不承认国外的资料确实多且全。最近看到了 XTLS BBS 里面提到了有关使用 ECH + DoH 的讨论,以及由于这个帖子启发而自己手搓的worker的DoH涉及到了大量对GFW策略的试探。本文不对绕过GFW做叙述,只分享一些GFW常用的阻断策略。
当前 GFW 使用的阻断方式总的可以归于 3 类:DNS 污染、SNI 阻断以及 IP 黑洞。其中,使用 ECH + DoH 可以有效避免前两者的影响,但不能对后者抗干扰。据我所知,IP 黑洞应该是无解的。
声明
这个算是一个比较敏感的话题,所以我先声明一下几点内容:在这篇文章里,我不会提及任何关于突破内容审查机制的关键词,不介绍、传播、扩散任何关于突破内容审查机制的工具。任何技术探究均在个人的局域网中完成,使用的信道均为国家网络运营商提供的合法有效的信道。
友情提示:GFW在2021年11月中国国家互联网信息办公室发布的《网络数据安全管理条例(征求意见稿)》中被称为跨境安全网关,用于阻断访问境外反动网站和有害信息、防止来自境外的网络攻击、管控跨境网络数据传输、防范侦查打击跨境网络犯罪。擅自使用、搭建国际信道涉嫌违法,并有因此而被逮捕的事例,请各位慎重行事。
DNS 污染
我们在访问网站的时候,多数使用的是域名访问(我至少没看到谁闲的无聊用 IP 访问网站的)。在计算机网络中,计算机与计算机之间只能使用 IP 相互访问,域名的设计是为了让人类好记。人类是好记了,那机器怎么办?
于是就出现了 DNS 这个东西。DNS 负责将域名转换为 IP 返回给计算机,再让计算机访问那个 IP。比如说我要访问 baidu.com,那么会先在 DNS 上查询 baidu.com 的 A / AAAA 解析的 IP,然后计算机访问这个归属于百度的 IP。
那么假如说某一个用户尝试访问 google.com,按照 DNS 的标准理应返回 google.com 的 A / AAAA 解析,但是由于众所周知的原因,google 配不上东大。于是 GFW 搞了个 DNS 污染:当用户访问不被允许的网站的时候,DNS 直接返回一个错误的 IP,比如某个 DNS 非常的喜欢返回 localhost(感兴趣的可以查查)。由于客户端没有识别机制,那么计算机就会尝试去访问那个错误的 IP,很明显并不会有返回,那么这样最简单的 DNS 污染就实现了。
那么就有人要问了:换一个没有被污染的 DNS 不就可以了吗。早期确实是这样的,但是现在 1.1.1.1、8.8.8.8 还有 9.9.9.9 会被劫持到运营商的 DNS 上,同 IP 的 DoH 倒是没有被劫持,可能与 GFW 的逻辑有关。
SNI 阻断(RST阻断)
如果你能成功解决 DNS 污染的问题(比如用 DoH),恭喜你,你来到了第二关:SNI 阻断。现在大部分网站都上了 HTTPS,而 HTTPS 依赖于 TLS 握手。在 TLS 握手的第一个包——ClientHello 中,客户端会告诉服务器自己要访问哪个域名。这个字段就叫 SNI(Server Name Indication)。问题在于,TLS 1.2 及以下的 ClientHello 是明文传输的。也就是说,GFW 不需要解密你的流量,只需要读取 ClientHello 里的 SNI 字段,就知道你要访问哪个网站。
一个 IP 上可能托管了几百个网站(比如 Cloudflare 的 CDN),SNI 就是用来告诉服务器「我要访问的是你托管的那几百个网站里的哪一个」。(一部分 CDN 厂商的策略如
cf导致了其优化解析会非常简单,由于篇幅原因暂且不表)
如果说你尝试访问 google.com,那么你的数据包会经历那么几个环节:
- 客户端发送
ClientHello,其中SNI = google.com - GFW 的 DPI(深度包检测)设备看到这个 SNI
- 匹配到黑名单 → GFW 向客户端和服务器同时发送伪造的
TCP RST包 - 连接被重置,浏览器显示
ERR_CONNECTION_RESET
这个过程非常快,通常在 TLS 握手完成之前就结束了。而且由于是双向 RST,即使客户端忽略 RST 继续发包,服务器那边也已经断开了。
为什么 SNI 阻断比 DNS 污染更难搞
DNS 污染好歹可以通过用 DoH / DoT 来解决——毕竟只是换了个地图。但 SNI 阻断不同,它是直接在你和服务器之间的路上设卡,你不管问谁的路,最后走到这条路上都会被拦并接受检查。
不过有个细节值得注意:GFW 目前主要检查的是 TLS 1.2 及以下的 ClientHello。如果你用 TLS 1.3,情况会稍有不同,因为 TLS 1.3 支持 ECH(Encrypted ClientHello)。
ECH 的全称是 Encrypted ClientHello,由 Cloudflare、AWS 几个互联网厂商制定的 DNS 规则。它的核心思路很简单:既然 GFW 是通过明文的 SNI 来判断你要访问哪个网站的,那把 SNI 加密了不就完了?
ECH 会把真正的 ClientHello(含 SNI)用服务器提前公布的公钥加密,外面再套一层明文的掩护 SNI——一般是 CDN 的泛域名,比如 cloudflare-ech.com。GFW 看到的就是一个去 Cloudflare 的普通握手包,真正的目标域名藏在加密层里,它无从得知。
以 X 为例,当向 X 发送
ClientHello时,GFW 会收到一个发往cloudflare-ech.com的握手包,目标 IP 是 Cloudflare 的节点。此时对于 GFW 来说并不能判断出数据包是发往 X。而 X 的边缘节点正好有部署在 Cloudflare,所以此时 Cloudflare 的节点就会解包并转发到 X 的服务器。
但是 ECH 有几个现实问题:
- 需要服务器支持:服务器必须提前公布 ECH 公钥(通常通过 DNS
HTTPS记录发布),客户端才能加密ClientHello。目前支持 ECH 的网站还不算多,但是目前已知 Cloudflare 和 Meta 已开启 ECH 的支持。 - 需要客户端支持:浏览器要开启 ECH。Chrome、Firefox 都支持,但需要手动开启或者等默认启用。
- GFW 可能直接封 IP:如果你访问的 IP 本身就在黑名单里,即使 SNI 加密了也没用,比如说访问 Facebook 时不小心解析到了被墙的 IP——这就是下面要说的 IP 黑洞。
ECH + DoH 的组合
DoH 解决 DNS 层面的污染,ECH 解决 SNI 层面的阻断。两者结合,理论上可以绕过 GFW 的前两道防线。这也是开头提到的那个 XTLS BBS 帖子的核心思路。
但这里有个坑:DoH 的域名本身可能被 SNI 阻断。如果你用 dns.google 作为 DoH 服务器,那客户端在连接 Google DNS 的时候发的 ClientHello 里 SNI 就是 dns.google——这不又被拦了?所以实际操作中需要选一个没被墙的 DoH 域名,或者在 Cloudflare Workers 上自建 DoH。
我设计 DoH 思路就是将支持 ECH 的网站,使用各种方式获取对应网站 ECH 字段并植入到返回的 DoH 请求中,从而实现对大部分网站的 ECH 支持。
当然这个方案也有问题,从流量特征的角度来说,其由于把 ECH 和外层 SNI 放到同一个包中,MTU 会变得巨大以至于可以轻松地检测出其是否使用了 ECH。
人家GFW只是懒得管不是看不见,该工具为学习使用,请勿滥用
IP 黑洞
即使你完美解决了 DNS 污染和 SNI 阻断,你还有一个绕不开的问题:你总得和服务器建立连接。而建立连接就需要知道对方的 IP 地址,并且向这个 IP 发包。
GFW 维护了一个庞大的 IP 黑名单。当你的数据包的目的 IP 在黑名单里时,GFW 的边界路由器会直接丢弃这些包——不给你任何回应,不发 RST,什么都不发。从你的视角看,就是连接超时。
这比 SNI 阻断更底层、更暴力,因为它不依赖协议层面的特征,只看 IP。
SNI 阻断可以用 ECH 加密,DNS 污染可以用 DoH 绕过。但 IP 黑洞不同——IP 地址是网络层的寻址基础,你没法「隐藏」目的 IP。就好像你寄信总得写收件人地址,不可能把地址也加密了。
据我所知,目前在单机层面没有直接绕过 IP 黑洞的办法。你不可能让 GFW 的路由器不检查你的目的 IP。
据不可靠渠道所说,IPv6 的阻拦会比 IPv4 小不少,具体原因未知,但是似乎确实有这个情况。
以上均为个人观察总结,受限于测试环境与信息渠道,部分细节可能与实际情况存在偏差。技术本身是中立的,本文仅讨论技术原理,不构成任何操作建议,请遵守中国大陆法律。
一些个人的话(26.7.11)
ECH这个东西跟WG有点像,属于是比较的安全但是特征及其明显,我也是无语了RFC给的流量隐藏标准竟然是让所有流量都带上ECH?!
属实是嫌GFW不知道你是ECH的重度用户了😂
“The client offers ECH if it is in possession of a compatible ECH configuration and sends GREASE ECH otherwise.”
“客户端持有兼容的 ECH 配置时发送真实 ECH;否则发送 GREASE ECH。”
而且通过DoH+ECH的方法有很大的弊端,就是你先要有一个可以直接使用的DoH端点然后才能获取ECH密钥。
那么如果说DoH被污染了呢?
先不说比较困难的SNI阻断(RST阻断),光光DNS污染就够喝一壶的了。如果你要使用这个DoH端点,首先客户端会去53端口上的明文DNS上查询这个DoH端点,假如说这一步就被拿下那连连接都没建立就死了。
如果要抗这个DNS污染那么你就要换一个干净的DNS或者在本地写好DoH的初始ip。前者需要用一个干净的DNS去访问另一个DoH的端点,我都有干净的DNS了我用什么DoH啊?,后者需要改host,配置比较麻烦倒也不是不行。
如果DoH端点还上了SNI阻断那更完蛋:要么上ECH,要么上sni前置伪装。前者跟之前一样是套娃,后者还要为这个东西开tun接管流量,专门搞一个客户端,感觉相当麻烦。
整个流程下来问题还是很多的。把这个做好直面GFW我只能说
还是代理方便