最近开发一个让指定 app 走 http 代理的经过…..
由来
想劫持某个 app 的封包做一些验证,初步想法是找下 golang socks5 代理 server 上对每个 conn 的字节流反序列化后做动作,然后发现如果设备多了的话,没有代理池基本源 IP 就是一个了,所以想着手机端上有没有非 root 下让指定 app 走代理的代理软件。
然后想起了 v2rxxNG
里可以指定 app 走代理,于是研究折腾了一番到学习 kotlin 和安卓 compose 自己开发安卓 app。
底层探讨经过
先大致看了下 v2rxxNG
的代码,发现用的 tun2socks
技术,主要是 tun 接口,也就是 Linux 的 tun 技术。
Tun
在 Linux 里一切皆文件,Tap/Tun 是 Linux 提供的用户态封装报文的接口,Tap 是数据链路层二层,Tun 是网络 IP 层三层,Tun 一端连着内核的协议栈,另一端连着用户态的进程。使用流程是:
- 程序使用现有或者创建的虚拟网卡(Tap/Tun)
- 程序需要对收和发的报文进行处理(也就是读写 /dev/net/tun 字符设备),程序的逻辑就像物理网卡的硬件功能一样
详细流程见理解Linux虚拟网卡设备tun/tap的一切,不搞啥虚拟交换机,而是常规做软件代理隧道啥的一般都是使用 Tun。
例如 linux 机器使用 socks5 tun 模式代理:
- 走路由或者 iptables fmark 匹配到发往 tun 网卡
- Linux 协议栈会把 TCP/IP 报文发到用户态程序的 socks5 client
- client 程序解析 IP 层的数据,封装成 socks5 协议的包发出去
- 协议栈收到后发往物理网卡,物理网卡发出去
- socks5 server 端收到报文,解析后,本机发往目标地址
这样目标地址看到就是 socks5 server 的 IP 请求的自己了,和 socks5 无关,代理隧道啥的基本都是这样的工作原理。
更详细的图文见:
- https://www.junmajinlong.com/virtual/network/data_flow_about_openvpn/
- https://www.zhaohuabing.com/post/2020-02-24-linux-taptun/
安卓和 Tun
v2rxxNG 是把 badvxn
编译成二进制文件,放在 libs 下命名为 libtun2socks.so
,这样 Android 在安装 app 的时候会自动给 .so
后缀的文件 +rx
执行权限,在没有 root 的情况下,应用程序是无法给一个文件增加 x 权限的,这也算是一个骚操作。
然后在安卓开发的时候,安卓提供了接口,需要继承 VxnService
,在其类的内部使用 Builder()
创建 tun,只不过安卓上不再是 /dev/net/tun
而是文件描述符了:
1 | ParcelFileDescriptor tunDevice = new Builder() |
然后 libtun2socks
使用这个 fd 运行,badvxn
是基于 LwIP
修改的实现,c 语言基本都忘光了,找了下 golang 的实现看看,找到了 https://github.com/xjasonlyu/tun2socks
,为啥选它是应为它内置了好几个模式,例如 direct ,修改起来应该比较简单(这里我不去追求性能极限,以需求优先而选型)。
安卓开发
搜了下相关没有搜到现有的轮子,唯一一个比较接近我需求的 appproxy 是 flutter 写的,并且代理类型只有 http 和 socks5,没办法,就去学习了下 kotlin 和安卓开发。
相关资源
2025年,搞这种稍微偏向底层的对接的,当然是学习 kotlin 搞安卓开发了,之前看到的 kotlin 中文翻译文档 看了下感觉从学习路线来看好琐碎,安卓官方开发文档页面又对 kotlin 介绍很少,都是课程里穿插着基础知识。完整体系的还是要从互联网上找下看看,下面的 gist 是收藏的一些,基本都看过的:
https://gist.github.com/zhangguanzhang/cd2f3eb20de5a1314e5d3802401aa192
先学 kotlin,再去看安卓官方的教程,安卓官方文档是实战带你入门安卓开发,相对于完整安卓开发体系来说还是缺少很多内容,例如 Service、Intent 和 Flow 啥的讲解很少或者基本没讲。
需求分析
主要需求为如下:
- 添加、修改、删除代理配置
- 通知栏的前台运行通知
- 右下角的浮动开关
- tun2socks 对接启动
- app 选择界面选择哪些 app 走代理
快速看完官方文档的 compose 开发后,基于 inventory-app 复制修改开始,各个界面跳转用 navigate ,另外还发现 tailscale-android 也是全部用 kotlin + jetpack compose 开发的,可以从里面学下代码。
tun2socks aar
根据文档 tun2socks 的 How to use file descriptor in Android 得知,集成到安卓是要利用 gomobile 编译出安卓的 aar 后代码里加载(而不是 v2rxxNG
那样运行二进制文件)。先编译出来后写个最小 demo 试试看,需要 NDK 和 SDK_TOOLS 和 golang,搜索了下后制作了一个带环境的 docker 镜像,直接下面步骤快速编译:
1 | git clone https://github.com/xjasonlyu/tun2socks |
issue 里基本都是 java 开发的,我这里是 kotlin 和最新版本的 Android Studio
,最新的版本里,都是用 kotlin 的 DSL gradle 了,按照 issue 里的添加后在 Android Studio
里一直报红,然后搜索后下面的才行:
1 | # app 下的 buid.grade.kts 的 依赖下添加 |
把上面的 aar 文件放 app/libs/
下即可,使用和 issue 里一样。
前台
官方的 demo Toy 非常老,而且是 java 的,这里是我找的官方文档和一些参考代码:
- 前台服务
- android-vxn-implementation-guide
- https://github.com/microsoft/HydraLab/blob/main/android_client/app/src/main/java/com/microsoft/hydralab/android/client/vpn/HydraLabVpnService.kt
前台服务运行需要 Notification
,参考下别人代码后 Android Studio
模拟器里状态栏前台需要的通知出不来,在真机安卓 11 试了下没问题,发现模拟器的安卓版本太高了,谷歌了下需要加下面代码申请:
1 |
|
然后启动代理闪退,发现没有像以前用的 app 那样弹窗口和钥匙,就是说该 app 申请 vpn
接口,会对啥啥的,需要调用:
1 | VpnService.prepare(this) |
一些其他细节问题
启动后,切出去,再从通知栏进来,发现浮动按钮状态和实际不一致,最后还是按照 Binder 和 Service 通信才行,以及 Broadcast
让按钮状态和实际一致。
完整代码在 appproxy
后续
app 开发完成后,后续就是 fork tun2socks 复制 direct 后在 conn 对流解析和修改发送 ,当然可以天马行空下不要局限在代理层面,例如用户态 kotlin 直接对 fd 的流转发下只统计访问 ip,就可以做一个简单的网络访问统计了。