工作原理
一条连接进来,一条规则做判断,判断结果被记下来。全部发生在你的手机上 —— 没有任何一步需要别的机器参与。
为什么必须申请 VPN 连接
Android 只给了应用一条正规途径去看其他应用的流量:VpnService 接口。御流申请它,然后用它当一个本地防火墙。
建立隧道之后,每个应用的连接都会先进入御流自己的 TUN 网卡,被判定,然后要么当场拒绝,要么原样直连出去。
关键在于转发这一步不存在 —— 御流没有远端服务器,也就没有地方可以把流量转出去。这与「用 VPN 去广告」的常见做法在架构上就是两回事。
两层判断,因为主机名不总是够用
拆成两层不是为了显得复杂,而是因为「能判断什么」本身分成两种:只看主机名就能定的,和必须看到完整地址才能定的。
主机名即答案
连接到 ad.doubleclick.net,这个主机名本身就说明了问题。域名层的规则就是一份域名清单 —— 不解密任何内容,命中即拒绝。内置 441,386 条。
代价:几乎没有。CPU 占用可以忽略,也看不到任何请求内容。
同一个域名下也要分路径
同一个主机名上,「正文页」和「广告位」往往是同一个域名。深度解析在本机解密连接、读出完整路径再判断 —— 所以 /article 放行、/promo 拒绝。内置 11,135 条地址规则,需要你手动开启并装一次证书。
代价:这是侵入性最强的一层,因为它要解密。所以它默认不开,且可以只对你选的应用生效。
一次判定长什么样
下面是内核在真机上打出的一条日志(只把应用包名换成了示例名)。它一次说清了四件事:
[TCP] 172.19.0.1:44142(com.example.app, uid=10123) --> www.google-analytics.com:443 match RuleSet(cleardeck-track) using REJECT - com.example.app, uid=10123
- 这条连接属于哪个应用 —— 这是「按应用」统计的来源
- www.google-analytics.com:443
- 它想去哪里
- RuleSet(cleardeck-track)
- 命中了哪个模块 —— 拦截构成那张环形图就是按这个字段归类的
- using REJECT
- 判定结果。若是放行,这里会是直连出去
按应用归因是怎么做到的
内核被设置为始终解析连接归属的进程,每来一条连接,应用就向系统查一次这个 socket 属于哪个 uid。
这个查询不便宜,所以做了一层信号量节流 —— 这是仪表盘上每一格统计都能按应用拆开的原因。
认不出归属的流量不会消失也不会被算错,它们会显示为「未知来源」或 uid xxx。
两个进程,一个引擎
- 界面进程
- Compose 界面跑在应用主进程里
- 引擎进程
- 内核跑在独立的
:tun进程里 - 所以
- 界面崩溃不会掐断 VPN —— 防护还在跑,只是你看不到它
- 一个 Go 运行时
- 内核与深度引擎编译成同一个
libclash.so,因为一个进程装不下两个 c-shared Go 运行时
数字是怎么来的
拦截次数
来自内核的日志流,不是轮询快照 —— 所以统计不受采样间隔影响,一次都不会漏。
流量去向
按可注册域名聚合,同一个站点的子域会合成一条,CDN 子域不会碎成几十行。
已节省流量
这是估算:每条被拒绝的连接,按「同一个主机名在放行连接上的平均大小」计价;只被拦截过、从没成功连接过的主机,按每次 16 KB 估。
刻意不做的事
- 没有远程节点,也没有任何可以中转流量的服务器。
- 不需要 root,不改 hosts 文件,不动系统分区。
- 没有 WakeLock、没有 AlarmManager、没有 WorkManager、没有 JobScheduler、没有开机广播接收器 —— 隧道靠前台服务维持,不靠持有唤醒锁。
- 不收集你的上网流量内容。永远不。
代价是多少
实测:调优后,30 分钟内引擎进程累计 2,067 jiffies = 20.67 秒 = 1.148% 单核;同一时段界面进程只有 19 jiffies。
调优后的采样周期
| 项目 | 改前 | 改后 | 省电模式 |
|---|---|---|---|
| 连接快照推送 | 1 s | 3 s | 15 s |
| 通知刷新 | 3 s | 5 s | — |
| 统计落库 | 15 s | 30 s | — |
| 日志折叠 | 500 ms | 1 s | — |
| 界面轮询 | 5 s | 10 s | — |
| 内网扫描 | 10 s | 30 s | — |
把连接快照从 1 秒放宽到 3 秒,是唯一值得牺牲的一项:每一次快照都要把所有活着的连接重新序列化成 JSON,界面看没看都在发生。放弃的是「生命周期短于一个周期的连接」的字节数与一条历史记录;长连接的字节数不受影响(累计值靠差值算),被拦截的连接完全不受影响(拒绝来自日志流,不走这个快照)。
上线时通知你
第一个版本正在收尾。留下邮箱,准备好时我们通知你一次 —— 只发这一封。
预约通道即将开放。
仅用于上线通知,不发推广邮件,也不会分享给第三方。