解决家庭宽带443端口封禁问题
涛叔现在的运营商会主动封禁 80 和 443 端口,防止用户使用家庭宽带对外提供 WEB 服务。我们理论上可以使用非标准端口(比如 8080 或者 4430)。但不管你用哪个,只要哪天运营商注意到你,加一条规则就可以让你所有的链接都失效(URL 中有端口号)。这才是端口封禁最大威力。所以大家没法用家庭宽带建站,哪怕是一个简单的博客也不行。就我观察,这个事情有了转机。机会就在 HTTP/3 协议。今天就跟大家介绍一下如何利用 HTTP/3 协议最新的标准来突破端口封禁问题。
先简单说一下 HTTP/3 协议。HTTP 协议从诞生一直到 HTTP/2,都是依赖 TCP 传输数据。TCP 协议提供面向连接的可靠数据传输能力。但 TCP 有一个小缺点,所有的数据都是按顺序发送,按顺序确认。简单来说,前面的数据如果没有收到对方的确认包,后面的数据就得一直等待。HTTP/2 引入了流的概念,可以在一个 TCP 连接上同时发起多个 HTTP 请求。但在 TCP 层,所有的数据还是要排队发送,这就大大弱化了流的功能。这个问题英文叫作 Head-of-line blocking(HOL blocking) ,有兴趣的同学可以继续谷歌。
HTTP/3 从某种程度上就是为了解决 HOL 问题。其实也不是 HTTP/3 解决的,而是 HTTP/3 底层的 QUIC 协议解决的。QUIC 跟 TCP 类似可以实现数据的可靠传输。但是 QUIC 使用 UDP 协议,不同的逻辑流不会相互影响,底层数据在发送的时候不需要排队。这就解决了 HOL 问题。QUIC 还提供比 TCP 更快的连接握手机制,内置 TLC 加密等等,本文不再展开。后面会写一篇完整介绍 QUIC 协议的文章,敬请期待。
到现在,我们讲到了一个重点,HTTP/3 使用 UDP 通信,HTTP/1 和 HTTP/2 使用 TCP 通信。那么问题来了,当我们在浏览器输入网址按回车的时候,浏览器怎么确定要用 HTTP 协议的哪个版本来通信呢?浏览器要怎么确定是用 TCP 还是 UDP 呢?
其实在引入 HTTP/2 就有这个问题了。但 HTTP/2 跟 HTTP/1 都使用 TCP 连接,问题还不是很大。HTTP/2 也强制使用 TLS 加密(理论上也支持明文通信,但浏览器都没有实现)。TLS 协议本身支持所谓的 ALPN 功能,全称叫应用层协议协商。也就是说,如果浏览器支持 HTTP/2 协议,它会在创建 TLS 连接的时候给服务器发送 h2 标识,如果服务也支持,则双方就会继续使用 HTTP/2 通信;如果不支持,则降级回 HTTP/1 协议。因为都是用 TCP + TLS,这种降级不会带来性能问题上。
到了 HTTP/3 就不一样了。HTTP/3 依赖 QUIC,使用 UDP 通信。如果浏览器认为服务端支持 HTTP/3,那就会开始创建 QUIC 会话。那要用 UDP 的哪个端口呢?先用默认 443 端口吧。但现在 HTTP/3 的标准化还没正式发布,肯定很多站点不支持 HTTP/3 协议。那创建 QUIC 会话肯定会超时。这个时候浏览器再尝试建立 TCP 连接使用 HTTP/2 通信就有点太晚了。不能忍。
怎么办呢?现阶段只能假设网站默认支持 HTTP/2 或者 HTTP/1 了。也就是说,浏览器一上来还是要发起 HTTP/1 或 HTTP/2 请求。如果网站还支持 HTTP/3,那么应该在返回响应信息的时候附带上 UDP 端口信息。这就要用到 Alt-Svc 这个头信息(由rfc7838定义)。一个典型的 Alt-Svc 头是这样的:
alt-svc: h3-28=":443"; ma=86400, h3=":443"; ma=86400
h3-28
和 h3
表示 HTTP/3 服务,-28
表示 HTTP/3 标准草案编号,正式发布之后就不需要了。等号后面会指定 HTTP/3 服务的主机和端口。:443
表示使用使用 UDP 的 443 端口。后面的 ma
表示 max age,浏览器会根据这个值缓存 Alt-Svc 信息。等下一次需要发起 HTTP 请求的时候,浏览器就开始使用 HTTP/3 协议通信了。
这里同样有两个重点:第一,在发起 HTTP/3 通信之前至少要发起一次 HTTP/2 通信(后面可能会缓存 Alt-Svc 信息);第二,HTTP/3 的端口是动态的,需要根据服务端返回的信息动态确定。
到这里,我们发现了 HTTP/3 与老版本协议的一个根本区别,它的端口是动态变化的,需要通过 Alt-Svc 来发现。这就为我们突破端口封锁提供了一种可能。说白了,HTTP/3 可以工作在任意端口,你怎么封?
但有心的读者会注意一点,发起 HTTP/3 通信之前至少要发起一次 HTTP/2 通信。问题又绕回来了。HTTP/2 使用 443 端口,已经被运营商屏蔽了。所以到现在为止 HTTP/3 动态端口的优势根本发挥不出来。也是因为发起 HTTP/3 之前需要发起一次 HTTP/2 调用,这也增加了网页首次打开的耗时。为此,人们又开始研究新的解决办法。这个办法就是 DNS SVCB/HTTPS 记录。我之前写过一篇 DNS SVCB/HTTPS 记录介绍,请移步阅读。下面假设大家对 SVCB 有基础的认识。
首先,我们需要一个纯 HTTP/3 服务器。为了演示,我用 go 写了一段程序:
package main
import (
"flag"
"net/http"
"github.com/lucas-clemente/quic-go/http3"
)
var root, cert, key, addr string
func init() {
.StringVar(&root, "root", "", "static server root")
flag.StringVar(&cert, "cert", "", "cert file path")
flag.StringVar(&key, "key", "", "key file path")
flag.StringVar(&addr, "addr", "", "listen addr")
flag}
func main() {
.Parse()
flag:= http.FileServer(http.Dir(root))
fs .ListenAndServeQUIC(addr, cert, key, fs)
http3}
编译后执行
./h3 -cert 证书文件 -key 私钥文件 -root 根目录 -addr :4430
程序会在 UDP 的 4430 端口等待 HTTP/3 请求。
证书可以通过 letsencrypt 签发。我用的域名是 h3.taoshu.in,通过路由器的 DDNS 配置(家用宽带的公网IP会变)。因为 443 端口被封,必须用 DNS 的方式来验证域名所有权。我用的是lego(Go语言开发,方便交叉编译到路由器运行)。我的 DNS 解析平台是 cloudflare。执行下面的命令:
CLOUDFLARE_DNS_API_TOKEN=DNS令牌 ./lego \
--email 邮箱 \
--dns cloudflare \
--domains '*.taoshu.in' run
最终生成如下文件:
ls .lego/certificates/|grep _
_.taoshu.in.crt
_.taoshu.in.issuer.crt
_.taoshu.in.json
_.taoshu.in.key
_.taoshu.in.crt
就是证书文件,_.taoshu.in.key
就是私钥文件。最终启动 HTTP/3 服务器:
./h3 \
-cert .lego/certificates/_.taoshu.in.crt \
-key .lego/certificates/_.taoshu.in.key \
-root web -addr :4430
然后需要设计 DNS HTTPS 记录了。我用的是 cloudflare,对新标准支持的非常友好,可以直接在界面设置 HTTPS 记录。最终添加的 HTTPS 记录如下:
h3.taoshu.in. 7200 IN HTTPS 1 . alpn=h3,h3-29 port=4430
对应 cloudflare 的界面输入框分别是:
- Type: HTTPS
- Name: h3
- TTL: Auto
- Priority: 1
- Target: .
- Value: alpn=“h3,h3-29” port=“4430”
这里的 Target 设成了.
,意思是 h3.taoshu.in 的服务器域名就是 h3.taoshu.in 本身。其实也可以指向其他域名(功能类似 CNAME),在此不展开讨论了。
Value 中的 port 指定了端口为 4430。
一切就绪,现在需要验证一下 HTTP/3 服务是否正常。经过我测试,Firefox 浏览器支持的最好,但需要修改几个配置(iOS 15 的 Safari 也是支持的,但要打开 HTTP/3 实验特性)。在地址栏输入about:config
按回车。将以下配置改为true
:
- network.dns.upgrade_with_https_rr
- network.dns.use_https_rr_as_altsvc
然后开启 DNS over HTTPS(DoH),服务商选默认的 cloudflare 就行。如果无法访问,也可以选自定义,输入
https://doh.pub/dns-query
最后,在地址栏输入 https://h3.taoshu.in:4430 ,如果你看到hello h3
,那就成功了一大半了。然后可以在地址栏输入https://h3.taoshu.in,应该也能看到hello h3
。注意,这次没有端口号。
好了,总结一下技术要点:
- 启动 HTTP/3 服务器,监听非标端口 4430
- 设置 HTTPS 类型的 DNS 记录,通过 port 指定端口,通过 alpn 指定 h3 协议
- 开启 Firefox 的 https 记录相关配置
- 开启 Firefox 的 DoH 配置
- 访问 https://h3.taoshu.in
有了 DoH + HTTPS 记录这套组合拳,我们可以在任何端口提供 HTTP/3 服务。运营商再也没法封锁了。但后面政府肯定会对 DNS 服务特别是 DNS over HTTPS 加强监管,弄不好也得搞备案制。但无论如何,这是一个巨大的进步。
另外,IETF已经收到了DNS SVCB标准的发布请求,估计今年就会正式发布。HTTP/3 + DNS SVCB 会很快普及,其他浏览器也会逐渐支持。期待。