聊聊那些翻山越岭的神奇操作(下)-前篇
这是“翻山越岭”系列的第三篇文章,首先拖更致歉,各位久等了(,没看过前两篇的朋友欢迎移步这里:上篇,中篇
本人并非计网方面的专家,许多相关知识均从不同渠道获得,对于一些比较细化的方面也没有细致地了解过。下篇本身预计的内容对笔者来说相对比较陌生,需要阅读一定的资料,加上事务繁多,因此一直都没有动笔。不过,近期笔者发现了另一种比较有趣的翻山越岭思路(其实去年就产生了这方面的想法,但直到最近才付诸行动),因此临时加更一篇,这也是为什么本篇名为(下)-前篇,原本预计的内容将放在后篇。
本篇内容基本为中篇的延伸,而且偏实践,因此可能不需要过多专业知识即可读懂本篇文章。
回顾
经过前两篇文章的介绍,各位应该了解到,当下若想绕过防火墙对网站的封锁,基本需要满足以下三点:
- 需要能获取到网站对应的正确 IP
- 网站对应的 IP 不能被封锁
- 需要绕过防火墙的 SNI 阻断
其中 1 可以通过境外的 DoT 或 DoH 服务解决(不过就目前的情况而言,你大概率需要通过代理连接到这些服务器或是自行搭建解析服务)。若网站使用 AnyCast 技术,则可以从已公布的 IP 范围内扫描并筛选出可用的 IP 以解决 2。对于 3,前两篇文章介绍了三种绕过方法:域前置,QUIC 和 ECH。这三种方案均需要网站支持,对于域前置则还需要对加密流量进行 MitM 以达到修改 SNI 的目的。
将目光转向 QUIC 和 ECH,虽然作为新兴的技术尚未得到普及,但我们依然能利用这两项技术做一些有趣的事情。
HTTPS RR
在进行以下实践之前,有必要详细了解一下这个比较新的 DNS 记录类型:HTTPS
从 HTTP/0.9 到 HTTP/3,HTTP 协议在速度,效率,甚至底层传输协议上都有了翻天覆地的变化。随着 HTTP 协议的不断发展,一个问题浮现出来:客户端和服务端要怎样得知对方支持哪些协议?多年以来,人们一直在探究 HTTP 协议协商的最佳方案。目前广泛流行的协商方案有:TLS ALPN 扩展(RFC 7301)和 HTTP Header 中的 alt-svc 字段(RFC 7838)。前者通过 TLS Client Hello 握手包中的扩展(和 SNI 相同的位置)告知服务端客户端可用的协议,后者则通过 HTTP 头告知客户端服务端可用的协议(以及端口和缓存时间等信息)。其中前者可由服务端挑选出自身支持的协议,并在后续握手流程中告知客户端最终选定的协议;而后者则可使客户端在后续连接中升级到更新的 HTTP 协议。
这一套协商流程有什么问题?由于 HTTP/2 及之前的协议均基于 TCP,因此这套协商流程工作十分良好。但 HTTP/3 协议基于 UDP,因此无法在使用 TCP 连接的时候直接升级到 HTTP/3,只能新建一条连接。若要通过TLS ALPN 扩展协商,则意味着前期所做的 TCP 与 TLS 握手全部白费,要切换到 QUIC 从头再来一遍,非常浪费时间。因此早期,客户端若想使用 HTTP/3 与服务端连接,必须先使用 HTTP/2 或之前的协议连接至服务端,待识别到服务端发来的 HTTP alt-svc 响应头包含 h3 的时候,才能将后续的连接升级至 HTTP/3。
很明显,这样做的效率很低,那要如何才能第一时间让客户端与服务端通过双方支持的最新协议建立连接呢?突破点就在一切连接建立之前的 DNS 查询流程中。只要将服务端支持的协议放入 DNS 记录中,客户端就能通过查询 DNS 得知服务端所支持的协议了。HTTPS 这一记录类型就提供了这一功能,在 RFC 文档中对这一记录类型有这样的描述:
The SVCB (“Service Binding”) and HTTPS resource records (RRs) provide clients with complete instructions for access to a service. This information enables improved performance and privacy by avoiding transient connections to a suboptimal default server, negotiating a preferred protocol, and providing relevant public keys.
为了达成以上所述的效果,HTTPS 记录类型包含了以下几个关键字段:
alpn:服务端所支持的协议列表ipv4hint和ipv6hint:包含服务端所使用的 IP 地址,客户端可直接使用这些地址与服务端建立连接,而不必等待常规的 A/AAAA 查询返回结果[1]ech:一个ECHConfigList类型的数据,提供 ECH 所需的公钥和服务端域名等信息
不难发现,通过查询域名对应的 HTTPS 记录,客户端即可得知服务端支持的协议与是否支持 ECH,无需通过上文提到的协商流程即可直接与服务端发起 HTTP/3 和/或 ECH 连接,减少额外的协议探测与连接切换开销。
QUIC
中篇中提到,虽然 QUIC 在握手阶段不提供机密性,但防火墙在根据 QUIC 连接握手阶段的 SNI 信息阻断连接方面的能力较弱[2]。也就是说,只要我们能与目标网站建立 QUIC 连接,就能绕过防火墙的阻断。为了做到这一点,必须要满足以下两个要求:
- 网站本身必须支持 QUIC
- 浏览器需要知道网站支持 QUIC,并在首次连接就使用 QUIC
对于第一点,虽说 QUIC 作为较新的协议仍未得到广泛支持,但好在部分国外 CDN 厂商已经对 QUIC 提供了支持,如果被封锁的网站部署在这些 CDN 上,就大概率支持 QUIC。那要怎样才能得知网站的支持情况呢?根据上文的介绍,我们可以通过观察网站的 HTTPS 记录和/或 HTTP 响应头中的 alt-svc 字段以确认:

虽说以上两点可以覆盖大多数网站,但依然存在部分支持 QUIC,但并未通过任何方式告知客户端的网站。对于这种情况,我们可以使用 cURL 进行测试[3],以 reddit.com 为例,使用以下指令强制使用 QUIC 与其建立连接:
1 | |

测试发现返回 200 状态码,证明网站确实支持 QUIC
理想状态下,网站应为其域名添加 HTTPS 类型的解析记录并在 alpn 中包含 h3,否则仍然需要先建立 HTTP/2 连接再通过 alt-svc 升级到 HTTP/3。但这种常规的升级对被阻断的网站来说是致命的,毕竟在 TLS 握手阶段防火墙就会精准阻断连接,根本等不到升级那一步。
这种情况下,为了让浏览器直接通过 QUIC 与目标网站建立连接,我们需要自行搭建一个 DNS 转发器,手动为对应网站添加 HTTPS 类型记录,并将浏览器使用的 DNS 服务器指向我们自建的转发器。笔者使用的软件是 MosDNS(当然也有不少类似的软件,例如 SmartDNS)。软件本身虽然没有生成自定义 HTTPS 应答的功能,但可以将域名 A 的请求重定向到域名 B(通过插入 CNAME 记录)[4],正巧笔者之前注册过一个 eu.org 提供的域名,所以笔者的实现方案是:将网站对应的 HTTPS 记录和 A/AAAA 记录添加到自己注册的域名上,查询对应网站的域名记录时直接重定向到这个域名上。这样的优点是:相比一条条写 DNS 解析记录(一个网站通常有多个子域名提供各类资源),这样可以利用软件自带的域名匹配规则为同一域名下的所有子域批量生成对应的 DNS 记录。
说了这么多,接下来就拿几个网站举例
Reddit, Twitch
这两个网站均使用 Fastly 的 CDN 服务,特别注意 Fastly 与 Cloudflare 不同,单个网站有对应的一组 IP,不同网站之间 IP 不可混用。
首先我们要找到这两个网站对应的正确 IP,并根据上文所述创建相应的 DNS 记录,这里我为两个网站创建的地址分别是 reddit.kenxu.eu.org 和 twitch.kenxu.eu.org,创建的 DNS 记录如下图所示:

如果有多个可用的 IPv4 和/或 IPv6 地址也可以添加多个。关于 HTTPS 记录中的 no-default-alpn,RFC 文档中的描述是:
To determine the SVCB ALPN set, the client starts with the list of alpn-ids from the “alpn” SvcParamKey, and it adds the default set unless the “no-default-alpn” SvcParamKey is present.
也就是说,客户端在构建服务端支持的 ALPN 列表时,会包含 alpn 中的值和“默认值”(也就是 http/1.1[5]),通过设置 no-default-alpn 可以排除默认的 http/1.1,从而让客户端认为服务端只支持 HTTP/3。
写到这段的时候正好让AI查到了一位研究员所做的测试,结果显示目前没有任何一款主流浏览器对这个参数提供了支持,也就是说这个参数(连同 HTTPS 记录中的许多其它参数)目前只能起到心理安慰作用,加在这里也没坏处就是了 :)
然后,根据 MosDNS 的文档,我们需要创建一个文件(假设文件名为 redirect.txt)并向其中写入网站对应的域名以及转发到的域名:
1 | |
然后在配置文件中加入以下配置(这里只贴了关键段,如果你需要完整配置,推荐阅读官方文档):
1 | |
启动 MosDNS,查询 Reddit 和 Twitch 域名对应的记录,应当看到查询结果被重定向至对应域名。
如果你的 MosDNS 搭建在本地,记得去系统设置里将 DNS 地址改成 MosDNS 监听的本地地址。[6]
以 Firefox 为例(Chrome 的设置大同小异),在设置中将“HTTPS-Only 模式”更改为“在所有窗口启用 HTTPS-Only 模式”。如果你的 MosDNS 不在本地,你需要为其添加一个 HTTP 监听端口并配置 DoH 服务,之后在“基于 HTTPS 的 DNS”中启用“最大保护”并填入你自己的 DoH 地址。
之后你就可以试着访问这两个网站了,见证奇迹的时刻:
首先我们需要简单了解 Google 系网站服务器的组成,以下是对这篇文章的简要总结。总体来说,Google 大致有以下几类服务器:
- GWS - Google Web Service,提供 Google 系网站服务
- GGC - Google Global Cache,由 ISP 提供,旨在缓存 Google 系网站的资源以加速用户访问
- GVS - Google Video Server,提供 YouTube 上的视频资源分发服务
- GCC - Google China Cache,阉割版的 2,向中国大陆用户提供有限度的服务(例如 Chrome 和 Android Studio 软件包的分发以及广告服务)
1 和 2 的 IP 地址可以混用[7],也就是说,只要找到任意一个未被防火墙封禁的 GWS 或 GGC IP,即可将其套用到所有 Google 系网站上。而 3 则具有严格的对应关系,YouTube 上不同的视频资源不可套用同一个 IP。
作为 QUIC 协议的主要开发者之一,Google 系网站对 HTTP/3 的支持情况如何呢?对于各类服务的域名,均能在 HTTP alt-svc 头中找到 h3 的身影,但 Google 只为其主域名 www.google.com 添加了 HTTPS 记录,其余域名均无相关记录
GVS 的情况则截然相反,可以看得出来 Google 十分想让你在观看 YouTube 视频的时候使用 QUIC
很明显,我们需要仿照上文为 Google 系的网站添加 HTTPS 记录,不过在这之前还有一个问题需要解决:GWS 的 IP 基本已经被防火墙封锁殆尽,如何找到可用的 IP 地址呢?
天无绝人之路,IPv4 被封了,我们还有 IPv6!查询 www.google.com 的 AAAA 记录可得:
1 | |
可得到一系列 IPv6 地址。以第一个地址为例,虽然 DNS 记录的结果指向 2001:4860:4827:7700:0000:0000:0000:0000,但事实上,2001:4860:4827:7700::/64 这一整块地址均可用于访问 GWS,令人惊喜的是,防火墙对于 IPv6 依然采取封禁单个 IP 的策略[8]。也就是说,我们只需要从这个 /64 段中任意挑选一个未被封锁的 IP 加入 DNS 记录中即可。
同样,对于 GVS,虽然 IPv4 地址的情况与 GWS 相同,但 IPv6 大多幸免于难,加上已经存在的 HTTPS 记录,我们甚至不需要做什么就能正常加载 YouTube 上的视频资源。
也就是说,若想使用 QUIC 突破防火墙访问 Google 系网站,你的网络环境最好支持 IPv6
接下来的事情就很简单了,仿照上文的例子添加对应的 DNS 记录,并将以下域名加入 redirect.txt 中:
1 | |
对于 GVS 所使用的域名 *.googlevideo.com,你可能还想让转发器屏蔽该域名的 IPv4 地址,仅返回 IPv6 地址:
1 | |
重启 MosDNS,打开浏览器见证奇迹吧
ECH
很明显以上方法并不适用于所有网站,例如 x.com 就完全无法使用 QUIC 建立连接
在上篇文章中,我们还提到了另一种突破封锁的方案——ECH,只可惜支持这项技术的网站更是少之又少。不过,我们在上篇文章的末尾提到:Cloudflare 免费计划部署的网站默认启用 ECH(且无法关闭),考虑到 Cloudflare 使用的 AnyCast 技术,这起码说明 Cloudflare CDN 的所有端点都是支持使用 ECH 建立连接的。
同时我们注意到一件有趣的事:Cloudflare 上所有支持 ECH 的网站,其 HTTPS 记录中的 ech 字段的值都是一样的
这就让笔者产生了一个大胆的猜测:能否强制使用 ECH 与部署在 Cloudflare 上的网站建立连接?
想要严谨地验证这一点,还是要请出我们的老朋友 cURL,只不过截至文章写作的时点,ECH 相关的功能并未包含于 cURL 的预编译软件包中,你需要自行编译并通过 flag 启用相关功能,详细的教程可以阅读这篇文档,使用编译完成的 cURL 测试,先使用 HTTPS 记录中包含 ECHConfigList 配置的网站进行测试:
可以看到这里 sni 已经是 encrypted 的状态了,接下来我们通过命令行参数手动提供一个 ECHConfigList,对使用 Cloudflare CDN,但未提供 HTTPS 解析记录的 x.com 进行测试:
成功,这说明使用 Cloudflare CDN 的网站即使未在 HTTPS 记录中添加 ech 字段或干脆就没有 HTTPS 解析记录,也可以通过指定相同的 ECHConfigList 建立 ECH 连接。
事实上,笔者并非发现这一点的第一人,至少在去年下半年就有人提出了类似的想法和教程,见这里和这里。简单来说,部署在 Cloudflare CDN 上的网站如果未启用 ECH,Cloudflare 就会删去 HTTPS 解析记录中的 ECHConfigList,但这并不影响客户端使用 ECH 连接到这些网站。这就意味着一件事:所有使用 Cloudflare CDN 的网站其实都支持通过 ECH 连接!
剩下的事就简单了,只要确定被封锁的网站使用了 Cloudflare CDN,仿照上一节的例子使用自己的域名添加对应的 DNS 记录就可以了,这次甚至更简单——无需为每组网站单独创建记录,因为使用 Cloudflare CDN 的网站对应的 A/AAAA 和 HTTPS 记录都是通用的,你甚至无需使用自己的域名——只要找一个部署在 Cloudflare CDN 上且支持 ECH 的网站,并将请求重定向到那个域名即可。唯一一个要注意的细节就是:如何确认网站使用了 Cloudflare CDN?直接查找域名对应的 IP 其实不太准确,因为部分网站考虑到负载均衡和容错等因素可能会将网站部署在多家 CDN 上。
此时我们需要再次请出 cURL,通过强制指定目标网站 IP 为 Cloudflare CDN 的 IP 进行测试

