保护VpnService中的套接字

时间:2013-11-18 09:26:29

标签: android sockets android-4.0-ice-cream-sandwich vpn packet-capture

我正在探索Android的VpnService的功能。目前,我通过在用户空间重建IP堆栈构建了一个非常基本的请求转发器:我从VpnService的输入流中读取IP数据包,解析它们,对于我不想转发的连接,我尝试重新创建那些套接字连接外部 VPN连接。

我已经明白最后一点是由VpnService.protect()促成的,并尝试按如下方式实施:

Socket socket = new Socket();
vpnService.protect(socket);
socket.connect(new InetSocketAddress(
        header.getDestinationAddress(),  // From my IP datagram header
        body.getDestinationPort()));     // From the TCP datagram header

不幸的是,这种方法导致环回VPN接口。

虽然上面的代码只是阻塞并最终超时,但我通过从一个单独的线程调用Socket.connect(InetSocketAddress)来观察环回;连接直接返回到我的VpnService的输入流,并重复该过程。

毋庸置疑,这会导致循环。我觉得这样做的原因是在创建套接字时(以及随后调用VpnService.protect(Socket)),我没有设置目标IP&港口。

这似乎确实如此,因为在我的VpnService实现中重写VpnService.protect(Socket)VpnService.protect(int)并在两种情况下调用supers都会返回false。

如何正确保护套接字连接?

5 个答案:

答案 0 :(得分:10)

以下代码有效。

Socket socket = SocketChannel.open().socket();
if ((null != socket) && (null != vpnService)) {
    vpnService.protect(socket);
}
socket.connect(...);

new Socket()没有有效的文件描述符,因此无法保护它。

答案 1 :(得分:4)

我发现另一种解决方案是用C / C ++写出来。

爪哇:

public native int createSocket();

public native int connectSocket(int fd);

C ++:

// For sockets
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// For error codes
#include <errno.h>

extern "C" {

JNIEXPORT jint JNICALL
Java_com_pixplicity_example_jni_VpnInterface_createSocket(
        JNIEnv * env, jobject thiz) {
    // Create the socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    int err = errno;
    // Return the file descriptor
    return sockfd;
}

JNIEXPORT jint JNICALL
Java_com_pixplicity_example_jni_VpnInterface_connectSocket(
        JNIEnv * env, jobject thiz, jint sockFd) {
    // Host & port are hard-coded here
    char* host = "74.125.136.113"; // google.com
    int port = 80;
    struct sockaddr_in peerAddr;
    int ret;
    peerAddr.sin_family = AF_INET;
    peerAddr.sin_port = htons(port);
    peerAddr.sin_addr.s_addr = inet_addr(host);
    // Connect to host
    ret = connect((int) sockFd, (struct sockaddr *) &peerAddr,
            sizeof(peerAddr));
    if (ret != 0) {
        perror("connect failed");
        close(sockFd);
    }
    // Return the error code
    return ret;
}

}

答案 2 :(得分:3)

您需要在保护之前绑定套接字。这对我有用:

Socket socket = new Socket();
//bind to any address
socket.bind(new InetSocketAddress(0));

vpnService.protect(socket);

socket.connect(...);

答案 3 :(得分:1)

您可以通过将应用程序添加到vpnService.builder.addDisallowedApplication("your package name")

来排除使用VPN的应用程序套接字(以及流经它的流量)

我尝试了这个并在vpn隧道接口和我的传出Internet接口上运行tcpdump进行了测试。来自我的应用程序的数据包不会在vpn界面中循环,而是通过手机的前向互联网接口发送。

答案 4 :(得分:1)

我需要保护OkHttpClient中的套接字。它会创建尚未保护的未连接套接字(这意味着service.protect()返回false),并且当它们连接时显然为时已晚(例如,当我尝试在networkInterceptor内部保护它们时)。经典的晦涩的Android行为。

无论如何,Mai Quoc Huy的答案对我来说是正确的,整个代码看起来像这样。

    val protectedHttpClient = OkHttpClient.Builder()
            .socketFactory(object : SocketFactory() {

                override fun createSocket(): Socket = Socket().apply {
                    bind(InetSocketAddress(0))
                    val result = service?.protect(this)
                }

                override fun createSocket(host: String?, port: Int) = unsupported()
                override fun createSocket(host: String?, port: Int, localHost: InetAddress?, localPort: Int) = unsupported()
                override fun createSocket(host: InetAddress?, port: Int) = unsupported()
                override fun createSocket(address: InetAddress?, port: Int, localAddress: InetAddress?, localPort: Int) = unsupported()

                private fun unsupported(): Nothing = throw UnsupportedOperationException("This factory can only create unconnected sockets for OkHttp")

            })
            .build()