Защитите сокет в 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.

В то время как вышеприведенный код просто блокирует и, в конечном итоге, время ожидания, я наблюдаю loopback, вызывая Socket.connect(InetSocketAddress) из отдельного потока; соединение возвращается прямо в поток ввода VpnService, и процесс повторяется.

Излишне говорить, что это вызывает цикл. У меня возникает ощущение, что причина этого в том, что во время создания сокета (а затем и на вызов VpnService.protect(Socket)) я еще не установил IP-адрес и порт назначения.

Это действительно так, потому что, переопределяя VpnService.protect(Socket) и VpnService.protect(int) в моей реализации VpnService и вызывая supers в обоих случаях, возвращает false.

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

Ответ 1

Работает следующий код.

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

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

Ответ 2

Я обнаружил, что альтернативным решением было написать его в C/С++.

Java:

public native int createSocket();

public native int connectSocket(int fd);

С++:

// 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;
}

}

Ответ 3

Перед защитой необходимо связать сокет. Это работает для меня:

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

vpnService.protect(socket);

socket.connect(...);

Ответ 4

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

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