Featured image of post Google Play 商店的國內 CDN:從密碼學入門到分流策略最佳化

Google Play 商店的國內 CDN:從密碼學入門到分流策略最佳化

一個被封鎖的服務竟然還能有國內 CDN,挺神奇的。

改用自己的 Mihomo 覆寫規則後,手機從 Google Play 上下載應用就會一直轉圈圈,而換用機場的規則就沒問題,非常奇怪。遂調取 Mihomo 核心日誌檢視。

1
2
3
4
5
play-lh.googleusercontent.com:443 match RuleSet(cdn_domainset) using 靜態資源[🇭🇰 香港 07]
play.googleapis.com:443 match GeoSite(GFW) using 節點選擇[🇭🇰 香港 07]
play-lh.googleusercontent.com:443 match RuleSet(cdn_domainset) using 靜態資源[🇭🇰 香港 07]
play-fe.googleapis.com:443 match GeoSite(GFW) using 節點選擇[🇭🇰 香港 07]
services.googleapis.cn:443 match GeoSite(CN) using 全球直連[DIRECT]

(以上日誌已精簡)

國行手機內建的 Google Play 服務使用services.googleapis.cn而非services.googleapis.com域名提供服務,而這個域名在大多數分流規則中都是直連,對,問題就出在這裏,修改規則把這個域名分流到代理就萬事大吉了!

……萬事大吉了,嗎?

1
2
3
rr4---sn-j5o7dn7s.xn--ngstr-lra8j.com:443 match Match using 漏網之魚[🇭🇰 香港 07]
rr2---sn-j5o7dn7s.xn--ngstr-lra8j.com:443 match Match using 漏網之魚[🇭🇰 香港 07]
rr1---sn-j5o76n7z.xn--ngstr-lra8j.com:443 match Match using 漏網之魚[🇭🇰 香港 07]

等等,為什麼每次從 Play 商店下載應用都會出現這些奇怪的域名?在網上查詢一番資料後,新世界的大門就此開啓。

與 Google 截然相反卻又異曲同工的 Ångströ

看到 xn–ngstr-lra8j.com,熟悉域名的同學肯定知道,這是 PunyCode 編碼,這串字元解碼後就是 ångströ.com。Anders Jonas Ångström 是一位瑞典物理學家,他是光譜學的奠基人。1埃斯特朗(Ångström)是為紀念他而以他的名字命名的長度單位,$ 1 Å = 10^{-10} m = \frac{1}{10} nm $,通常用於描述非常短的距離,比如原子和分子尺寸或光的波長。其代表極端小的量級,尤其是在物理學和化學中用於精細測量。

雖然 Google 的名字並不是直接取自「Googol」,但是它的靈感來源於該詞。「Googol」是一個數學術語,表達$10^{100}$,即1後面跟着100個零,是一個極其龐大的數字,常用來表示電腦科學中涉及的巨大數字或資訊量。

在命名哲學上,Google 與Ångströ 這對看似分處光譜兩端的科技概念,卻展現出耐人尋味的對稱美學。前者源於數學概念「Googol」的創造性改寫,後者則脱胎於物理單位「Ångström」的意象重構。這兩個經過藝術化變奏的稱謂,恰似科技文明的雙螺旋:Google 之稱承載着駕馭浩如星海的數字宇宙的雄心,Ångströ 之謂則暗喻着雕琢精微的原子級技術圖景。當這兩個經過創造性變形的專業術語在矽谷相遇,恰好構成了一組完美的認知座標——既指向人類處理資訊的尺度極限,也昭示着科技文明同時向宏觀與微觀進軍的壯闊征程。

入門密碼學:Google 基礎設施域名的規律2

OK,enough。現在讓我們把視角轉回 Google 的基礎設施。先來看一些完整的連線域名:

1
2
3
rr4---sn-j5o7dn7s.xn--ngstr-lra8j.com
rr1---sn-j5o76n7z.xn--ngstr-lra8j.com
rr5---sn-i3b7knzs.xn--ngstr-lra8j.com

這些看似隨機的字串,實際上是經過一些簡單的密碼學加密後的結果。讓我們拆解其中的核心部件。

城市名稱的轉換規則

rr1---sn-j5o76n7z為例,關鍵的資訊段在sn-後面的 8 位:r1---sn-[123][45][6][78]。前三位,也就是本例中的j5o是城市名稱,由該城市主要機場的 IATA 碼透過一定的密碼學變換轉換而來。

首先構建一張 5 * 7 的數字字母表:

1
2
3
4
5
6
7
行0: 1 0 2 3 4
行1: 5 6 7 8 9
行2: a b c d e
行3: f g h i j
行4: k l m n o
行5: p q r s t
行6: u v w x y

逆時針旋轉這張表,即從新表格的左下角開始,依次按照原表格「從左到右,從上到下」的順序,把原表的資料在新表上按照「從下到上,從左到右」的順序謄抄。

旋轉完成後,可以得到下表:

1
2
3
4
5
6
7
8
9
| 6 d k r y |
| --------- |
| 5 c j q x |
| 4 b i p w |
| 3 a h o v |
| 2 9 g n u |
| 0 8 f m t |
| --------- |
| 1 7 e l s |

表格中間用線條框出來的 5*5 的部分就是最終的密碼錶,一共有 25 個字元,「從左到右,從上到下」依次代表字母a到字母y。例如,上海虹橋國際機場的 IATA 代碼為sha,我們可以透過這張表得到加密後的密文:

  • s在字母表中是第 19 個字母,「從左到右,從上到下」依次在密碼錶中數到第 19 個字元,也就是n
  • h在字母表中是第 8 個字母,「從左到右,從上到下」依次在密碼錶中數到第 8 個字元,也就是i
  • a在字母表中是第 1 個字母,「從左到右,從上到下」依次在密碼錶中數到第 1 個字元,也就是5

加密後的密文就是ni5

反之亦然,回到一開始的城市名稱j5o,從我們剛剛得到的密碼錶中反推出原文:

  • j按照「從左到右,從上到下」的順序是第 3 個字元,也就是c
  • 5按照「從左到右,從上到下」的順序是第 1 個字元,也就是a
  • o按照「從左到右,從上到下」的順序是第 14 個字母,也就是n

翻譯完成的明文是can,也就是廣州白雲國際機場的 IATA 碼,顯然,我們連線到的伺服器位於廣州。

要是谷歌有某個伺服器位於蘇黎世,而蘇黎世機場的 IATA 碼是zrh,有一個字母z,可是我們的密碼錶只有ay啊?別擔心,Google 當然也考慮到了這個問題,z對應密碼錶中的1

將這套規則用代碼表示如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
table = "5cjqx4bipw3ahov29gnu08fmt1"
def iata2cipher(iata):
    global table
    iata = iata.upper()
    cipher = ""
    for element in iata:
        index = ord(element) - 65
        cipher += table[index]
    return cipher

def cipher2iata(cipher):
    global table
    cipher = cipher.lower()
    iata = ""
    for element in cipher:
        index = table.find(element)
        iata += chr(65 + index)
    return iata

# print(iata2cipher(input("IATA:")))
# print(cipher2iata(input("Cipher:")))

伺服器組編號

[45]和[78]位,如767z,表示伺服器組(接入點)編號,由下表第一列(已經用分隔線隔開)包含的字元組成,7elsz6dkry分別代表0123456789。所以,76的明文是057z的明文是04

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  | 1 2 3 4 5 6
7 | 8 9 a b c d
e | f g h i j k
l | m n o p q r
s | t u v w x y
z | 0 1 2 3 4 5
6 | 7 8 9 a b c
d | e f g h i j
k | l m n o p q
r | s t u v w x
y | z

以 Python 表示如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
codeTable = "7elsz6dkry"
def cipher2code(cipher):
    global codeTable
    cipher = cipher.lower()
    codeString = ""
    for element in cipher:
        index = codeTable.find(element)
        codeString += chr(index + 48)
    return codeString

# print(cipher2code(input("Cipher:")))

同樣要將數字加密為密文同理,這裏不再贅述。

支援協議

[6]位表示網絡協議資訊:

  • n:IPv6(地址段 0x000-0x3FF)
  • u:IPv6(地址段 0x400-0x7FF)
  • m:僅支援 IPv4

例如:

  • a5mekn7r,IPv6 字首:2607:f8b0:4007:a::/64,IPv4 字首:74.125.103.0/24
  • a5m7zu7r,IPv6 字首:2607:f8b0:4007:407::/64,IPv4 字首:74.125.215.0/24
  • a5mekm76,僅支援 IPv4,字首:208.117.242.0/24

正確的分流處理

⚠️ 警告

Google 顯然不會為這些位於中國大陸的,未經 ICP 備案的 CDN 的穩定性和速度背書。如果流量足夠,那還是直接將services.googleapis.cn加入代理列表吧。

瞭解了 Google 基礎設施域名的加密規律後,我們可以根據這些資訊實現更精準的分流策略。目前已知 Google 在中國大陸的 CDN 節點主要分佈在北京(2x3)、上海(ni5)和廣州(j5o),對應的域名特徵可以透過正規表示式識別:

1
2
rules:
  - DOMAIN-REGEX,^r+[0-9]+(---|\.)sn-(2x3|ni5|j5o)\w{5}\.xn--ngstr-lra8j\.com$,DIRECT

這條規則會匹配所有形如 rr1---sn-j5o76n7z.xn--ngstr-lra8j.com 的國內 CDN 域名,並將其標記為直連。而對於其他未被覆蓋的 Google 域名(如 play.googleapis.com 等),仍遵循原有的代理規則。

事實上,GeoSite 數據庫的上游 v2fly/domain-list-community 已針對 Google Play 的國內 CDN 節點進行了最佳化3。只需在 Mihomo 配置中啓用 GEOSITE,GOOGLE-PLAY@CN 規則,即可自動實現國內 CDN 直連與海外域名代理的智慧分流:

1
2
3
rules:
  - GEOSITE,GOOGLE-PLAY@CN,直連
  - GEOSITE,GOOGLE,代理
採用 CC BY-NC-SA 4.0 協議進行許可
上次改過於 2026 年 3 月 17 日 13:08 +0800