Защитите сокет в VpnService

Я изучаю возможности Android VpnService. В настоящее время я построил очень простой перенаправитель запросов, по сути, перестраивая стек IP в пространстве пользователя: я читаю IP-пакеты из входного потока VpnService, анализирую их, а для соединений, которые не хочу пересылать, я пытаюсь воссоздать их сокетные соединения вне 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.protect(Socket) а также VpnService.protect(int) в моей реализации VpnService и при вызове supers в обоих случаях возвращается false.

Как правильно защитить сокетное соединение?

4 ответа

Решение

Следующий код работает.

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

new Socket () не имеет допустимого дескриптора файла, поэтому он не может быть защищен.

Вы должны связать свою розетку перед защитой. Это работает для меня:

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

vpnService.protect(socket);

socket.connect(...);

Я обнаружил, что альтернативным решением было написать его на 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;
}

}

Мне нужно было защитить розетки в OkHttpClient. Он создает неподключенные сокеты, которые еще нельзя защитить (это означает, что service.protect() возвращается false), а когда они подключены, очевидно, слишком поздно (например, когда я пытался защитить их внутри networkInterceptor). Классическое непонятное поведение Android.

Во всяком случае, ответ Май Куок Хай был правильным для меня, и весь код выглядит так.

    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()

Вы можете исключить использование сокетов вашего приложения (и, следовательно, трафика, проходящего через него) из VPN, добавив ваше приложение в vpnService.builder.addDisallowedApplication("your package name")

Я попробовал это и проверил это с запуском tcpdump как на туннельном интерфейсе VPN, так и на исходящем интернет-интерфейсе. Пакеты из моих приложений не зацикливаются в интерфейсе vpn и отправляются через передний интернет-интерфейс телефона.

Другие вопросы по тегам