Android VPN Service实现免root防火墙

Android VPN Service实现免root防火墙

🗨

在使用android手机过程中,发现自己手机的流量经常被一些不小心下载的带广告的应用偷走了。自己在各个市场上搜了下,绝大部分防火墙都是需要手机root的。其中多数又是修改自著名的Droidwall,例如github上这个项目:https://github.com/skullone/android_firewall。其原理是在root后的机器使用root权限来配置iptable,利用linux这个自带的防火墙实行流量控制。

但root本身破坏了android的安全机制,反而容易导致系统被恶意代码利用。在googleplay上,终于找到一款noroot firewall。它利用了android的vpnservice技术,实现了免root的防火墙。很感兴趣,于是在网上找了些资料,这里分享下自己的经验。

Android系统支持配置VPN service。可以在设置->更多->VPN中添加。但系统只支持部分VPN协议。如果用户想实现自己的VPN协议,那怎么办呢?为了支持这种扩展,Android提供了VpnService类。这是一个Service的子类。一旦start了该service,它会创建一个类似于应用代理的服务。任何应用外出的包,都会先发给该服务,然后该服务再转发到网络上。于是这个VpnService就成为需要使用网络的应用和网络服务器之间的一个中间人。这就提供了一个机会来控制外出流量。

VpnService和client交互

那VpnService是如何跟client应用通信的呢?这里利用了linux的TUN/TAP机制。TUN/TAP提供了一种虚拟的软网络接口(相对于物理网卡而言)。开发人员可以打开这个软接口设备,获取一个文件描述符(设备即文件),然后read就是从其中读数据,write就是向其中写数据。不同于物理网卡接口是把数据发送到网上或者接收自网上(其实是写/读到内核里,然后网卡驱动发/收到网上),该虚拟网络接口是读/写到应用空间。当建立其TUN/TAP后,client应用本来要发送到实际网卡的包,都会发送给这个虚拟的tun/tap,此时vpnservice就可以read到client发送的数据;vpnservice可以写数据到tun/tap中,此时client如果recv或read,那就会读取这个虚拟网口,获取vpnservice写的数据。这个我估计是通过修改路由表实现的,使得所有的网楼包都优先走tun/tap虚拟网口。

tun和tap的区别是前者这有IP头和IP负载(即三层和以上),而tap包括数据链路层头(二层和以上)。在Android中,实际是一个tun,读写的数据是IP原始报文。为了验证vpnservice创建了一个虚拟网口,可以下载noroot filewall,然后通过adb shell netcfg。如果已经打开了vpnservice,则名字是tun0的接口,其状态是UP。

关于tun/tap可以参考http://backreference.org/2010/03/26/tuntap-interface-tutorial/https://www.kernel.org/doc/Documentation/networking/tuntap.txt。这是linux的机制,而不仅仅是android的。

VpnService和网络服务器交互

那VpnService如果把收到的数据发送到网上呢?不同于普通linux允许用户发送原始ip报文,Android安全机制只允许socket发送普通的tcp/udp报文。这就要求我们把通过tun收到的ip报文进行解包,获取其tcp/udp payload(即应用层数据),然后通过send发送到服务器。在接收的时候,需要把服务器过来的recv的数据,添加tcp/udp头和ip头,然后write到tun中。对于tcp具体而言要复杂很多:

  • 如果受到的是syn包,则要connect
  • 如果connect成功,则需要返回给tun一个syn ack。这样子三次握手结束(因为不用处理最后一个ack)。
  • 传输的时候要记录双向的seq,以生成seq和ack序号。
  • 收到fin包的时候需要close,并返回fin ack。有时候是服务器发起连接终止,此时需要向客户端发fin。
  • 收到rst同样要close连接。

某种程度上,这里要重现实现一个tcp协议栈,不过要比完整的简单很多。

实现

首先要start service,这一般在activity提供一个button实现。并不是直接startservice,而是要先打开一个需要用户同意的activity,当用户同意时,才能打开service。这是为了防止恶意vpnservice偷窥网络数据。

参考这段代码(在activity中):

复制代码
    public void enableVpnService() {
        Intent intent = MyVpnService.prepare(getApplicationContext());
        if (intent != null) {
            startActivityForResult(intent, 0);
        } else {
            onActivityResult(0, RESULT_OK, null);
        }
    }

    @Override
    protected void onActivityResult(int request, int result, Intent data) {
        if (result == RESULT_OK) {
            Intent intent = new Intent(this, MyVpnService.class);
            startService(intent);
        }
    }
复制代码

然后在service的onstartcommand中,需要创建tun。

为了方便VpnService创建tun,Android提供了一个Builder类VpnService.Builder。例如我们可以调用:

Builder builder = new Builder();
builder.addAddress("10.0.8.1", 32).addRoute("0.0.0.0", 0).setSession("Firewall")
                .setMtu(1500);
ParcelFileDescriptor interface = builder.establish();

此interface就代表着tun。只可以对其进行读写。为了避免ANR,最好是创建一个thread来做。

对于真实跟外界交互的socket,如果你对其不做任何处理,那数据会再次发送到tun中,造成死循环。所以此时要调用VpnService.protect保证该socket发送的数据都直接发到物理网卡。

Android提供了一个ToyVpn的sample project,可以搜索看其代码,也可以在eclipse创建android sample code来看。


频道:Android