简体   繁体   中英

Using a file descriptor from Android VpnService.Builder in native code

I am making a VPN application in Android and the following seems to work great:

public class MyVpnService extends VpnService  {

    private ParcelFileDescriptor lt;

    public int onStartCommand(Intent intent,
                               int flags,
                               int startId) {
        VpnService.Builder builder = new VpnService.Builder();

        builder=builder.addAddress("192.168.2.2", 0)
                .addDnsServer("192.168.2.1")
                .setBlocking(true);

        lt = builder.establish();
        if(lt==null) {
            Log.i(logTag,"We are not prepared");
            return START_STICKY;
        }

        new Thread() {
            @Override
            public void run() {
                FileInputStream in = new FileInputStream(lt.getFileDescriptor());
                byte[] buffer = new byte[2000];
                for (;;) {
                    int len;
                    try {
                        len = in.read(buffer);
                    } catch (IOException e) {
                        Log.i(logTag, "Got exception " + e);
                        break;
                    }
                    if (len <= 0) {
                        Log.i(logTag, "No more packets; exits");
                        break;
                    }
                    Log.i(logTag, "Got a packet with length " + len);
                }

                try {
                    lt.close();
                } catch(IOException e) {
                    Log.i(logTag,"Exception when closing fd - likely it was closed already "+e);
                }
            }
        }.start();

        return START_STICKY;
    }

    // ... other methods omitted...

}

Now, I want to do the VPN handling in native code instead. So I have tried to replace the thread with something like this:

new Thread() {
    @Override
    public void run() {
        int fd = lt.getFd();
        Jni.doVPN(fd);
        try {
            lt.close();
        } catch(IOException e) {
            Log.i(logTag,"Exception when closing fd - likely it was closed already "+e);
        }
    }
}.start();

Where the JNI code looks something like this:

#include "unistd.h"
#include <android/log.h>

JNIEXPORT void JNICALL Java_com_example_Jni_doVPN(JNIEnv *env, jclass cls, jint fd) {
    char buf[2000];
    for(;;) {
        int n=read(fd,&buf,sizeof(buf));
        __android_log_print(ANDROID_LOG_VERBOSE, "foo", "Got packet with length %i",n);        
        if(n<=0) {
            break;
        }
    }
}

This also seems to work.

HOWEVER: If I close the file descriptor from Java with something like:

  lt.close()

Then in the pure Java code, the read call immediately throws an InterruptedIOException which seems reasonable.

But in the native code, it often seems to take a long time before the read call reports an error - it just keeps blocking. Further, if I close the VPN by clicking in the Android UI and ask Android to forget the VPN (which triggers a call to VpnService.OnRevoke), then the native read call seems to block forever. I suspect that the read call blocks until there is something different from an error to return, and then it returns an error afterwards. This would explain both observations.

Any ideas of how I can fix this or what is going on? I really would prefer not read from the file descriptor from Java code.

It turns out you are not supposed to close a file descriptor while reading of it. Ways to work around this are described at C: blocking read should return, if filedescriptor is deleted .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM