发现网络上关于Android VpnService的巴啦啦很多,但没有一个能够简单的能够实现抓包全部ip报文,然后不影响正常使用的关于VpnService的Demo,都是只考虑拦截不考虑正常功能的也是醉了…
补充
- Talk is cheap, show me the code.
写在前面
- 如何使用Android系统自带的VPN服务框架
- https://developer.android.com/reference/android/net/VpnService
- 原理啥的也不多说了,把踩到的坑记录下来(假设抓所有包)。
先说说我的理解
- 假设是抓所有的IP包,那么要想把报文发送出去,一定要自己建立socket负责与外界的沟通(VpnService类提供了一个叫protect的函数),直接往虚拟网卡里面写是不行的!
- 现在要做的事情:
- 建立一个NatManager,管理本地端口与与外面连接的对应关系;
- 建立一个TCPServer,处理对内对外的TCP连接;
- 建立一个UDPServer,处理对内对外的UDP报文;
- 建立一个VpnService,专门与VpnService.Builder返回的ParcelFileDescriptor实例,以及TCPServer/UDPServer对接
- 基于Android VpnService的框架,我们有必要选取三个永远用不到的地址出来作为标识,这样实现难度将大大降低。
- 一个用于VpnService.Builder.addAddress方法的地址A,ParcelFileDescriptor实例将其识别为本地ip,把它当作localhost就行
- 一个用于标识本地TCP Server的地址B
- 处理实例读出的本地TCP IP报文时,不妨将本地source ip 设为地址B
- 这样TCP server收到报文,可以根据source ip是否为地址B,判断请求来自网络还是内部。
- 如果是内部请求建立报文,向外部网络发起TCP建立连接。将此时的两个连接结对。
- 如果是内部一般报文,转发给结对的外部。
- 如果是外部一般报文,转发给结对的内部。这时,写给内部的ip报文的目的地址就是地址B了(从哪里来,回哪里去)。
- 此时,我们再看实例读出的TCP IP报文时,可以根据目的地址是否是地址B来判断报文是否来自本地Server。
- 一个用于标识本地UDP Server的地址C
- 处理实例读出的本地UDP IP报文时,不妨将本地source ip 设为地址C
- 这样UDP server收到报文,可以根据source ip是否为地址C,判断请求来自网络还是内部。
- 如果是内部,直接转发给外部
- 如果是外部,直接通过实例返回给本地应用即可
- 此时,我们知道,实例读出的UDP IP报文,只会是本地报文。
- 三个地址可以变为两个地址,因为地址B和地址C可以是相同的。
坑s
- 什么IP包会往虚拟网卡发?或者说,虚拟网卡出入的IP报文怎么才算有效的?
- 只有本地到外地,或者外地到本地的报文,才会途径虚拟网卡。
- 比如说, 本地6666端口,往本地9999端口发送了一个UDP包,其对应的IP报文不会经过虚拟网卡,不要指望读ParcelFileDescriptor实例能够得到这份IP报文。
- 比如说, 本地6666端口,往外地
8.8.8.8:53
发送了一个UDP包。虚拟网卡收到后,将其目的地改为指向本地UDPServer,6.6.6.6:6666
,重新写入虚拟网卡。
但这时本地UDPServer并不会收到这个报文,只有将这个IP报文的源地址更改一下,例如由本地ip改为8.8.8.8(原目的地址),这个报文才会被本地UDPServer收到。 - PS:这里的
6.6.6.6:6666
中的IP地址,即前文所说的地址A,取这个地址只是因为6666比较好看 - PS2:这里说的
将本地IP改为原目的地址
只是一个举例,事实上任意非本地IP都行,这取决于你的实现。但建议改成地址C,方便逻辑判断。
- TCP/UDP/IP报文长度
- 没有接收或接收到不服预期的内容,可能是这个原因。
- TCP/UDP校验
- 没有接收或接收到不服预期的内容,可能是这个原因。
- 关于DNS报文
- 似乎如果不在Builder生成时指定DNS服务器,手机本身另有一套机制,使得系统默认的DNS查询不会经过虚拟网卡。如果有需要,不妨再加一条:
builder.addDnsServer("114.114.114.114")
- 似乎如果不在Builder生成时指定DNS服务器,手机本身另有一套机制,使得系统默认的DNS查询不会经过虚拟网卡。如果有需要,不妨再加一条: