Android VpnService 转发数据包

5
我想使用Android的VpnService来捕获数据包,并基于IP地址进行过滤。我可以从“tun”接口正常获取数据包,但之后我不确定如何将它们转发到它们的原始目的地。根据这个答案中的评论,似乎我只需要:
  1. 创建一个新的套接字以连接到目标IP地址和端口
  2. 削减IP和TCP头以仅发送数据
  3. 在收到响应时重新附加IP和TCP头
  4. 将完整数据包发送到输出流

我尝试像这样发送数据:

Socket socket = new Socket();
socket.bind(new InetSocketAddress(0));
if (protect(socket)){
    Log.e(TAG, "Socket protected");
}else{
    Log.e(TAG, "Socket NOT protected");
}
socket.connect(new InetSocketAddress(ipPacket.getDestinationIp(), ipPacket.getDstPort()));
Log.e(TAG, "Socket connected: " + socket.isConnected());

socket.getOutputStream().write(getTCPHeader(getIpHeader(packet)[1])[1].array());

方法getTCPHeader(ByteArray packet)getIpHeader(ByteArray packet)将数据包按照以下方式分成两个ByteArray

private ByteBuffer[] getIpHeader(ByteBuffer packet){
    packet.position(0);
    ByteBuffer ipHeader = ByteBuffer.allocate(20);
    ByteBuffer data = ByteBuffer.allocate(packet.limit() - 20);
    
    packet.get(ipHeader.array(), 0, 20);
    
    packet.get(data.array(), 0, packet.limit() - 20);
    
    return new ByteBuffer[]{ipHeader, data};
}

private ByteBuffer[] getTCPHeader(ByteBuffer packet){
    packet.position(20);
    ByteBuffer tcpHeader = ByteBuffer.allocate(20);
    ByteBuffer data = ByteBuffer.allocate(packet.limit() - 20);

    packet.get(tcpHeader.array(), 0, 20);

    packet.get(data.array(), 0, packet.limit() - 40);

    return new ByteBuffer[]{tcpHeader, data};
}

现在我正在使用以下代码从服务器获取响应:
ByteBuffer responsePacket = ByteBuffer.allocate(65535);
InputStream socketInputStream = socket.getInputStream();
try{
    int responseLength = socketInputStream.read(responsePacket.array());
    if (responseLength > 20){
        Log.e(TAG, "===Server Response===");
        Log.e(TAG, "Length: " + responseLength);

        ByteBuffer trimmedResponseData = ByteBuffer.allocate(responseLength);
        System.arraycopy(responseData.array(), 0, trimmedResponseData.array(), 0, responseLength);

        String resp = "";
        for (int i = 0; i < responseLength; i++){
            resp += String.valueOf(responseData.get(i) + " ");
        }

        Log.e(TAG, "Response data: " + resp);

        ByteBuffer finalPacket = ByteBuffer.allocate(40 + responseLength);
        ByteBuffer swappedIpHeader = swapSrcDstAddress(getIpHeader(packet)[0]);
        ByteBuffer swappedTcpHeader = swapTCPSrcDst(getTCPHeader(getIpHeader(packet)[1])[0]);

        finalPacket.put(swappedIpHeader.array());
        finalPacket.put(swappedTcpHeader.array());
        finalPacket.put(serverResponseData.array());

        Packet finPack = debugPacket(finalPacket);
        Log.e("VPN", "Final packet --> Packet size: " + finPack.getTotalLength() + " from " + finPack.getSourceIp() + " src port: " + finPack.getSrcPort() + " going to " + finPack.getDestinationIp() + " dst port: " + finPack.getDstPort());

        out.write(finalPacket.array());
    }
}catch (Exception e){
    //Log.e(TAG, "EXCEPTION: " + e);
    e.printStackTrace();
}

这段代码似乎要么极其缓慢,要么根本不起作用。有时候如果我去www.google.com,它会加载得很慢,但大多数情况下它根本不会加载。有时我会在int responseLength = socketInputStream.read(serverResponse.array());这一行上遇到以下错误:

java.net.SocketException: recvfrom failed: ECONNRESET (Connection reset by peer)

是什么导致了这个错误,如何将这些数据包正确地转发到相应的目的地?非常感谢您的帮助!

1个回答

0
这个错误是什么原因引起的? recvfrom failed 异常意味着服务器已经关闭了客户端套接字,但是客户端仍在读取输入数据(在您的情况下是 serverResponse.array())。详情请参见 此处
如何将这些数据包正确地转发到相应的目标?
谷歌源代码中有一个示例代码 此处 可以转发可用的数据包。请查看代码和相关注释。根据谷歌源代码:
该应用程序由Android客户端和服务器示例实现组成。它执行UDP上的IP,并且能够在接收相同VPN参数的情况下在不同网络之间进行无缝切换。它展示了如何使用API 14中引入的VpnService类构建VPN客户端。
服务器端实现的示例代码是特定于Linux的,并且可在“server”目录中找到。要运行服务器或将其移植到另一个平台,请参阅代码中的注释以获取详细信息。
还有一个有用的应用链接here

我已经查看了谷歌的toyVpn示例,但那个示例使用单独的VPN服务器。我正在尝试在手机上实现所有这些功能,而不需要连接到另一个服务器。 - TychoTheTaco
你需要通过Linux特定的服务器端实现。按照指示进入服务器目录。你的手机本身也是基于Linux系统的。 - rupinderjeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接