0

我正在 Android 中制作一个 VPN 应用程序,以下似乎效果很好:

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...

}

现在,我想用本机代码进行 VPN 处理。所以我试图用这样的东西替换线程:

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

JNI 代码如下所示:

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

这似乎也有效。

但是:如果我从 Java 中关闭文件描述符,例如:

  lt.close()

然后在纯 Java 代码中,读取调用立即抛出一个看起来合理的 InterruptedIOException。

但在本机代码中,read 调用通常似乎需要很长时间才能报告错误——它只是一直阻塞。此外,如果我通过单击 Android UI 关闭 VPN 并要求 Android 忘记 VPN(这会触发对 VpnService.OnRevoke 的调用),那么本机读取调用似乎会永远阻塞。我怀疑 read 调用会阻塞,直到返回与错误不同的东西,然后再返回错误。这将解释这两个观察结果。

关于如何解决这个问题或发生了什么的任何想法?我真的不希望从 Java 代码中读取文件描述符。

4

1 回答 1

0

事实证明,您不应该在读取文件描述符时关闭它。C:blocking read should return, if filedescriptor is deleted中描述了解决此问题的方法。

于 2021-11-23T05:36:39.853 回答