Protect a socket in VpnService
Asked Answered
I

5

12

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?

Incardination answered 18/11, 2013 at 9:26 Comment(0)
B
12

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.

Backwoods answered 19/11, 2013 at 4:31 Comment(2)
Also the protect method should be called after establishing the vpn interface.Disobey
This method creates a socket that crashes the app when written to on Android 8.0 or higher. Mai Quoc Hui's answer below works reliably, though. Use that one instead!Limeade
M
5

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(...);
Meeting answered 4/3, 2015 at 6:28 Comment(1)
this solved my issue, thanks! needed to protect before connecting and docs wasn't so clear!Teat
I
4

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

}
Incardination answered 19/11, 2013 at 9:46 Comment(0)
B
2

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()
Bounded answered 1/10, 2020 at 13:41 Comment(0)
R
1

You could exclude your application socket(s) (and thus the traffic flowing past it) from using the VPN by adding your application to vpnService.builder.addDisallowedApplication("your package name")

I tried this and tested it with running tcpdump on both the vpn tunneled interface and my outgoing Internet interface. The packets from my applications don't loop around in the vpn interface and are sent over the forward facing Internet interface of the phone.

Report answered 18/9, 2016 at 4:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.