搭建 Cloudflare 背后的 IPv6 AnyCast 网络
人一穷,鬼点子就特别多。
大家应该知道,本博客使用了 Cloudflare 作为主要的 CDN,可以配置一些 Page Rules 来减轻源站压力,同时隐藏源站地址啥的,此外,Cloudflare 还可以做到防止 DDoS 的效果。
Anycast IP
我们知道,DDoS 作为一个比较 dirty 的攻击手段,从原理上其实并不是很好防御,基本只能通过升级硬件,加大带宽的方式处理,而 Cloudflare 处理 DDoS 的方式比较特别,部分原因是因为他们的边缘节点 IP 是 Anycast IP,这里可能有同学会问了,什么是 Anycast IP?
简单来说,就是同一个 IP 出现在了世界各地,世界各地的主机都是访问到了离自己最近(延迟最低)的主机所宣告的那个相同 IP。
在我们学习了计算机网络之后,我们知道了一个比较容易理解的道理:「IP 地址在互联网上是一台主机唯一的地址」,这样当我们「ping 美国一台主机的 IP」的时候,ICMP 包会通过路由协议,走各种线路,最终到达目的主机,而其中产生的网络延迟很大一部分就是由于距离所导致的,或者说,由于光速也就那样了,所以中美之间肯定会有 100+ms 的延迟,例如,从世界各地到达日本 Vultr 机房的延迟可能如下:
但是如果你是 Cloudflare 的客户的话,你可能会发现追踪到你站点的延迟如下:
是不是发现许多城市到达同一个 IP 的延迟都非常低?这个就是 Anycast 的魅力了,想要了解更多关于 Cloudflare Anycast 的细节,可以参考 A Brief Primer on Anycast 和 What is Anycast? How does Anycast Work?。
由于有了 Anycast 网络和「计算机网络基础」的铺垫,即使是 DDoS 的攻击,数据包的行径也是要根据基本法,所以 DDoS 的模式从之前的多点对一点变成了多点对多点,这样流量就被分散掉了,每个节点上的压力就会小了很多。
Cloudflare 工作原理
在有了上述知识铺垫之后我们很容易就可以想到一个问题——Cloudflare 有那么多的 IP,那么它是如何回源的,从 「关于 Cloudflare Warp 的一些细节以及是否暴露访客真实 IP 的测试」一文中我们可以知道以下两个结论:
- 一般的,Cloudflare 回源的方式是由访客命中的数据中心进行回源
- 开启 Argo 之后,Cloudflare 由它认为距离你源站 IP 最近最快的 Cloudflare 数据中心回源
如果上述两个结论不好理解的话,我们带入两个条件来方便大家理解,假设我的博客在法国(假设解析为 origin.nova.moe
),你是一个大陆访客,假设 Cloudflare 使用的 Nginx,在目前网络环境下:
- 一般的,你会访问到 Cloudflare 位于美西的 San Jose 节点,然后 San Jose 节点上的 Nginx 就
proxy_pass https://origin.nova.moe;
了,很简单是不是?但是这样就是「公网回源」 - 如果你有 Argo,你还是会访问到 Cloudflare 位于美西的 San Jose 节点,但是这个时候 Cloudflare 会发现我们的源站和 Paris 的节点距离很近,就会将请求转发到 Cloudflare 位于 Paris 的机器上
proxy_pass https://origin.nova.moe;
开了 Argo 之后由于回源流量会有很大一段不经过公网(而是 Cloudflare 的各种公网隧道),所以理论上说路由路径更多地受到 Cloudflare 管控,在某些时候可以减少绕路,简单来说,速度应该会快一些,Cloudflare 官方宣传图如下:
更多详细的 Argo 对比可以参考郭泽宇的「Cloudflare Argo 与 Railgun 对比测试,CDN 加速的黑科技」。
自己实现类似 Argo 的网络
我们知道 Cloudflare 对于免费用户来说是公网回源,同时知道 Argo 的工作原理之后,就会想到一个问题——我能不能自己建立一个类似的玩意呢?
还是刚刚那个例子,我的博客在法国,大陆用户访问 Cloudflare 之后会从美西公网回源到法国,如果你正好有一个美西的机器,就可以考虑建立一个美西到法国的隧道,然后考虑让美西的流量走到自己美西的机器上反代隧道另一端的主机,也就做到了类似的效果。
我们要解决的第一个问题是:如何让 Cloudflare 的流量尽快进入自己的网络。
DNS 轮询
考虑一个情况,我们有源站法国 A,和两台分别位于美国,荷兰的主机 B、C,在 B 和 C 上配置反向代理到 A,并且添加两个 Cloudflare A 记录解析(并开启 CDN)到 B、C 主机的 IP 是否可以?
答案是不可以,因为 DNS 层面看上去有两条解析,但是这两条解析的权重并不会随着来源 IP 的改变而改变,所以还是有可能会出现大陆访客访问到 San Jose 节点后正好解析到了 C 主机,公网回源到位于荷兰的 C 主机后 C 主机回源 A 的情况发生。
Anycast
如何保证流量尽快进入自己的网络呢?答案就是 Anycast。
我们还是有法国源站 A,和两台分别位于美国,荷兰的主机 B、C,且 B、C 宣告了相同的 IP 地址(假设为 10.10.10.10
),这时,我们只需要添加一个 A 记录解析(并且开启 CDN)到 10.10.10.10
就可以了。
由于 Cloudflare 回源的机器也是一台很正常的服务器(路由也遵循基本法),这样当大陆访客访问到 San Jose 节点的时候,他会直接反向代理到对应 IP 10.10.10.10
,但是由于这个 IP 在美西也进行了宣告,所以数据包会被路由到位于美西的主机上,也就做到了进入了自己的网络的情况。
第一个实践
作为 Halo 的金牌 CDN 服务商,我自己持有一个 ASN 和一小段 IPv6 地址,在第一次实验中,我在美西和荷兰两个地方宣告了同一个 IPv6 地址(xxxx:xxxx:xxxx::1
,别问这 IP 为啥看上去这么奇怪,我也是这么认为的),所以在延迟上,这个 IP 看上去是这样的:
从图中我们可以看到,在 San Francisco 和 Amsterdam 两点的延迟分别为:2.4 ms 和 1.7 ms,所以我们可以认为这是一个成功的 Anycast。
接下来,在由 Wireguard 建立起来的全球大内网的加持下,A,B,C 三台主机全部位于一个 192.168.1.0/24
的内网下,三台主机之间可以直接 ping 通,对应延迟如下:
B -> A(美西 -> 法国)(不知道为啥第一个包总是抖一下,还望读者指出)
root@B:~# ping 192.168.1.5 PING 192.168.1.5 (192.168.1.5) 56(84) bytes of data. 64 bytes from 192.168.1.5: icmp_seq=1 ttl=64 time=308 ms 64 bytes from 192.168.1.5: icmp_seq=2 ttl=64 time=153 ms 64 bytes from 192.168.1.5: icmp_seq=3 ttl=64 time=153 ms 64 bytes from 192.168.1.5: icmp_seq=4 ttl=64 time=153 ms 64 bytes from 192.168.1.5: icmp_seq=5 ttl=64 time=153 ms ^C --- 192.168.1.5 ping statistics --- 5 packets transmitted, 5 received, 0% packet loss, time 3999ms rtt min/avg/max/mdev = 153.089/184.173/308.162/61.996 ms
C -> A (荷兰 -> 法国)
root@C:~# ping 192.168.1.5 PING 192.168.1.5 (192.168.1.5) 56(84) bytes of data. 64 bytes from 192.168.1.5: icmp_seq=1 ttl=64 time=1.28 ms 64 bytes from 192.168.1.5: icmp_seq=2 ttl=64 time=1.17 ms 64 bytes from 192.168.1.5: icmp_seq=3 ttl=64 time=1.32 ms 64 bytes from 192.168.1.5: icmp_seq=4 ttl=64 time=1.31 ms ^C --- 192.168.1.5 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3004ms rtt min/avg/max/mdev = 1.176/1.274/1.327/0.068 ms
好了,接下来就在 B 和 C 上共享一波 Nginx 配置文件就好了,暂时使用的 NFS 的方式共享,proxy_pass
部分内容最简写法大致如下:
location /{
proxy_pass https://192.168.1.5;
proxy_set_header Host $host;
}
最后 Cloudflare 部分我们这样做,先创建一个不开启 CDN 的 AAAA 解析,比如叫 secret.nova.moe
到自己的 IPv6 地址。
然后创建对应的 CNAME 解析,比如 halo.nova.moe
解析到 secret.nova.moe
并开启 CDN。
这样既可以让 IPv4 用户访问到自己的 IPv6 站点,还可以使用到 Cloudflare 的 CDN,这个操作真的很神奇~
最后网络结构图大概如下:
这么做有什么好处
主要是好玩,其次,作为 Halo 的金牌 CDN 服务商,从我托管的 Halo JAR 包镜像中可以看出,TTFB 平均减少了 40% 左右。
在这样之前,公网回源法国:
在有了 Anycast 之后:
至于为啥 TTFB 都很大,我怀疑这个与 DigitalOcean 的 Space 有关,我们只看对比差距就是了。
后记
在本文中,我们使用 Anycast IPv6 来实现让更多的流量进入「自己的网络」,然后在我们的 PoP 上可以有更多的限制和操作的空间,形成了一个类似 Argo 的效果。
一个不恰当的例子:试想,在原始的情况下,在世界各地都有大量的人从自己的机器上下载/上传,流量远大于服务商的 200Mbps(假设),Cloudflare 倒是可以很轻易地将流量转发过来,但是在只有一个源站的情况下,就会在带宽上出现问题,这个时候我们多个 PoP 就可以回源到自己的其他源站上,减少一个源站收到的压力。当然了,都这种时候了最佳方案其实还是加 LB 和升级带宽啦。