当前位置: 首页>后端>正文

Android的VpnService使用二次NAT转发TCP流量的原理

最近在工作中需要实现利用VPN对网络流量的抓取,UDP协议还好说,实现起来比较简单,只要通过数据报套接字直接发送和接收数据包即可。但是TCP协议的话处理起来就变得很麻烦,不光是需要完全吃透TCP协议的详细内容,还要自己实现TCP三次握手和四次分手的处理,真正搞起来会非常花时间。github上虽然有现成的开源库,但是都是GPL证书,并且代码量极大,难以导入。前一阵子突然发现一下这个非GPL的开源库,仔细研究了一下,基本原理基于二次NAT,实现方式相对简单,不得不佩服作者的思路。

https://github.com/huolizhuminh/NetWorkPacketCapture

以下为相关学习笔记

概要

大概的思路是先创建一个虚拟的TCP代理服务器,本地发出的流量先发给代理服务器,在通过代理服务器发到外部。然后代理服务器接收到外部的回复在写会本地,最终避免了TCP协议相关的一些繁琐处理。代价就是数据的拷贝多了一次。

下面分析一些关键代码

    public TcpProxyServer(int port) throws IOException {
        mSelector = Selector.open();

        mServerSocketChannel = ServerSocketChannel.open();
        mServerSocketChannel.configureBlocking(false);
        mServerSocketChannel.socket().bind(new InetSocketAddress(port));
        mServerSocketChannel.register(mSelector, SelectionKey.OP_ACCEPT);
        this.port = (short) mServerSocketChannel.socket().getLocalPort();

        DebugLog.i("AsyncTcpServer listen on %s:%d success.\n", mServerSocketChannel.socket().getInetAddress()
                .toString(), this.port & 0xFFFF);
    }

创建一个服务端套接字并绑定一个本地端口,转发的流量先临时将目的端口号改为本地端口号后发送到这个代理服务器。

        if (tcpHeader.getSourcePort() == mTcpProxyServer.port) {
            ...
            if (session != null) {
                ipHeader.setSourceIP(ipHeader.getDestinationIP());
                tcpHeader.setSourcePort(session.remotePort);
                ipHeader.setDestinationIP(LOCAL_IP);
                CommonMethods.ComputeTCPChecksum(ipHeader, tcpHeader);
                mVPNOutputStream.write(ipHeader.mData, ipHeader.mOffset, size);
                mReceivedBytes += size;
            } 
            ...
        } else {
            ...
            //转发给本地TCP服务器
            ipHeader.setSourceIP(ipHeader.getDestinationIP());
            ipHeader.setDestinationIP(LOCAL_IP);
            tcpHeader.setDestinationPort(mTcpProxyServer.port);
            CommonMethods.ComputeTCPChecksum(ipHeader, tcpHeader);
            mVPNOutputStream.write(ipHeader.mData, ipHeader.mOffset, size);
            ...
        }

根据作者描述两个if分支分别实现以下功能:

解析获得源端口,如果源端口是由Local Tunnel发出来的,则修改了目标IP为Session所保存的源IP,源IP为Session所保存的目标IP,源端口为Session所保存的目标端口,合成新包。

解析IP包,获得其源端口,通过源端口判断此IP包是由Local Socket还是Local Tunnel,如果是由Local Socket发出的则修改了目标IP和目标端口为本地建立的ServerSocket的IP和端口,并将源IP修改成需要目标的IP,合成新包,并建立Session,保存此链路的源端口、目标IP,目标端口。

只是目前来看有以下两个问题难以解决:
1.由于应用层没有权限使用原始套接字,TCP和UDP以外的包,比如说ICMP没办法处理。这个是所有本地做流量抓去功能都无法实现的。
2.目前只支持ipv4而不支持ipv6,不过已经5年没更新了,估计就这么地了。


https://www.xamrdz.com/backend/3m41925164.html

相关文章: