需要开发一款流量统计的软件( Android ),于是乎就想到了 VpnService,天然适合。
但是呢,由于 VpnService 收到的是三层的 IP 报文,而我本身没有对于数据修改的需求,只是想简单的记录。
参考了一下部分开源软件的实现,各种代理类软件(比如 SS ),都是将收到的三层报文解析后,发向远程端。 当然这类软件需要修改数据内容。
而我现在不需要修改任何数据,只想单纯的统计发向每个服务端的数据量、时间等,并不关心数据内容。
由于 RawSocket 肯定没法用,设备不存在 root。Java 似乎本身也未提供能够工作在三层的网络通讯方式。
那么是否有什么尽可能简单的方法(或大佬的轮子),可以把三层报文转换成四层,发出去后,再返还给应用的方法?
这里还有一个疑问,由于 Java 中没有找到直接操作三层网络协议的方法,对于非 TCP/UDP 报文(例如 ICMP/IGMP 等),又如何实现直接出口呢?
1
nondanee 2019-08-05 18:29:51 +08:00
不需要修改的话好像挺简单的
我之前看 VPNservice 的 demo 都是 read 进来 write 回去就完了 参考 https://blog.csdn.net/jsqfengbao/article/details/52462125 demo 代码 Github 里有很多 https://github.com/search?q=vpnservice+FileOutputStream&type=Code |
2
realpg 2019-08-05 18:32:43 +08:00
模拟个 null 0 接口出来
用 loopback 手动开阀下一跳丢进去 |
3
realpg 2019-08-05 18:33:16 +08:00
哦 没注意是 android 节点 请无视
|
4
gam2046 OP @nondanee #1,实际上并不是。VpnService.Builder#establish()返回的是 tun0 的句柄,因此 out 实际上应该写入的是返回给应用的响应内容,而把 in 都进来的直接 out 写回去,就变成了 echo 方法,这个请求根本就没有从真是的物理网卡出口。
你给的地址,我搜到过,中间的过程被三个点给一笔带过了.... // Read packets sending to this interface int length = in.read(packet.array()); ... // <- 我关心的恰恰是这里应该怎么做 // Write response packets back out.write(packet.array(), 0, length); |
5
DioV 2019-08-05 20:11:35 +08:00
没有办法,必须程序处理。
现在几个开源的就两种实现,一个是两次 NAT,还一种就是用用户态的 TCP/IP 栈 |
6
ysc3839 2019-08-05 21:02:19 +08:00
@gam2046 这段代码或许参考的是 Android 的 ToyVpn 示例代码 https://android.googlesource.com/platform/development/+/master/samples/ToyVpn/src/com/example/android/toyvpn/ToyVpnConnection.java#225
这个代码是把 IP 包直接通过 UDP 发给远程服务器。 许多开源软件的实现都是用 LwIP 解析 IP 包然后再处理的,可以参考一下 tun2socks。 |
7
1423 2019-08-05 21:03:13 +08:00 via Android
|
8
allenforrest 2019-08-05 21:07:11 +08:00
@gam2046 要先 vpnService.protect(socket) 一下,这样才能确保绑定物理设备发出去,否则还是 tun0,write 就又回去了。
|
9
gam2046 OP |
11
gam2046 OP @ysc3839 然而并不可以,NDK 创建 socket 直接失败(需要 root 权限),Java 没有提供相关方法,Java 只提供封装后的 UDP/TCP 相关类。网上的实现是调用 shell ping 然后读取 stdin
|
12
ysc3839 2019-08-05 23:47:27 +08:00 via Android
@gam2046 那调用 ping 为何不需要 root 权限呢?假如真的不需要,你也起个新进程来发送 ICMP 不就好了?
|
14
ysc3839 2019-08-06 14:40:51 +08:00 via Android
@wwqgtxx 我确认了一下,ping 并没有 suid 权限。
``` ~$ which ping /system/bin/ping ~$ stat /system/bin/ping File: `/system/bin/ping' Size: 69208 Blocks: 88 IO Blocks: 512 regular file Device: fc00h/64512d Inode: 7235 Links: 1 Access: (755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 2000/ shell)Access: 2009-01-01 00:00:00.000000000 Modify: 2009-01-01 00:00:00.000000000 Change: 2009-01-01 00:00:00.000000000 ``` 再者,即使系统自带的 ping 有 suid,Termux 这个软件用的可不是系统的 ping 而是自己的 ping,仍然是可以正常使用的。 ``` $ which ping /data/data/com.termux/files/usr/bin/ping $ /data/data/com.termux/files/usr/bin/ping 192.168.1.1 PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. 64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=18.7 ms ^C --- 192.168.1.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 18.752/18.752/18.752/0.000 ms ``` |
15
gam2046 OP @wwqgtxx #13
@ysc3839 #14 感谢。我翻阅了一下 aosp 的源码。 https://android.googlesource.com/platform/external/ping/+/27ca8cd5cb0891c8a15175b52c5c24253dea5b17/ping.c 结果.... 我原本是这样写的 socket(AF_INET, SOCK_RAW, IPPROTO_ICMP),毫无疑问返回了-1 然而 aosp 里 ping 是创建的 icmp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); // 1 if (icmp_sock != -1) using_ping_socket = 1; else icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); // 2 嗯....显然在 1 处创建时一定成功的,如果 1 的地方失败,按理说 2 应该是一定创建失败的。 |
16
wwqgtxx 2019-08-06 18:09:06 +08:00 via iPhone
@gam2046 我记得一篇文章上写过,直接创建 SOCK_DGRAM 是可以成功的,不需要 root 权限,但是好像是拿不到完整的 ICMP 数据包,需要自己想办法构建,可以看一下这个
https://github.com/bgylde/PingForAndroid |
17
lkbert 2020-07-17 11:01:55 +08:00
@gam2046 这个问题最后是咋解决的啊,我现在也遇到这个问题,现在在写一个 vpn 的 app 来模拟网络延迟,对 TCP 和 UDP 做了处理,但是对 ICMP 协议不知道咋处理,查遍所有资料基本都是对 ICMP 丢弃,应用层执行 shell ping 就会收不到链接,我尝试直接将 ICMP 包写入 ,如你之前所说的“VpnService.Builder#establish()返回的是 tun0 的句柄,因此 out 实际上应该写入的是返回给应用的响应内容”,就没有任何响应了 ,这个不知道该咋处理了
|
18
gam2046 OP @lkbert #17,如果 TCP/UDP 你已经处理完毕,那么你遇到关于 ICMP 情况可以参考 AOSP 相关的代码。
https://android.googlesource.com/platform/external/ping/+/27ca8cd5cb0891c8a15175b52c5c24253dea5b17/ping.c#121 Java 层并没有暴露除 TCP/UDP 以外的编程能力。 |
19
lkbert 2020-07-17 20:42:06 +08:00
@gam2046 嗯 ,现在是我用 jni 实现 native ICMP 了,但是 Echo Reply 怎么给回应用的 socket,不知道怎么整了。请教下,你那边有处理方案吗?
|
20
CrazyBoyFeng 2021-03-24 16:07:52 +08:00
@gam2046 #9
请问你最终实现报文转发了吗? 我搜了一圈,网上几乎都是 NAT 实现的。java 层似乎并不能实现用户态协议栈,因为不能发 raw 包,只能发 java 封装好的 tcp 和 udp 包。可以借助 jni 可以发 raw 包,但是如你所说,需要 root 。 |
21
CrazyBoyFeng 2021-03-24 19:07:02 +08:00
至于为什么 tun2socks 使用用户态协议栈 lwip,那是因为它把包发给 socks 服务器了,而不是传输修改了 header 的 tcp 和 udp 。jvm 上并不能修改包头并重新发送。如果能直接发的话,题目的要求(转发)将变得十分简单。
所以 jvm 要转发只有俩实现方案: 1. 本地起个 socks 服务器,tun2socks 转给 socks 服务器。socks 往外的连接要传给 android protect() 一下。 2. nat 实现。各自缓存一套 tcp 和 udp 的 natsession map 。收到来自 lan 的包,检查一下有没有 session,有的话直接取出来往 wan 传送 data 。没有的话建立一个 protect() wan 连接并存入 session map 。tcp 要处理握手和挥手,收到 lan 握手包建立外部连接,lan 挥手包关闭连接清除 session,如果是 wan 关闭连接则向 lan 发送挥手包。wan udp 连接设置个 timeout,超时自动关闭,关闭时清除 session 。 |
22
9ttttttt 2021-04-08 14:24:16 +08:00
@CrazyBoyFeng 请问 Android 上面只能发 TCP 和 UDP 包么?那如果我实现一个基于 VPNService 的 Android VPN 应用,是不是除了 TCP 和 UDP 包的其他数据包会被截断呢?想问一下可以对 IP 包里面有没有可以自定义的标志位或信息字段呢,我想基于 IP 包里面自定义的标志位或信息字段来在 Android 上实现是否向 VPN 发数据的判断,类似于 PAC 那种想法。谢谢大佬!
|
23
CrazyBoyFeng 2021-04-23 01:49:02 +08:00
@9ttttttt java 层无法建立除 tcp 和 udp 以外的通信。VPNService 可以收到 icmp 包,以字节数组的形式,但是无法在不 root 的情况下发出去,只能丢弃。
你的第二个问题,大概是想实现类似 iptables 这类的东西? iptables 打标记的原理并不是修改数据包,而是建立数据表。而 java 层也不能改包,所以就不能发送自定义内容的数据包。 |
24
jeesk 2022-12-21 23:08:39 +08:00
问题解决了吗? 这个恐怕需要创建一张网卡来操作?
|
25
lysShub 234 天前
四年了,请问解决了吗?
|