使用 HTTP 而非 HTTPS 协议建立连接,不难发现若网站部署在 Cloudflare CDN 上,返回的状态码会是 301 重定向,反之则会返回 409。
具备以上知识后,我们继续修改 redirect.txt,这里笔者图方便直接将结果重定向到 v2ex.com:
1 | |
根据笔者的测试,在强制使用 ECH 的时候,使用 IPv6 连接到网站的时候会产生错误,因此我们需要屏蔽返回的 IPv6 地址,只让转发器返回 IPv4 地址,你可以从这里下载到 Cloudflare 所有的 IP 地址段(假设保存为 cf.txt),在查询类型为 AAAA 且结果位于这些 IP 段中的时候直接丢弃响应:
1 | |
你还可以优选 Cloudflare IP:
1 | |
重启 MosDNS,打开浏览器访问 x.com,见证奇迹的时刻:
后记
上述所举例子只是一小部分网站,你可以按照笔者在文章中所述的方法测试并添加更多的网站。
对于 ECH,Cloudflare 似乎不会轮换 HTTPS 记录中的 ECHConfigList,这样的好处是简化了我们的配置,坏处是如果将来防火墙屏蔽了目前 Cloudflare 使用的外层 Client Hello 中的 SNI cloudflare-ech.com,强制使用 ECH 建立连接的方法就会失效。
不多说了,祝各位玩的开心:-)
- 这并不意味着客户端不会进行 A/AAAA 查询,事实上,这个参数仅用于降低初始连接的延迟,若本地已缓存域名对应的 IP 地址,或后续 A/AAAA 查询返回了结果,客户端会切换到常规途径查询到的 IP 地址 ↩
- Chrome 和 Firefox 均在 QUIC 握手阶段启用了 SNI 分片功能,SNI 会被分散到多个 UDP 包中,而 GFW 目前不具备重组这些包的能力 ↩
- 旧版 cURL 默认未启用 HTTP/3 支持,需手动编译并启用相关 flag,可使用以下指令检测当前版本的 cURL 是否支持 HTTP/3:
curl --version | grep HTTP3↩ - 详情参考:https://irine-sistiana.gitbook.io/mosdns-wiki/mosdns-v5/ru-he-pei-zhi-mosdns/ke-zhi-xing-cha-jian#redirect ↩
- https://www.rfc-editor.org/rfc/rfc9460.html#section-9-5 ↩
- 自 Firefox v129 开始,在不配置 DoH 的情况下也会进行 HTTPS 记录解析(macOS 除外),详见该版本的 Release Note: https://www.firefox.com/en-US/firefox/129.0/releasenotes/ ↩
- 理论如此(根据 Google 官方文档,GGC 服务对终端用户是透明的),但 ISP 可能会限制仅其内网用户才能访问这些服务器,笔者用这个列表中列举出的一些 IP 测试访问其 443 端口,结果均不可达 ↩
- 使用 cURL 测试:
curl https://www.google.com --resolve www.google.com:443:$IP,会发现受到 SNI 阻断,过一段时间会发现无法 Ping 通此 IP,但该 /64 段中的其他 IP 不受影响 ↩