VpnService 能否原样将三层的 IP 报文发出去?

2019-08-05 17:28:27 +08:00
 gam2046

需要开发一款流量统计的软件( Android ),于是乎就想到了 VpnService,天然适合。

但是呢,由于 VpnService 收到的是三层的 IP 报文,而我本身没有对于数据修改的需求,只是想简单的记录。

参考了一下部分开源软件的实现,各种代理类软件(比如 SS ),都是将收到的三层报文解析后,发向远程端。 当然这类软件需要修改数据内容。

而我现在不需要修改任何数据,只想单纯的统计发向每个服务端的数据量、时间等,并不关心数据内容。

由于 RawSocket 肯定没法用,设备不存在 root。Java 似乎本身也未提供能够工作在三层的网络通讯方式。

那么是否有什么尽可能简单的方法(或大佬的轮子),可以把三层报文转换成四层,发出去后,再返还给应用的方法?

这里还有一个疑问,由于 Java 中没有找到直接操作三层网络协议的方法,对于非 TCP/UDP 报文(例如 ICMP/IGMP 等),又如何实现直接出口呢?

14877 次点击
所在节点    Android
25 条回复
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
realpg
2019-08-05 18:32:43 +08:00
模拟个 null 0 接口出来
用 loopback
手动开阀下一跳丢进去
realpg
2019-08-05 18:33:16 +08:00
哦 没注意是 android 节点 请无视
gam2046
2019-08-05 18:46:17 +08:00
@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);
DioV
2019-08-05 20:11:35 +08:00
没有办法,必须程序处理。
现在几个开源的就两种实现,一个是两次 NAT,还一种就是用用户态的 TCP/IP 栈
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。
1423
2019-08-05 21:03:13 +08:00
allenforrest
2019-08-05 21:07:11 +08:00
@gam2046 要先 vpnService.protect(socket) 一下,这样才能确保绑定物理设备发出去,否则还是 tun0,write 就又回去了。
gam2046
2019-08-05 21:16:19 +08:00
@DioV 二次 NAT,工程量毕竟大,相比较我更愿意接受用户态自己实现协议栈,但问题在于 Java 也好,NDK 也罢,在不取得 ROOT 权限的情况下,似乎都不能发起 TCP/UDP 以外的请求。那么我在用户态收到例如 ICMP 的请求,应该如何实现呢?


@ysc3839 #6
@1423 #7

感谢两位,我现在去了解一下 LwIP/tun2socks 等相关信息。稍后再来询问 /感谢两位。
ysc3839
2019-08-05 23:25:16 +08:00
@gam2046 应该是可以发 ICMP 的,不然哪来那么多 ping 的工具?
gam2046
2019-08-05 23:30:31 +08:00
@ysc3839 然而并不可以,NDK 创建 socket 直接失败(需要 root 权限),Java 没有提供相关方法,Java 只提供封装后的 UDP/TCP 相关类。网上的实现是调用 shell ping 然后读取 stdin
ysc3839
2019-08-05 23:47:27 +08:00
@gam2046 那调用 ping 为何不需要 root 权限呢?假如真的不需要,你也起个新进程来发送 ICMP 不就好了?
wwqgtxx
2019-08-06 09:41:36 +08:00
@ysc3839 因为 Ping 设置了 suid 呀,和 su/sudo 一样的原理
ysc3839
2019-08-06 14:40:51 +08:00
@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
```
gam2046
2019-08-06 16:09:29 +08:00
@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 应该是一定创建失败的。
wwqgtxx
2019-08-06 18:09:06 +08:00
@gam2046 我记得一篇文章上写过,直接创建 SOCK_DGRAM 是可以成功的,不需要 root 权限,但是好像是拿不到完整的 ICMP 数据包,需要自己想办法构建,可以看一下这个
https://github.com/bgylde/PingForAndroid
lkbert
2020-07-17 11:01:55 +08:00
@gam2046 这个问题最后是咋解决的啊,我现在也遇到这个问题,现在在写一个 vpn 的 app 来模拟网络延迟,对 TCP 和 UDP 做了处理,但是对 ICMP 协议不知道咋处理,查遍所有资料基本都是对 ICMP 丢弃,应用层执行 shell ping 就会收不到链接,我尝试直接将 ICMP 包写入 ,如你之前所说的“VpnService.Builder#establish()返回的是 tun0 的句柄,因此 out 实际上应该写入的是返回给应用的响应内容”,就没有任何响应了 ,这个不知道该咋处理了
gam2046
2020-07-17 13:20:09 +08:00
@lkbert #17,如果 TCP/UDP 你已经处理完毕,那么你遇到关于 ICMP 情况可以参考 AOSP 相关的代码。

https://android.googlesource.com/platform/external/ping/+/27ca8cd5cb0891c8a15175b52c5c24253dea5b17/ping.c#121

Java 层并没有暴露除 TCP/UDP 以外的编程能力。
lkbert
2020-07-17 20:42:06 +08:00
@gam2046 嗯 ,现在是我用 jni 实现 native ICMP 了,但是 Echo Reply 怎么给回应用的 socket,不知道怎么整了。请教下,你那边有处理方案吗?
CrazyBoyFeng
2021-03-24 16:07:52 +08:00
@gam2046 #9
请问你最终实现报文转发了吗?
我搜了一圈,网上几乎都是 NAT 实现的。java 层似乎并不能实现用户态协议栈,因为不能发 raw 包,只能发 java 封装好的 tcp 和 udp 包。可以借助 jni 可以发 raw 包,但是如你所说,需要 root 。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/589264

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX