Android 下代理的使用探究

1. 代理的简介

代理,也称网络代理,是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。

例如上图所示,手机处在网络 A,它无法直接访问网络 B ,但是却可以通过代理服务 C,向网络 B 发起请求并得到响应,从而实现间接的访问网络 B,有点类似于 MVP 的模式。

2. VPNService 简介

要在 Android 上使用代理,可以在系统设置的 WIFI 处设置代理服务器的 IP 地址和端口。但这种代理方式有很大限制,最明显的就是手机流量模式下无法使用,并且只能代理系统浏览器和一些使用 HTTP 协议的应用,不能做到全局的代理。

在 Android 4.0 开始,系统提供了一个使用全局代理的方式,那就是 VPNService。

具体实现方式如下所示:

  • 应用程序使用 socket,将相应的数据包发送到真实的网络设备上。
  • Android 系统将所有的数据包转发到 TUN 虚拟网络设备上去,端口是tun0
  • VPN 程序通过打开/dev/tun设备,并读取该设备上的数据,可以获得所有转发到 TUN 虚拟网络设备上的IP 数据报
  • VPN 程序可以对数据做一些处理,然后将处理过后的数据报,通过真实的网络设备发送出去。

简单的理解为:系统帮我们做了一些事情,可以在 VPNService 中创建并初始化好 TUN虚拟网络设备,并且通过它得到应用发送的 IP 数据报

当应用收到代理服务器返回的消息时,仍然会通过 TUN虚拟网络设备。

如上图所示。

简洁的代码示例如下:

  1. // inputStream 得到所有发送出去的 IP 数据包
  2. FileInputStream in = new FileInputStream(interface.getFileDescriptor());
  3. // outputStream 得到所有返回的 IP 数据包
  4. FileOutputStream out = new FileOutputStream(interface.getFileDescriptor());
  5. // 缓存字节数组
  6. ByteBuffer packet = ByteBuffer.allocate(32767);
  7. ...
  8. // 读取所有要发送出去的 IP 数据包
  9. int length = in.read(packet.array());
  10. ...
  11. // 将远端服务器返回的数据写入系统的 TCP/IP 栈中
  12. out.write(packet.array(), 0, length);

Network Activity -> TUN -> in -> tunnel -> Remote Server
Network Activity <- TUN <- out <- tunnel <- Remote Server

总之,不管是实现代理还是实现 VPN,通过 VPNService 来全局得到手机数据都是第一步。

所以一切分析的起点也都是从得到手机数据包开始的。

关于详细解析可以参考博文:

301 Moved Permanently

2.1. TUN 设备

关于 TUN 设备。Android 底层基于 Linux 的,而 TUN 也是 Linux 中的一个概念。

在计算机网络中,TUN 是操作系统内核中的虚拟网络设备。不同于普通靠硬件网路板卡实现的设备,这些虚拟的网络设备全部用软件实现,并向运行于操作系统上的软件提供与硬件的网络设备完全相同的功能。

TUN模拟了网络层设备,操作第三层数据包比如 IP 数据封包。

操作系统通过 TUN 设备向绑定该设备的用户空间的程序发送数据,反之,用户空间的程序也可以像操作硬件网络设备那样,通过TUN 设备发送数据。在后种情况下,TUN 设备向操作系统的网络栈投递(或 “注入” )数据包,从而模拟从外部接受数据的过程。

正因为它是基于 Linux 的,所以 Linux 上的一些关于网络方面的优秀开源库可以通过 NDK 编译在 Android 上使用。

具体可参考:https://www.ibm.com/developerworks/cn/linux/l-tuntap/

2.2. 为什么是 IP 数据报?

在前面曾多次提到 IP 数据报,为什么是 IP 数据报而不是 TCP 或者 UDP 数据报呢?

在这里首先要涉及一下网络 ISO 的七层模型,如下图:

层次划分及其部分相关协议:

  • 第7层应用层(Application Layer)
    • HTTP、SNMP、DNS、FTP…
  • 第6层表达层(Presentation Layer)
  • 第5层会话层(Session Layer)
  • 第4层传输层(Transport Layer)
    • TCP、UDP、PPTP、TLS/SSL…
  • 第3层网络层(Network Layer)
    • IP、ICMP、IPSec…
  • 第2层数据链接层(Data Link Layer)
  • 第1层物理层(Physical Layer)

前面提到,TUN虚拟网络设备操纵了第三层,也就是网络层 L3,IP 协议正处于该层。

而根据 TCP/IP 协议栈与数据包的封装:

TCP/IP 通讯的过程如下:

https://akaedu.github.io/book/images/tcpip.transferlan.png

而 TCP/IP 数据包的封装也是和各层协议相对应的。

https://akaedu.github.io/book/images/tcpip.datagram.png

TCP/IP 的设计,是吸取了分层模型的精华思想——封装。每层对上一层提供服务时候,上一层的数据结构是黑盒,直接作为本层的数据,而不需要关心上一层协议的任何细节。

最后的 IP 数据报把 TCP 数据报和用户数据全都打包在一起了。

可参考 youtube 上的视频动画:

Animation of packet Transmission through Layers of TCP/IP
Packet Transmission through Layers of TCP/IP

可以简单的把 IP 数据报理解成一艘货轮,而用户数据和 TCP 数据则理解成货轮上的集装箱,一旦 TCP 数据报再封装成以太网帧,那么就要驶离港口,开始远行了。

而实现代理或者 VPN 软件,截获的也正是 IP 数据报,要赶在 IP 数据报发送之前进行处理。

而如果截获的是 TCP 数据报或者是 UDP 数据报,则会显得过早,而且 TCP 和 UDP 是两条支线最后汇总到 IP 数据报中,显然要在汇总的 IP 数据报处拦截最好。

2.3. 代理 == VPN ?

在前面有提到代理,又有提到 VPN,那么代理等于 VPN 嘛?

代理就如最前面的图所示,通过代理服务器去访问目标网络。

而 VPN,全称是Virtual Private Network,虚拟专用网络。意思就是在两个网络之间建立一条专有的通讯线路,就好比最前面的图中 虚线 所代表的线路一样。

所以,从概念上讲,代理 是不等于 VPN 的;从协议上讲,代理更不等于 VPN。

至于为什么经常会把 VPN 和代理混在一起,那是因为国内上网有限制,所以很多人是通过 VPN 代理服务器翻墙去浏览国外网站,也就是通过国内网络接入别人的企业内线虚拟网络从而达到预览。

具体可参考:http://www.weiruoyu.cn/?p=1607

3. 代理协议

实现代理通常有两种协议:

  • HTTP 协议
  • SOCKS 协议

这里只考虑 SOCKS 协议。

SOCKS 协议是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。

当防火墙后的客户端要访问外部的服务器时,就跟SOCKS代理服务器连接。这个代理服务器控制客户端访问外网的资格,允许的话,就将客户端的请求发往外部的服务器。

根据OSI模型,SOCKS是会话层的协议,位于表示层与传输层之间。

SOCKS 协议有 SOCKS4 和 SOCKS5 两种,SOCKS5比SOCKS4a多了鉴定、IPv6、UDP支持。

关于 SOCKS 协议更多细节可以参考维基百科的内容:

SOCKS - 维基百科,自由的百科全书

4. Shadowsocks 实现

4.1. Shadowsocks 简介

Shadowsocks 是一种基于 Socks5 代理方式的开源软件。

Shadowsocks 分为服务器端和客户端,在使用之前,需要先将服务器端部署到服务器上面,在服务器端部署完成后,用户需要按照指定的密码、加密方式和端口使用客户端软件与其连接。

在成功连接到服务器后,客户端会在用户的电脑上构建一个本地 Socks5代理。

浏览网络时,网络流量会被分到本地 Socks5 代理,客户端将其加密之后发送到服务器,服务器以同样的加密方式将流量回传给客户端,以此实现代理上网。

Shadowsocks 本地客户端的配置服务器所需要的数据:IP 地址、端口、加密方式、密码。

Shadowsocks 本地客户端配置本地 Socks5 代理。

Shadowsocks 本地客户端配置 HTTP 代理服务器。

4.2. Shadowsocks Scala 版本实现

Github 上有个 Shadowsocks 的开源项目,用 Scala 写的,项目地址:https://github.com/shadowsocks/shadowsocks-android

可以利用它实现 Shadowsocks 代理。

事实上,它不仅仅是使用了 Scala,更是使用到了许多 Linux 的开源库,正是因为 Android 底层基于 Linux 的,或许我们可以不再使用 Retrofit 、 OkHttp 来进行网络请求, 而是在 Native 层来完成。

涉及到的开源库如下:

  • redsocks
  • libevent
  • tun2socks
  • libancillary
  • libsodium

对绝大多数代理或者 VPN 软件的实现,都是从 VPNService 中创建TUN虚拟网络设备截获手机 IP 数据包开始的。

Shadowsocks 的整体架构可以参考如下图:

它主要是由图上六个进程组成的。其中,白色的为 Java 进程,而绿色的为 Native 进程。

Shadowsocks 进程启动流程及其进程间通信的关系如下:

shadowsocks => shadowsocks:vpn => ss-local => pdnsd => ss-tunnel => tun2socks

其中,ss-local就是在上面简介中提到的 代理服务器进程,它一边连接着网络代理服务器,一边连接着手机本地客户端。

shadowsocks进程就是 UI 进程,shadowsocks:vpn是 VPNService 所在的进程。

VPNService 进程在创建虚拟网卡 TUN设备后,就拥有了读写虚拟网卡 IP 数据包的能力。

此时 VPNService 通过 Socket 的方式跨进程通信,将 TUN网卡的文件描述符(FileDescriptor)发送到tun2sock进程,此时tun2sock进程也可以读取 IP 数据包了。

tun2sock对 IP 数据包处理完了之后,就会发送到 ss-local进程,由它去和远程服务器通信。

此时的通信流程如下:

本地进程 => tun设备(虚拟网卡)=> tun2socks => ss-local => 物理网卡 => …

然而,TUN虚拟网卡的数据报是位于网络层L3的,实现了 Socks5 协议的ss-local进程工作在传输层L4的,无法处理L3层的 IP 数据报。

显然,tun2sock的作用就是来完成 L3 层的 IP 数据报转换成 L4 层的数据流的。

tun2sock在用户态实现了一个轻量级的 TCP/IP 栈(lwip),程序打开一个TUN设备后,将读取的 TCP/UDP 数据报转发到 lwip,然后 lwip将其中的 tcp 解释出一个连接,将一个连接上的数据转发到 SOCKS,由本地 Socks 代理服务器转发到真正的 网络代理服务器。

ss-local进程得到数据流之后,就会通过真实的物理网卡发送数据。然而,发送的数据又会被拦截到tun虚拟网络设备上,这样反而成了一个死循环。

为了避免这样的死循环,在发送数据前,需要 VPNService 中调用 protect方法,将一个建立好的 Socket 保护起来,这样就能够再次发送数据了。

关于其整体架构实现原理,可参考这篇文章:http://ct2wj.com/2016/02/28/shadowsocks-android-source-code-analysis/

关于tun2socks部分,可参考这篇文章:
https://ftwo.me/post/badvpn-tun2socks%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/

4.3. Shadowsocks Java 版本实现

同时,在 Github 上还有一个 Java 版本的 Shadowsocks,项目地址:https://github.com/dawei101/shadowsocks-android-java

该项目不涉及 Linux 底层的代码,纯净 Java 版本,虽说牺牲掉了很多功能,但编译运行后配置相关参数仍可以实现 Shadowsocks 代理科学上网,亲测可用

它的实现没有 Scala 版本的那么精彩,大概架构相似,只是用线程代替了进程,用 Java 代码代替了 C 代码,用自己写的 TCP、IP、UDP 协议包的拆包和装包代替了开源库的 TCP/IP 协议栈。

所有的分析依旧要从 VPNService 截获网络层所有 IP 数据报开始。

在 VPNService 启动时,创建并运行了线程 TcpProxyServer,类似于上述讲到的ss-local本地 Sock5 代理服务器。

VPNService 在网络层截获所有流量,从数据包的的 IP 头和 TCP 头解析该数据包去往的 IP 地址、端口号,并将它们改成本地另一个 TCP 服务器的地址和端口,也就是TcpProxyServer

这里就实现了一个网络层的转发,由网络层转发到传输层。类似上述讲到的tun2socks转发到ss-local类似。

TcpProxyServer在传输层得到刚才转发过来的流量,根据代理规则向外网建立连接并建立隧道,在传输层通过 TCP Socket 进行转发。

TcpProxyServer获得外网回来的流量后,会转发给系统内部一个不存在的 IP 地址,TUN虚拟网卡截获到去往这个不存在的 IP 地址数据报后,用同样的方法将它们修改回原本的地址和端口号,并将它们写入到虚拟网卡中,交由系统的 TCP/IP 协议栈去处理。

5. VPN 协议

VPN 协议相对于 Socks5 代理协议就复杂了很多,它不在是一个单纯的经过加密的访问隧道了,它已经融合了访问控制、传输管理、加密、路由选择、可用性管理等多种功能。

VPN 的实现协议有很多:

  • L2TP
  • PPTP
  • IPSec
  • SSL VPN
  • OpenVPN

Android 系统本身也是支持 VPN 的。它支持了PPTP、L2TP/IPSec PSK、L2TP/IPSec RSA、IPSec Xauth PSK、IPSecXauth RSA、IPSec Hybrid RSA等6种类型。

关于 Android 上的 VPN 相关内容,可以参考这篇文章:

http://www.aichengxu.com/android/2554139.htm

6. Android VPN 开源应用

6.1. OpenVPN For Android

在 Github 上有开源项目,使用了 OpenVPN 协议,https://github.com/schwabe/ics-openvpn,它是 OpenVPN 在 Android 上的实现。

它涉及到了三个 Linux 开源库:

  • breakpad
  • openssl
  • ovpn3

OpenVPN 也是从 VPNService 截获网络层 IP 数据报开始,对数据进行加密,处理等操作。

关于其具体的实现,目前涉猎较少,待后续探索更多细节。

关于 OpenVPN 协议的原理,数据包的分析文章,可参考如下博文:

http://lx.wxqrcode.com/index.php/post/107.html
301 Moved Permanently

6.2. StrongSwan For Android

在 Github 上有开源项目,使用了 StrongSwan 协议,https://github.com/strongswan/strongswan/tree/master/src/frontends/android,它是 StrongSwan在 Android 上的实现。

7. 小结

无论是实现代理还是 VPN ,它们都具有共同点。

1、首先,都要借助 TUN 虚拟网络设备得到数据。
2、根据协议和实现需要,对数据报进行处理。
3、最后,通过真实的网卡将数据发送出去。
4、在远程服务器端也需要相应的配置。

对于代理的实现,可供选择项比较少,通过 Socks5 协议,相对于使用 VPN 的方式要便捷一些。

Shadowsocks 软件代码的开源对于实现 Android 下的代理有很大帮助,可参考性也特别大。

对于 VPN 的实现,可供选择项比较多,功能点也比较多,但相对于代理的实现也要复杂很多。

代理 相对于 VPN 在对数据报和协议的处理上,要相对容易实现一些。

8. 参考

1、《Android 安全架构深究》

2、http://ct2wj.com/2016/02/28/shadowsocks-android-source-code-analysis/

3、http://blog.csdn.net/roland_sun/article/details/46337171

4、https://cokebar.info/archives/236

5、http://www.freebuf.com/articles/terminal/121634.html

6、http://www.jianshu.com/p/4b9d43c0571a

7、http://www.cnblogs.com/feitian629/archive/2012/11/16/2774065.html

8、https://www.ibm.com/developerworks/cn/linux/l-tuntap/

9、http://www.aichengxu.com/android/2554139.htm

觉得文章还不错,可以关注一下微信公众号【纸上浅谈】

评论 在此处输入想要评论的文本。

Copied title and URL