Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protect a socket in VpnService

I'm exploring the capabilities of Android's VpnService. Presently, I've built a very rudimentary request forwarder by essentially rebuilding the IP stack in user space: I read IP packets from the VpnService's input stream, parse them, and for connections I don't want to forward, I attempt to recreate those socket connections outside the VPN connection.

I've understood that this last bit is facilitated by VpnService.protect() and have tried implementing it as follows:

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

Unfortunately, this approach is causing a loopback into the VPN interface.

Whereas the above code will simply block and eventually time out, I observe the loopback by calling Socket.connect(InetSocketAddress) from a separate thread; the connection comes straight back into my VpnService's input stream and the process repeats.

Needless to say, this causes a loop. I get the feeling that the reason for this is that at the time of socket creation (and subsequently, the call to VpnService.protect(Socket)), I haven't set the destination IP & port yet.

This seems to indeed be the case, as the by overriding VpnService.protect(Socket) and VpnService.protect(int) in my VpnService implementation and calling the supers in both cases returns false.

How can I properly protect a socket connection?

like image 362
Paul Lammertsma Avatar asked Nov 18 '13 09:11

Paul Lammertsma


4 Answers

I needed to protect sockets in OkHttpClient. It creates unconnected sockets which cannot be protected yet (meaning that service.protect() returns false) and when they are connected it apparently is too late (e.g. when I tried to protect them inside networkInterceptor). Classic obscure Android behaviors.

Anyway, Mai Quoc Huy's answer was right for me and the whole code looks like this.

    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()
like image 64
Michał Klimczak Avatar answered Sep 28 '22 05:09

Michał Klimczak


You need to bind your socket before protecting. This works for me:

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

vpnService.protect(socket);

socket.connect(...);
like image 45
Mai Quoc Huy Avatar answered Oct 21 '22 01:10

Mai Quoc Huy


The following code works.

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

new Socket() doesn't have a valid file descriptor, so it cannot be protected.

like image 12
wshztlg Avatar answered Oct 21 '22 00:10

wshztlg


I found that an alternative solution was to write it out in C/C++.

Java:

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

}
like image 4
Paul Lammertsma Avatar answered Oct 21 '22 01:10

Paul Lammertsma