Files
vpsetup/docs/cdn-list-audit.md
eli ca5d8ee84a refactor: CDN 清单生成器移交 gc-toolkit/cdn-list
这份清单存在的唯一原因是 GCP 把 CDN 出站单独计价(Carrier Peering,$0.08/GiB 从
第一字节起算、无 200 GiB 免费额度),而消费它的 VPC 拒绝规则 cdn-egress-deny 本身
也是 GCP 资源。所以生成器归 gc-toolkit 更合理。

- 删掉本仓库的 tools/refresh-cdn-lists.sh(功能已合并进 gc-toolkit,避免两份分叉)
- 脚本头部与 docs/cdn-list-audit.md 改成指路 gc-toolkit
- 清单内容本次无变化:v4 271 条 / v6 193 条,与 gc-toolkit 生成的 canonical 文件一致

迁移后 gc-toolkit 那边多了两件事:生成 canonical 的 cdn-cidrs-v4/v6.txt,
以及 --vpsetup 参数(一次运行同时同步本仓库的内嵌清单与 v6 文件)。
2026-11-08 13:12:01 +08:00

9.8 KiB
Raw Permalink Blame History

CDN 网段清单:来源、依据与实测结论

状态:已采纳并上线(2026-11-08) 影响:setup_cdn_bypass_tproxy.sh 内嵌 v4 清单 + cdn-cidrs-v6.txt, 以及 GCP 项目里的 egress 拒绝规则(cf-no-cdn / fastly-no-cdn / no-akamai-0 / no-akamai-1;新项目 vps-free-xu11 里是单条 cdn-egress-deny)


0. 这份清单干什么用

GCP 的 CDN 出站走 Network Data Transfer Out via Carrier Peering Network 科目: $0.08/GiB、从第一字节起算、没有 200 GiB 免费额度(官方原话,见 Network Connectivity pricing: "Egress charges for CDN Interconnect appear on the invoice as Network Data Transfer Out via Carrier Peering Network")。普通互联网出站则有 200 GiB 免费。

CDN 绕行就是把"目的地属于 CDN 网络"的出站流量 TPROXY 到上游 Oracle 节点, 让 GCP 看到的目的是 Oracle 而不是 CDN,从而落回有免费额度的科目。


1. 依据是什么(本文档最重要的部分)

1.1 GCP 怎么判定"这是 CDN 流量"

按目的地属于哪个网络自动判定,不看你配了什么、也没有 IP 清单可查:

If your CDN provider is already part of the program, you don't have to do anything. Traffic from supported Google Cloud locations to your CDN provider automatically takes advantage of the direct connection. —— CDN Interconnect overview

所以真正的 ground truth(GCP 内部的路由分类表)我们看不到,只能靠账单科目反推。

1.2 Cloudflare / Fastly:官方公开,权威

来源 条数(v4 / v6)
https://www.cloudflare.com/ips-v4、/ips-v6 15 / 7
https://api.fastly.com/public-ip-list(addresses / ipv6_addresses) 19 / 3

这两家没有争议,直接用。

1.3 Akamai:没有官方公开清单,只能用 ASN/BGP 派生

已穷举确认(不是关键词搜索):

  • 官方 TechDocs 里的 CIDR 列表全是客户作用域的:Site Shield、Origin IP ACL、 Client Access Control —— 给的是"你这个合同的回源入口",不是"Akamai 全部边缘",要登录。
  • 官方 GitHub 组织 github.com/akamai 207 个仓库全量枚举,没有任何网段仓库; github.com/linode 118 个同样没有。
  • 第三方研究文档结论一致: firehol/update-ipsets 调研 —— "Akamai CDN: No official public unauthenticated bulk feed;Authenticated API exists for customers;建议 BGP enumeration of AS20940 + AS16625"。

实际采用的数据源:SecOps-Institute/Akamai-ASN-and-IPs-List

  • 做法:按 40 个 Akamai ASN(AS20940 CDN 边缘、AS16625、…、AS63949 Linode 云主机) 从 RADb 拉 BGP 宣告记录,拼成 IP 列表。
  • 更新频率:每小时(Last-Checked-At 文件,2026-11-08 08:03 IST)。
  • 提供的文件:
文件 说明
akamai_ip_list.lst v4,9462 行,按注册原样、互相重叠(有 2.16.0.0/13 又有 2.16.0.0/24)
akamai_ip_cidr_blocks.lst v4,已合并好,235 条 —— 与上面那个 collapse 后完全等价
akamai_ipv6_list.lst v6,3849 行,同样未合并(仓库没提供 v6 合并版,必须自己合)
akamai_asn_list.lst 收录的 40 个 ASN

关键教训:之前内嵌的那份是拿 akamai_ip_list.lst 手工合并、且是一两年前的快照。 2026-11-08 复核发现:

  • 已过时:265 段 / 209,106 个地址 Akamai 已不再宣告(还在绕,纯浪费); 另有 49 段 / 13,504 个地址是新宣告的(当时没绕 → 在按 $0.08/GiB 计费)。
  • 合并方式不必要:手动合并出 368 条,而仓库自带的合并版只要 235 条,覆盖完全相同。

⇒ 不要自己合并 v4,直接用 akamai_ip_cidr_blocks.lst;v6 仓库没给合并版,自己 collapse。

1.4 Linode 在里面,怎么办

akamai_asn_list.lst 含 AS63949(Akamai Connected Cloud / Linode)。那是客户虚拟机, 不是 CDN 边缘,严格说不该算 CDN 目的地。

2026-11-08 评估过"精确挖掉":

条数
挖之前 368
精确挖掉后 381(减法会把整块切成碎片)

而且挖完必须同步改 GCP 拒绝规则,否则那些站点会被 VPC 直接丢包(不是回退直连)。 净负收益 ⇒ 保留。若将来"Linode 站点被绕行"这件事本身不可接受,再单独处理。


2. 条数要精简吗:不需要

2.1 这不是 iptables 规则链,是一个集合查表

ip daddr { … 269 个 … } accept 编译成一条 lookup 指令。用 nft --debug=netlink list chain ip cdn_bypass_mark output 直接看到:

[ payload load 4b @ network header + 16 => reg 1 ]
[ lookup reg 1 set __set0 0x0 ]

不是 269 条 cmp。集合元素在 nft 用哈希表/红黑树存放 (官方 wiki),区间集是 rbtree, 查找 O(log n)。

2.2 实测

在 gcoregon-2 上做 A/B,目标选不在集合里的站点(不经过代理,隔离查找本身), 每组 120 次,并人为把集合灌到 5 万条做灵敏度对照:

集合元素数 均值 中位 P90
188 0.2108s 0.2099s 0.2164s
488 0.2101s 0.2094s 0.2157s
5,188 0.2112s 0.2095s 0.2154s
50,188 0.2095s 0.2085s 0.2158s
188(还原) 0.2107s 0.2101s 0.2176s

5 万条都测不出差异 ⇒ 条数在性能上不是问题,不构成精简理由。

2.3 而且 GCP 也没有 256 条上限(之前的推测是错的)

曾推测 no-akamai-0(256) + no-akamai-1(112) 的拆分是因为单条规则上限 256。 实测:300 / 1000 / 3000 个 destinationRanges 全部创建成功。 那个拆分是人为的,不是平台限制。

2.4 强行合并到"几十条"的代价

对 Akamai 段做 trie 上的精确 DP,得到"条数 ↔ 多绕地址"前沿:

条数 多绕地址 占全部公网 IPv4
368 0 0%
300 186,048 0.004%
256 1,624,863 0.038%
210 14,755,006 0.34%
150 124,964,718 2.9%
50 1,157,258,014 27%
20 2,385,523,230 56%

"几十条"意味着把公网 27%~56% 的地址丢给 3 台 Oracle 转发 —— 不可接受。


3. 最终清单

内容 条数
v4 CF 15 + Fastly 19 + Akamai 235(仓库自带合并版) 269
v6 CF 7 + Fastly 3 + Akamai 183(自己 collapse) 193

审计日期记录在脚本里的 CDN_LIST_AUDIT_DATE;超过 180 天未刷新会打印告警。

验证记录(2026-11-08):

  • 两台机器渲染出的 /etc/nftables/cdn-bypass.nft md5 一致(c8f16ac8…)。
  • 真实 Akamai 边缘地址能命中:www.akamai.com → 2600:1417:4400:26::17c9:673c 命中 2600:1400::/24;www.microsoft.com → 2600:1417:4400:1188::356e 同样命中 (微软是 Akamai 客户)。www.apple.com 的 AAAA 走移动线路,不在清单里,符合预期。

4. 怎么刷新

生成器已经挪到 gc-toolkit 的 cdn-list 子工具(这份清单存在的唯一原因是 GCP 计费, VPC 拒绝规则本身也是 GCP 资源):

cd ../gc-toolkit
./cdn-list/refresh.sh                        # 只报告差异
./cdn-list/refresh.sh --apply --vpsetup ../vpsetup   # 落盘并同步本仓库的内嵌清单

.github/workflows/refresh-cdn-list.yml 每天 04:00 UTC 自动跑一次(GitHub 托管 runner, 无需自备机器)。

历史:原先本仓库有过 tools/refresh-cdn-lists.sh,功能等价但只服务本仓库;已合并进 gc-toolkit,避免两份生成器分叉。

手工流程(脚本不可用时的兜底)

见 setup_cdn_bypass_tproxy.sh 头部注释的【怎么更新这份清单】。要点:

  1. 先看仓库有没有在更新:curl -s $B/Last-Checked-At
  2. v4 用 akamai_ip_cidr_blocks.lst(已合并),不要拿 akamai_ip_list.lst 自己合
  3. v6 用 akamai_ipv6_list.lst,必须自己 collapse(仓库没给合并版)
  4. CF / Fastly 用官方两个 URL
  5. 三个来源 cat 到一起 collapse,替换清单体,更新 CDN_LIST_AUDIT_DATE

5. 两侧必须一致

主机侧清单和 GCP 拒绝规则的网段集合必须是同一份。不一致会导致:

  • 主机侧少了 → 那些 CDN 目的地直连出去,按 $0.08/GiB 计费(漏绕)
  • GCP 侧多了 → 拒绝规则把本应经代理绕出去的流量挡住(误丢包)

6. 未完成

  • gcoregon 所在项目(western-watch-268811)的四条拒绝规则仍是旧的 368 段快照, 未与新的 269 段对齐(主机的绕行名单已更新,所以现在是"主机新、GCP 旧")。 影响:老的 209k 段仍被 VPC 拒绝但主机已不再绕它们 → 那些目的地会丢包。 需要尽快对齐(改成单条 269 段即可,3000 条实测可行)。
  • 清单是内嵌的(被封锁的机器在代理起来之前访问不了 cloudflare.com), 不会自动更新,只能靠 §4 的手工流程 + 180 天告警。

7. 本次纠正的错误认知

  1. ❌ "GCP 单条规则上限 256 个网段" → 实测 3000 个也能建。
  2. ❌ "Akamai v6 没有可用来源,所以故意不收" → 前提错了。该仓库有 akamai_ipv6_list.lst。之前 v6 只有 CF+Fastly 10 条, 所有走 v6 的 Akamai 边缘流量都没被绕行,一直在按 CDN 计费。
  3. ❌ "精确挖掉 Linode 会更简洁" → 368 → 381,反而更多。
  4. ❌ "清单 188 条够用" → 那是 1~2 年前的快照,已漂移约 20 万地址。