NiceLeeのBlog 用爱发电 bilibili~

Android VpnService初探

2019-02-14
nIceLee

阅读:


发现网络上关于Android VpnService的巴啦啦很多,但没有一个能够简单的能够实现抓包全部ip报文,然后不影响正常使用的关于VpnService的Demo,都是只考虑拦截不考虑正常功能的也是醉了…

写在前面

先说说我的理解

  • 假设是抓所有的IP包,那么要想把报文发送出去,一定要自己建立socket负责与外界的沟通(VpnService类提供了一个叫protect的函数),直接往虚拟网卡里面写是不行的!
  • 现在要做的事情:
    • 建立一个NatManager,管理本地端口与与外面连接的对应关系;
    • 建立一个TCPServer,处理对内对外的TCP连接;
    • 建立一个UDPServer,处理对内对外的UDP报文;
    • 建立一个VpnService,专门与VpnService.Builder返回的ParcelFileDescriptor实例,以及TCPServer/UDPServer对接
  • 基于Android VpnService的框架,我们有必要选取三个永远用不到的地址出来作为标识,这样实现难度将大大降低。
    • 一个用于VpnService.Builder.addAddress方法的地址AParcelFileDescriptor实例将其识别为本地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包。虚拟网卡收到后,将其目的地改为指向本地UDPServer6.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")

相似文章

内容
隐藏