平时从 Google Drive 拖几十 GB 的数据集或备份,走代理往往既烧流量又受限于节点带宽;用浏览器下载还经常在中途断流后从头再来。为此我写了 gdirect:一个在终端里浏览、下载、上传和挂载 Google 云盘的工具。
这篇文章按三个部分组织:先用几分钟讲清楚它是怎么“伪装”并直连 Google 的(TL;DR),接着是开箱即用的安装与 10 分钟图文配置指南,最后再深入源码做底层技术解析。
TL;DR:我们是怎么“伪装”直连的?
在大陆网络下,Google Drive 的域名(drive.google.com、www.googleapis.com、drive.usercontent.google.com)处于被封锁状态;但与此同时,Google 为了给国内提供 Chrome 更新、Android 连通性校验、字体等基础服务,在大陆境内(电信、联通、移动骨干网内)保留了一批可直连的边缘服务器——也就是分流规则集(如 DustinWin/ruleset_geodata)里归入 google-cn 的那批域名(如 dl.google.com、g.cn、fonts.googleapis.com)背后的国内 IP(例如广州电信的 183.56.143.147)。
关键在于:这些位于墙内的 google-cn 节点,跑的同样是 Google 统一的前端反向代理(GFE,Google Front End),背后通过 Google 内部专线直通全球云盘后端,并且持有 *.googleapis.com 与 *.googleusercontent.com 的合法官方 TLS 证书。
gdirect 就是利用这一点,用“借 google-cn 的国内肉身(IP),发 Google Drive 的原始请求(域名与证书)”完成伪装与直连的:
- 底层 TCP 指向国内
google-cn节点 IP:绕过被污染的系统 DNS,从内置种子 IP(183.56.143.147)和候选池出发,直接连向目标的 443 端口。经过实测探针(核验 Google 官方 TLS 证书 + 验证 Drive v3 API 响应)确认可用后存入本地 SQLite。在底层的套接字建立阶段(Rust 的reqwest::resolve和定制 C++libaria2的getaddrinfo钩子),哪怕下载中途发生多次 302 跨域名重定向,所有连接的目标 IP 都会被强制锁死在这个国内google-cn节点上——因为目标 IP 本身就在国内骨干网,物理链路根本不过墙。 - 上层 TLS / HTTP 保持真实云盘域名与证书校验:连上国内
google-cn的 IP 后,应用层请求写的依然是标准的https://www.googleapis.com(API 与分块上传)和https://drive.usercontent.google.com/https://*.googleusercontent.com(文件下载)。国内 GFE 节点收到后,按主机名通过 Google 内网把云盘数据拉回来;本机也照常按真实域名严格校验 TLS 证书(若遇到针对明文域名的拦截,还可用--sni/--no-sni改写或剥离 TLS 握手中的 SNI)。 - 控制面与数据面分离:只有几 KB 的登录与刷新令牌(
accounts.google.com、oauth2.googleapis.com)照常走系统网络(翻墙代理),保证随时能登得上;真正吃带宽的目录浏览、多连接并发下载、32 MiB 分块上传全部走上述国内直连 IP,不耗一字节代理流量就能跑满本地宽带。
| 流量类型 | 涉及命令 / 域名 | 走向 |
|---|---|---|
| 云盘 API、下载、上传 | browse、ls、download、upload(www.googleapis.com、drive.usercontent.google.com、*.googleusercontent.com) |
扫描到的国内 google-cn 可用 IP。主机名和证书校验不变,可用 --sni / --no-sni 改写或去掉 TLS SNI |
| 登录与刷新令牌 | login 及自动刷新(accounts.google.com、oauth2.googleapis.com) |
系统网络(走系统 DNS 与翻墙代理) |
| 磁盘挂载 | mount(底层调用 rclone) |
系统网络 |
大陆网络环境特别提示: 就算你的翻墙软件已经把 DustinWin/ruleset_geodata 的
google-cn设为了直连,开着翻墙软件(尤其是 TUN 模式)跑gdirect时依然有可能走代理!因为gdirect虽然连的是google-cn的 IP,但 TLS 握手发的是云盘域名(drive.usercontent.google.com、www.googleapis.com),而这些域名在规则集里属于需要翻墙的google-cn,会被翻墙软件嗅探(Sniff)出域名后半路截胡送进代理。 因此大陆用户请务必二选一(详见下文大陆网络与代理配置指南):
- 在翻墙软件中配置规则:显式让上述云盘域名(或
gdirect进程)走DIRECT直连并关闭fake-ip;- 退出翻墙软件或关闭 TUN:开着代理完成
gdirect login登录好之后,在scan和下载时退出翻墙软件(或关闭 TUN 模式)。
安装指南
预编译二进制支持 macOS 15+(Apple Silicon / Intel)、Linux x86_64(glibc 2.35+,如 Ubuntu 22.04+、Debian 12+、Fedora、Arch)和 Windows x86_64。
方式一:一键安装脚本
macOS / Linux:
curl -fsSL https://raw.githubusercontent.com/mizorewww/gdirect/main/scripts/install.sh | bashshWindows PowerShell:
& ([scriptblock]::Create((Invoke-RestMethod 'https://raw.githubusercontent.com/mizorewww/gdirect/main/scripts/install.ps1')))powershell安装脚本会把 gdirect 和配套校验用的 cacert.pem 放到 ~/.local/bin(Windows 为 %LOCALAPPDATA%\gdirect\bin)。
如果你还想用 gdirect mount 把云盘挂载成本地磁盘,在安装命令末尾加上 --with-mount(PowerShell 用 -WithMount),脚本会顺带装好依赖(rclone 1.75.2+,Linux 额外安装 fuse3,Windows 通过 winget 安装 WinFsp):
curl -fsSL https://raw.githubusercontent.com/mizorewww/gdirect/main/scripts/install.sh | bash -s -- --with-mountsh方式二:Homebrew(macOS / Linux)
brew tap mizorewww/gdirect https://github.com/mizorewww/gdirect
brew install mizorewww/gdirect/gdirect
brew install rclone # 仅挂载功能需要;Linux 另需用系统包管理器安装 fuse3sh后续升级执行 brew update && brew upgrade gdirect 即可。
Google Cloud 一次性配置指南(约 10 分钟)
为了避免共用 OAuth 客户端导致全员配额耗尽(429 限流)或被封禁,gdirect 不内置公共客户端,而是让你在自己的 Google Cloud 项目里创建一个 Desktop app(桌面应用) 客户端。只需操作一次,Google 完全免费。
1. 新建项目并启用 Drive API
-
打开 Google Cloud 控制台,点击页面顶部的项目选择器,再点击右上角的 New project(新建项目)。名称随意(例如
gdirect),个人账号的 Location(位置) 保持 No organization(无组织) 即可。点击 Create(创建) 后,确认顶部已经切换到这个新项目。
-
打开 Google Drive API 页面,确认顶部是刚才新建的项目,点击 Enable(启用)。

完成后,页面会显示服务名称
drive.googleapis.com,状态变为 Enabled(已启用)。
2. 配置 OAuth 应用
-
打开 Google Auth Platform,点击 Get started(开始)。
-
App information(应用信息):填写应用名称(比如
gdirect,登录授权时会显示),用户支持邮箱选择你自己的邮箱,点击 Next(下一步)。
-
Audience(受众群体):选择 External(外部),点击 Next(下一步)(个人账号无法选 Internal)。

-
Contact information(联系信息):填写你的邮箱,点击 Next(下一步)。
-
Finish(完成):勾选同意 Google API 服务用户数据政策,点击 Continue(继续),再点击 Create(创建)。

3. 添加 Drive 权限范围(Scope)
-
在左侧菜单打开 Data Access(数据访问),点击 Add or remove scopes(添加或移除范围)。
-
在弹出的面板中勾选
.../auth/drive(说明为“查看、修改、创建和删除您的所有 Google 云端硬盘文件”)。如果列表里找不到,直接把下面的地址粘贴到 Manually add scopes(手动添加范围) 中,点击 Add to table(添加到表),再点击 Update(更新):https://www.googleapis.com/auth/drive
-
注意:回到数据访问页面后,一定要点击底部的 Save(保存)。只点上一步的 Update 并不会真正保存。

4. 创建桌面客户端并下载 JSON
-
在左侧菜单打开 Clients(客户端),点击 Create client(创建客户端)。应用类型必须选择 Desktop app(桌面应用)(网页客户端、API Key 和服务账号 JSON 都不能用),名称随意(如
gdirect Desktop),点击 Create(创建)。
-
在弹出的窗口中,关闭之前点击 Download JSON(下载 JSON)。窗口关闭后就无法再次下载这个文件;如果不小心关掉了,重新创建一个客户端即可。

5. 允许你的账号登录(解决 7 天过期问题)
新建的应用默认处于 Testing(测试) 状态,只有测试用户列表中的账号可以登录:
-
在左侧菜单打开 Audience(目标对象),在 Test users(测试用户) 下点击 Add users,填入你要在
gdirect中使用的 Google 账号邮箱,点击 Save(保存)。
测试状态下,登录凭证会在 7 天后失效,届时执行 gdirect login --reauth 重新授权即可。
如果不想每周重新登录,可以直接在 Audience(目标对象) 页面顶部点击 Publish app(发布应用),把状态改为 In production(正式版)。自己使用的应用不需要经过 Google 审核,切到正式版后就不会再有 7 天过期的限制。
6. 在终端登录
在终端执行下面的命令,把路径换成你下载的 JSON 文件:
gdirect login --client-secret ~/Downloads/client_secret.jsonsh-
程序会自动打开浏览器(如果没有自动打开,请把终端打印的链接复制到同一台电脑的浏览器中打开,因为授权完成后要回调本机的临时端口)。
-
选择你的 Google 账号,Google 会提示“此应用未经验证”——这是正常的,因为应用是你自己刚刚创建的:
- 测试状态:点击 Continue(继续),不要点“返回到安全网页”。
- 正式版(In production):点击左下角 Advanced(高级),再点击 Go to 你的应用名 (unsafe)(转至……(不安全))。

-
核对应用名称和 Google 云端硬盘权限,确认无误后点击 Continue(继续)。

-
回到终端,看到
Logged in即大功告成。此时可以删掉下载的 JSON 文件(客户端信息已保存在本地 SQLite 中,以后切换账号或重登只需执行gdirect login --reauth)。
无界面服务器(Headless)提示:如果在没有浏览器的远程服务器上使用,可以先在桌面电脑上完成登录,或者用
gdirect login --credentials 路径导入已有的authorized_user凭据 JSON;写脚本时也可以直接通过环境变量GDIRECT_ACCESS_TOKEN传入访问令牌。
大陆网络与代理配置指南
在大陆使用 gdirect 时,最容易遇到的困惑是:我的翻墙软件(sing-box / Mihomo / Clash)明明已经用了 DustinWin/ruleset_geodata 规则集,并且把 google-cn 设为了 DIRECT 直连,为什么跑 gdirect 下载时流量还是有可能走代理?
仔细算一笔账:翻墙软件是怎么把直连流量截胡的?
虽然 gdirect 在应用层禁用了 HTTP/SOCKS 代理环境变量(no_proxy),但当你开着翻墙软件(尤其是 TUN 虚拟网卡模式)时,所有流量都会先经过翻墙软件的路由引擎。我们顺着翻墙软件的匹配逻辑看一遍:
- 我们借的是
google-cn的 IP,但发的是 Google Drive 的域名:gdirect在底层把连接目标 IP 换成了国内google-cn节点(比如183.56.143.147),但为了让 Google 边缘节点正确路由并通过本机 TLS 证书校验,它在 TLS 握手(ClientHello的 SNI)和 HTTPHost头里发出的依然是www.googleapis.com、drive.usercontent.google.com和*.googleusercontent.com。 - 云盘的这些域名,并不在
google-cn规则集里! 翻开 DustinWin/ruleset_geodata 的google-cn规则集可以看到,里面收录的是dl.google.com、g.cn、gstatic.cn、fonts.googleapis.com、clientservices.googleapis.com等国内本来就能直连的域名;而www.googleapis.com、drive.usercontent.google.com、drive.google.com、*.googleusercontent.com并不在google-cn里,它们全部躺在需要翻墙的google规则集里。 - TUN 流量嗅探(
sniff)触发按域名截胡: 当gdirect向国内 IP183.56.143.147:443发起连接时,TUN 网卡拦下数据包,通过sniff(TLS 域名嗅探)读出里面的 SNI 是drive.usercontent.google.com或www.googleapis.com。随后翻墙软件拿着这个嗅探出来的域名从上往下走规则:- 经过
rule_set: google-cn(直连) 未命中(因为域名是云盘域名,不在google-cn中); - 走到
rule_set: google(代理) 直接命中! - 在主流规则模板里,域名规则优先于 IP 规则(或者当
scan尝试用系统 DNS 解析上述云盘域名时,直接在 DNS 层命中google规则被远端代理解析或返回198.18.x.x的 Fake-IP)。结果就是:这股明明发往国内电信 IP 的连接,因为身上带着google规则集里的域名,被翻墙软件当成翻墙流量重新塞进了代理节点!
- 经过
理清了这个逻辑,大陆用户只需要在下面两种做法中二选一即可彻底解决:
做法一:登录好之后,scan 和下载时退出翻墙软件(或关闭 TUN)
如果你不想改动翻墙软件现有的分流规则,这是最简单、也最干净的做法:
- 登录阶段(开着翻墙软件):保持翻墙软件正常开启,执行
gdirect login --client-secret ...完成授权(因为浏览器打开accounts.google.com以及终端向oauth2.googleapis.com换取/刷新令牌必须走代理)。 - 扫描与下载阶段(退出翻墙软件,或关闭 TUN 模式):
- 登录完成后(本地已拿到有效期约 1 小时的短期
access_token),直接退出翻墙软件,或者关掉翻墙软件的 TUN 模式(如果只开系统代理 System Proxy、不开 TUN,gdirect自身会忽略系统代理环境变量直接连出去,但关掉 TUN 最稳妥)。 - 执行
gdirect scan扫出当前纯物理宽带下最快的国内google-cn节点 IP。 - 执行
gdirect download ...或gdirect browse开始满速直连下载(若遇到宽带运营商对明文 SNI 重置连接,加上--no-sni即可):
- 登录完成后(本地已拿到有效期约 1 小时的短期
gdirect scan
gdirect download FILE_ID
# 若遇到连接或 TLS 重置(libaria2 code 1),去掉 SNI 即可:
gdirect download FILE_ID --no-snish做法二:在翻墙软件中配置规则,把云盘域名(或 gdirect 进程)也并入直连
如果你希望翻墙软件和 TUN 模式 24 小时常开,思路其实很简单:既然翻墙软件是因为嗅探到了云盘域名才把流量分给 google(代理)的,那我们就在 google 规则之前,手动把这几个云盘域名(或 gdirect 进程本身)也像 google-cn 一样指派给 DIRECT 直连:
- 保留走代理(
PROXY)的域名(仅用于登录和令牌自动刷新):accounts.google.comoauth2.googleapis.com
- 需要放行直连(
DIRECT)并关闭fake-ip的传输域名:drive.usercontent.google.com*.googleusercontent.comdrive.google.comwww.googleapis.com、drive.googleapis.com、content.googleapis.com
注意:普通浏览器访问
www.googleapis.com时并不会像gdirect这样把底层 IP 替换成google-cn节点,所以如果你把www.googleapis.com全局设为DIRECT,可能会影响浏览器里依赖该域名的其他网页。因此在桌面端最推荐配合PROCESS-NAME(进程名分流),或者只对gdirect进程放行直连。
以 Mihomo(Clash) 为例,在配置文件的 GEOSITE,google 规则之前加入:
dns:
fake-ip-filter:
- "www.googleapis.com"
- "drive.googleapis.com"
- "content.googleapis.com"
- "drive.usercontent.google.com"
- "drive.google.com"
- "+.googleusercontent.com"
rules:
# 1. 登录与刷新令牌走代理
- DOMAIN,accounts.google.com,PROXY
- DOMAIN,oauth2.googleapis.com,PROXY
# 2. 推荐:让 gdirect 进程的其余所有流量(扫描、API、下载、上传)强制直连,不被 sniff 域名规则截胡
- PROCESS-NAME,gdirect,DIRECT
# 3. 或者按具体域名直连(放在 GEOSITE,google / RULE-SET,google 之前):
- DOMAIN,drive.usercontent.google.com,DIRECT
- DOMAIN-SUFFIX,googleusercontent.com,DIRECT
- DOMAIN,www.googleapis.com,DIRECT
- DOMAIN,drive.googleapis.com,DIRECT
- DOMAIN,content.googleapis.com,DIRECTyaml如果你用的是 sing-box,同样可以在 route.rules 的 sniff 或 rule_set: ["google"] 之前加上一条按进程或域名的直连规则(保留 oauth2.googleapis.com 走代理),就能在 TUN 常开的情况下随时免流跑满云盘下载。
日常使用与排障速查
常用命令
gdirect # 等同于 gdirect browse,打开交互式 TUI
gdirect browse -o ~/Downloads # 指定下载目录(默认 ./downloads)
gdirect ls root --json # 列出目录内容(支持 JSON 输出)
gdirect download 文件ID或分享链接 # 下载文件,断流重跑同一命令即可续传
gdirect download FILE_ID -o file.zip
gdirect download DOC_ID --export application/pdf -o doc.pdf
gdirect upload ./file.zip # 32 MiB 分块上传,支持断点续传
gdirect upload ./file.zip --parent FOLDER_ID --name backup.zip
gdirect mount # 挂载到 ~/GoogleDrive(macOS 走原生 NFS 免 FUSE)
gdirect unmount # 卸载并清理临时导出的凭据sh在 gdirect browse 界面中,可以用键盘完成几乎所有日常操作:
| 按键 | 操作 |
|---|---|
j / k、↑ / ↓ |
上下选择文件或文件夹 |
Enter、l / → |
进入文件夹 |
h / ←、Backspace |
返回上级目录 |
d |
加入下载队列(Google 文档、表格、幻灯片自动导出为 PDF) |
+ / - |
调整后续下载的并发连接数(1–16),并保存为默认值(初始为 8) |
r |
刷新当前目录 |
c |
取消当前下载并保留已下载部分 |
q |
退出 |
如果遇到特殊网络环境,download 和 browse 还支持手动干预传输层参数:
--ip IP:强制使用指定的 Google IP,跳过自动扫描结果;--sni HOST/--no-sni:改写 TLS SNI 主机名,或完全不发送 SNI(证书仍按真实 Google 主机名校验);-j N:仅对当前命令设置并发连接数(1–16);gdirect scan --ips FILE:探测自定义候选 IP 列表(每行一个公网 IP,#后为注释)。
常见报错速查表
| 现象 | 处理方法 |
|---|---|
access_denied 或提示应用正在测试 |
把当前账号加为测试用户,或发布应用(见配置指南第 5 步) |
redirect_uri_mismatch |
下载的 JSON 不是 Desktop app 客户端,按第 4 步重新创建 |
Drive API 403 … insufficient permission |
在客户端所在的同一个项目中启用 Drive API(第 1 步),并保存 Drive 权限范围(第 3 步底部点 Save);然后执行 gdirect login --reauth |
refresh credential expired or revoked |
执行 gdirect login --reauth。如果每 7 天出现一次,把应用从测试状态发布为正式版(第 5 步) |
技术解析:底层是怎么实现的?
如果你对 gdirect 内部是如何用 Rust 配合定制 libaria2 实现上述机制感兴趣,下面结合仓库源码逐一拆解。
1. 候选 IP 扫描与双端点校验
第一次运行传输类命令时,如果本地 SQLite 里没有 24 小时内验证过的可用 IP,src/scan.rs 就会启动扫描。候选池由三部分汇总(上限 256 个):
- 内置引导种子 IP
183.56.143.147(来自 Cealing-Host,每次都会重新探测,从不盲目采信); - 系统 DNS 对
www.googleapis.com、drive.usercontent.google.com、drive.google.com解析出的公网 IP; - SQLite 中过去 7 天内曾经成功连通的历史 IP(超过 7 天未成功的僵死记录会在扫描前自动清理,避免候选池无限膨胀)。
在进入探测池前,public(ip) 会先把私网、环回、链路本地地址以及 198.18.0.0/15(Clash / Mihomo fake-ip 默认网段)全部过滤掉。随后启动 8 个工作线程并发执行 probe(ip):
78fn probe(ip: IpAddr) -> Result<(i64, Option<String>)> {
79 let client = Client::builder()
80 .no_proxy()
81 .redirect(reqwest::redirect::Policy::none())
82 .connect_timeout(Duration::from_secs(3))
83 .timeout(Duration::from_secs(5))
84 .resolve("www.googleapis.com", SocketAddr::new(ip, 443))
85 .resolve("drive.usercontent.google.com", SocketAddr::new(ip, 443))
86 .build()?;
87 let start = Instant::now();
88 let response = client
89 .get("https://www.googleapis.com/drive/v3/about?fields=user")
90 .send()
91 .map_err(|e| e.without_url())?;
92 ensure!(
93 response.status() == reqwest::StatusCode::UNAUTHORIZED,
94 "unexpected Drive API status {}",
95 response.status()
96 );
97 let data: serde_json::Value = response.json()?;
98 ensure!(data["error"]["code"] == 401, "invalid Drive API response");
99 let response=client.get("https://drive.usercontent.google.com/download?id=0B9P1L--7Wd2vU3VUVlFnbTgtS2c&export=download&confirm=t").send().map_err(|e|e.without_url())?;
100 let status = response.status();
101 let warning = if matches!(
102 status,
103 reqwest::StatusCode::FORBIDDEN | reqwest::StatusCode::NOT_FOUND
104 ) {
105 Some(format!("HTTP {status}"))
106 } else {
107 ensure!(status.is_success(), "Drive download probe HTTP {status}");
108 let mut bytes = Vec::new();
109 response.take(32).read_to_end(&mut bytes)?;
110 (bytes != b"spam\n").then(|| "unexpected content".into())
111 };
112 Ok((start.elapsed().as_millis() as i64, warning))
113}这段探测逻辑有三个关键设计:
.no_proxy()+.resolve():强制绕过环境变量代理,在客户端内部把www.googleapis.com和drive.usercontent.google.com直接绑定到待测ip:443,同时走完整的 TLS 证书校验(验证证书链及 SAN 域名匹配,杜绝连上运营商拦截页或中间人)。- 无凭证 API 校验:不携带任何用户 Token 请求
GET /drive/v3/about?fields=user。真正的 Google Drive API 节点一定会返回 HTTP401 Unauthorized且 JSONerror.code == 401。这样既验证了应用层路由,又不会在探测未知 IP 时泄露用户的 Bearer Token。 - 下载链路探针:接着向
drive.usercontent.google.com请求一个公开的小测试文件。如果测试文件本身因 Google 限流返回 403/404,只要前一步 Drive API 校验通过,该 IP 依然会被标记为可用并打印警告,避免因为公开测试文件失效而把正常 IP 全部误杀。
如何自己寻找更多候选直连 IP? 如果内置种子 IP
183.56.143.147在你所在地区的运营商网络下延迟较高,你可以从 DustinWin/ruleset_geodata 的google-cn列表里挑几个域名(如dl.google.com、g.cn、fonts.googleapis.com、gstatic.cn),用国内 DNS(如dig @223.5.5.5 dl.google.com +short)解析出一批你当地运营商的 Google 边缘 IP,每行一个存成ips.txt,再执行gdirect scan --ips ips.txt。探测仪会自动帮你筛出其中持有合法证书且能跑通 Drive API 的最快节点。
2. 定制 libaria2:为什么必须给 C++ 源码打补丁
对于 ls 和 upload,Rust 层的 reqwest 配合 .resolve() 就够用了;但下载大文件需要多连接分段并发和断点续传,gdirect 选择把 aria2 1.37.0 裁剪成纯 HTTP/HTTPS + OpenSSL 3 的静态库 libaria2 嵌进二进制。
问题在于:Google Drive 的下载请求经常会发生跨域名 302 重定向(例如从 www.googleapis.com 或 drive.google.com 重定向到 drive.usercontent.google.com 乃至动态生成的 *.googleusercontent.com)。在原版 aria2 里,如果你只是修改初始 URL,一旦跟随 302 跳转到新域名,aria2 又会去查系统 DNS;而如果你把 URL 里的域名硬改成 IP,TLS 证书校验又会立刻因为主机名不匹配而失败。
为此,gdirect 在构建 libaria2 时打了一个不到 90 行的补丁(vendor/aria2-1.37.0.patch 配合 vendor/gdirect_route.h),在 C++ 底层做了四处改动:
(1)Google Drive 传输域名白名单
先在 vendor/gdirect_route.h 中收拢所有受控的 Google Drive 域名:
1// gdirect's narrow routing hook for the pinned aria2 1.37.0 build.
2#pragma once
3#include <string>
4extern const char* gdirect_ip;
5extern const char* gdirect_sni;
6inline bool gdirect_host(const std::string& h) {
7 const std::string suffix = ".googleusercontent.com";
8 return h == "drive.google.com" || h == "drive.usercontent.google.com" ||
9 h == "www.googleapis.com" || h == "content.googleapis.com" ||
10 h == "drive.googleapis.com" ||
11 (h.size() > suffix.size() && h.compare(h.size()-suffix.size(), suffix.size(), suffix) == 0);
12}(2)在 SocketCore.cc 拦截 getaddrinfo 与 TLS SNI
上层 HTTP 请求保持原始 URL 不变(因此 HTTP Host 头与 OpenSSL 证书校验使用的目标域名完全不受影响)。只有当 SocketCore 真正调用 getaddrinfo 建立连接时,才把命中 gdirect_host(host) 的主机名替换成扫描出的 gdirect_ip,并加上 AI_NUMERICHOST 彻底跳过 DNS 解析。同一处还接管了 tlsSession_->setSNIHostname():
22@@ -936,7 +941,9 @@
23 // "localhost" will produce TLS alert with GNUTLS.
24 if (tlsctx->getSide() == TLS_CLIENT && !util::isNumericHost(hostname) &&
25 hostname.find(".") != std::string::npos) {
26- rv = tlsSession_->setSNIHostname(hostname);
27+ const char* sni = gdirect_host(hostname) ? gdirect_sni : nullptr;
28+ rv = (sni && !*sni) ? TLS_ERR_OK :
29+ tlsSession_->setSNIHostname(sni ? std::string(sni) : hostname);
30 if (rv != TLS_ERR_OK) {
31 throw DL_ABORT_EX(fmt(EX_SSL_INIT_FAILURE,
32 tlsSession_->getLastErrorString().c_str()));
33@@ -1450,6 +1457,10 @@
34 hints.ai_flags = getDefaultAIFlags();
35 hints.ai_flags |= flags;
36 hints.ai_protocol = protocol;
37+ if (host && gdirect_ip && *gdirect_ip && gdirect_host(host)) {
38+ host = gdirect_ip;
39+ hints.ai_flags |= AI_NUMERICHOST;
40+ }
41 return getaddrinfo(host, service, &hints, resPtr);
42 }- 当
gdirect_sni为nullptr(默认)时,正常发送原域名作为 SNI; - 当传入
--sni HOST时,发送伪装的 SNI 主机名; - 当传入
--no-sni时(gdirect_sni为空字符串""),直接返回TLS_ERR_OK跳过设置,让 OpenSSL 发出不带 SNI 扩展的ClientHello。
(3)跨域重定向自动剥离 Authorization 头与重定向白名单
下载私有文件时,初始请求发往 https://www.googleapis.com/drive/v3/files/{id}?alt=media 并携带 Authorization: Bearer <token>。一旦 302 跳转到 *.googleusercontent.com 的临时签名 URL,如果继续携带 Authorization 头,Google 内容节点会因为同时收到两套认证信息而报错,也有跨域名泄露令牌的风险。因此补丁在 HttpRequest.cc 中规定只有当 getHost() == "www.googleapis.com" 时才输出 Authorization: 头;并在 Request.cc 中强制限制所有重定向目标都必须是 https 且满足 gdirect_host()。
(4)去掉 OpenSSL 3 Legacy Provider 依赖
原版 aria2 在 OpenSSL 3 下启动时会无条件调用 OSSL_PROVIDER_load(nullptr, "legacy")(为了 BitTorrent 加密里的 RC4)。在没有安装 legacy 模块的系统上会导致启动直接崩溃。补丁用 #ifdef ENABLE_BITTORRENT 把它围了起来,使纯 HTTP 静态构建不再依赖任何外部 OpenSSL 模块。
3. 下载防呆、大文件病毒扫描确认页与 MD5 校验
在 Rust 侧的 src/download.rs 中,围绕 libaria2 还有三层保障:
191 // Never treat an unrelated existing file as a resumable download.
192 let control = PathBuf::from(format!("{}.aria2", args.output.display()));
193 if args.output.exists() && !control.exists() {
194 ensure!(
195 matches_md5(&args.output, args.md5.as_deref())?,
196 "output already exists; choose another -o path"
197 );
198 if progress.is_none() {
199 println!("Already downloaded {}", args.output.display());
200 }
201 return Ok(());
202 }
203 if progress.is_none() {
204 eprintln!(
205 "libaria2 1.37.0 · IP={} · SNI={} · certificate verification ON",
206 args.ip
207 .map(|ip| ip.to_string())
208 .unwrap_or_else(|| "system DNS".into()),
209 match args.sni.as_deref() {
210 None => "original",
211 Some("") => "none",
212 Some(sni) => sni,
213 }
214 );
215 }
216 let mut progress = progress;
217 for attempt in 0..3 {
218 download(&args, &url, token.as_deref(), progress.as_deref_mut())?;
219 let mut prefix = Vec::new();
220 fs::File::open(&args.output)?
221 .take(1024 * 1024)
222 .read_to_end(&mut prefix)?;
223 // Public downloads can return the virus-scan confirmation form with HTTP 200.
224 // Only Drive's own HTML pages are inspected; other files stay untouched.
225 if token.is_none() && html_page(&prefix) {
226 let text = String::from_utf8_lossy(&prefix);
227 if text.contains("download-form") {
228 let next = drive::confirmation(&text, &url)?;
229 ensure!(attempt < 2, "Drive confirmation loop");
230 fs::remove_file(&args.output)?;
231 url = next;
232 continue;
233 }
234 if text.contains("accounts.google.com/ServiceLogin")
235 || text.contains("Google Drive - Quota exceeded")
236 || text.contains("Google Drive - Access denied")
237 {
238 bail!(
239 "Drive returned a login/quota error page; saved for inspection at {}",
240 args.output.display()
241 );
242 }
243 }
244 ensure!(
245 args.md5.is_none() || matches_md5(&args.output, args.md5.as_deref())?,
246 "MD5 does not match Drive's checksum; the file is kept at {} for inspection",
247 args.output.display()
248 );- 已存在文件的安全检查:如果目标文件已存在且没有对应的
.aria2断点文件,先算一次本地 MD5 与云端md5Checksum比对——匹配则直接提示Already downloaded,不匹配则拒绝覆盖。 - 公开大文件的病毒扫描确认页拆解:未登录状态下通过分享链接下载大文件时,Google Drive 会返回 HTTP 200 的 HTML 确认页(包含
<form id="download-form">)。gdirect只在文件头部确实是<!doctype html或<html时解析该表单,拼出带confirm/uuid的真实下载地址自动重下,绝不会误伤内容里恰好带有download-form字样的普通压缩包或源码文件。 - 落盘后 MD5 校验:下载完成后再次流式计算整个文件的 MD5 并与 Drive 元数据核对,确保多连接切片拼接毫无差错。
4. 32 MiB 分块可恢复上传
上传模块(src/upload.rs)基于 Google Drive v3 的 Resumable Upload 协议。考虑到跨国直连链路的 RTT 较高,分块过小会导致大量时间浪费在等待每个块的确认上,因此将单次分块大小设为 32 MiB。
每次开始上传时,程序以 (本地绝对路径, 目标父目录 ID, 云端文件名) 为键查询本地 SQLite:
85 let mut session = saved.filter(|s| s.size == size && s.modified == modified);
86 if let Some(s) = &session {
87 let response = self
88 .client
89 .put(session_url(&s.url)?)
90 .bearer_auth(&token)
91 .header("Content-Range", format!("bytes */{size}"))
92 .header("Content-Length", 0)
93 .send()
94 .map_err(|e| e.without_url())?;
95 match response.status() {
96 StatusCode::OK | StatusCode::CREATED => {
97 let result = response.json()?;
98 db.unset(&key)?;
99 return Ok(result);
100 }
101 StatusCode::PERMANENT_REDIRECT => start = offset(&response, size)?,
102 StatusCode::NOT_FOUND | StatusCode::GONE => session = None,
103 _ => return Err(failure(response)),
104 }
105 }只要本地文件的大小和纳秒级修改时间没有变化,中断后重跑同一条命令就会先发送 Content-Range: bytes */{size} 探测云端进度,从 308 Permanent Redirect 返回的 Range 头里拿到断点偏移量继续上传。
5. 本机 Loopback + PKCE 授权流
在 src/auth.rs 中,gdirect login 实现了无需外部 SDK 的 OAuth 2.0 Loopback + PKCE(RFC 7636 S256) 流程:
194 let listener = TcpListener::bind(("127.0.0.1", 0))?;
195 listener.set_nonblocking(true)?;
196 let redirect = format!(
197 "http://127.0.0.1:{}/callback",
198 listener.local_addr()?.port()
199 );
200 let state = random_string()?;
201 let verifier = random_string()?;
202 let mut url = url::Url::parse("https://accounts.google.com/o/oauth2/v2/auth")?;
203 url.query_pairs_mut().extend_pairs([
204 ("client_id", client_id),
205 ("redirect_uri", redirect.as_str()),
206 ("response_type", "code"),
207 ("scope", SCOPE),
208 ("access_type", "offline"),
209 ("prompt", "consent select_account"),
210 ("state", state.as_str()),
211 ("code_challenge", challenge(&verifier).as_str()),
212 ("code_challenge_method", "S256"),
213 ]);它在 127.0.0.1:0 上开启一个临时的非阻塞 TCP 监听,校验浏览器回调的 state 参数防 CSRF,再用授权码和 code_verifier 向 https://oauth2.googleapis.com/token 换取 refresh_token。落盘的 SQLite 数据库和临时导出的 rclone 凭据文件在创建时即强制设为 0600 权限,unmount 或挂载失败时也会自动清理导出的临时凭据文件。