java - Android本地VPN服务: can't get response

标签 java android vpn kotlin kotlin-android-extensions

我对 Android 及其服务还很陌生。我正在尝试在我的应用程序中实现本地 VPN 服务(使用 Kotlin 和 Java)。

问题

我的 VPN 服务取自 ToyVpn Google 示例,结合 1 中的示例, 2 , 3在本地使用它(无需连接到远程服务器)是行不通的。


我的应用程序原理

我看到 thisthis所以问题,但答案不是很有见地,我找不到我的问题的解决方案。

所以应用程序非常简单:当用户单击主 Activity 上的"is"按钮时,它应该转发所有数据包,当用户单击“否”时,它应该阻止它。目的:将其用作防火墙,如下所示:

The principle of my VPN app

我所有的代码都是用Kotlin语言编写的,但是并不复杂,对于JAVA开发者来说也很清晰。所以我希望上面的代码非常清楚,因为它取自 here (Google 提供的 ToyVpn 示例)并且刚刚转换为 kotlin。


我的配置和代码

为了在我的应用程序中启用 VPN 服务,我将 AndroidManifest.xml 放入 <application>标记此设置:

<service android:name="com.example.username.wifictrl.model.VpnFilter"
         android:permission="android.permission.BIND_VPN_SERVICE" >
    <intent-filter>
        <action android:name="android.net.VpnService" />
    </intent-filter>
</service>

我的MainActivity代码包含:

override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        ... // omitted for the sake of brevity

        val intent = VpnService.prepare(this);
        if (intent != null) {
            startActivityForResult(intent, 0);
        } else {
            onActivityResult(0, RESULT_OK, null);
        }

        ... // omitted for the sake of brevity
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == RESULT_OK) {
            val intent = Intent(this, VpnFilter::class.java);
            startService(intent);
        }
    }

我的VpnFilter类ToyVpn非常相似服务类,但必须在本地工作,无需任何身份验证、握手等,因此我使用此类设置编辑了示例:

 private void configure() throws Exception {
    // If the old interface has exactly the same parameters, use it!
    if (mInterface != null) {
        Log.i(TAG, "Using the previous interface");
        return;
    }

    // Configure a builder while parsing the parameters.
    Builder builder = new Builder();
    builder.setSession(TAG)
    builder.addAddress("10.0.0.2", 32).addRoute("0.0.0.0", 0)
    try {
        mInterface.close();
    } catch (Exception e) {}

    mInterface = builder.establish();
}

在我的 run 函数中,我刚刚配置了隧道以连接到本地 IP 地址:

tunnel.connect(InetSocketAddress("127.0.0.1", 8087))

因此:

  1. VPN 配置的设置this 非常相似示例以及上述问题中的两个示例,供本地使用。
  2. 我的数据包转发取自 ToyVpn例子。

我知道我的 VPN 正在运行,因为如果我更改 addRoute 配置,我将无法访问互联网。

所以我不知道我到底做错了什么!如果我使用来自 ToyVpn 的数据包转发代码,每次新数据包到来时,应用程序都会崩溃

更新

上述问题已解决,但我看到数据包正在发送,但我无法得到任何响应。我不明白为什么。


我的 VPN 服务的完整 JAVA 代码

public class VpnFilter extends VpnService implements Handler.Callback, Runnable {
    private static final String TAG = "MyVpnService";

    private Handler mHandler;
    private Thread mThread;

    private ParcelFileDescriptor mInterface;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The handler is only used to show messages.
        if (mHandler == null) {
            mHandler = new Handler(this);
        }

        // Stop the previous session by interrupting the thread.
        if (mThread != null) {
            mThread.interrupt();
        }

        // Start a new session by creating a new thread.
        mThread = new Thread(this, "ToyVpnThread");
        mThread.start();
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        if (mThread != null) {
            mThread.interrupt();
        }
    }

    @Override
    public boolean handleMessage(Message message) {
        if (message != null) {
            Toast.makeText(this, message.what, Toast.LENGTH_SHORT).show();
        }
        return true;
    }

    @Override
    public synchronized void run() {
        Log.i(TAG,"running vpnService");
        try {
            runVpnConnection();
        } catch (Exception e) {
            e.printStackTrace();
            //Log.e(TAG, "Got " + e.toString());
        } finally {
            try {
                mInterface.close();
            } catch (Exception e) {
                // ignore
            }
            mInterface = null;

            mHandler.sendEmptyMessage(R.string.disconnected);
            Log.i(TAG, "Exiting");
        }
    }

    private void configure() throws Exception {
        // If the old interface has exactly the same parameters, use it!
        if (mInterface != null) {
            Log.i(TAG, "Using the previous interface");
            return;
        }

        // Configure a builder while parsing the parameters.
        Builder builder = new Builder();
        builder.setSession(TAG)
        builder.addAddress("10.0.0.2", 32).addRoute("0.0.0.0", 0)
        try {
            mInterface.close();
        } catch (Exception e) {
            // ignore
        }

        mInterface = builder.establish();
    }

    private boolean runVpnConnection() throws Exception {

        configure()

        val in = new FileInputStream(mInterface.fileDescriptor)

        // Packets received need to be written to this output stream.
        val out = new FileOutputStream(mInterface.fileDescriptor)

        // The UDP channel can be used to pass/get ip package to/from server
        val tunnel = DatagramChannel.open()

        // For simplicity, we use the same thread for both reading and
        // writing. Here we put the tunnel into non-blocking mode.
        tunnel.configureBlocking(false)

        // Allocate the buffer for a single packet.
        val packet = ByteBuffer.allocate(32767)

        // Connect to the server, localhost is used for demonstration only.
        tunnel.connect(InetSocketAddress("127.0.0.1", 8087))

        // Protect this socket, so package send by it will not be feedback to the vpn service.
        protect(tunnel.socket())

        // We use a timer to determine the status of the tunnel. It
        // works on both sides. A positive value means sending, and
        // any other means receiving. We start with receiving.
        int timer = 0

        // We keep forwarding packets till something goes wrong.
        while (true) {
            // Assume that we did not make any progress in this iteration.
            boolean idle = true

            // Read the outgoing packet from the input stream.
            int length = `in`.read(packet.array())

            if (length > 0) {

                Log.i(TAG, "************new packet")

                // Write the outgoing packet to the tunnel.
                packet.limit(length)
                tunnel.write(packet);
                packet.clear()
                // There might be more outgoing packets.
                idle = false
                // If we were receiving, switch to sending.
                if (timer < 1) {
                    timer = 1
                }

            }

            length = tunnel.read(packet)

            if (length > 0) {
                // Ignore control messages, which start with zero.
                if (packet.get(0).toInt() !== 0) {
                    // Write the incoming packet to the output stream.
                    out.write(packet.array(), 0, length)
                }
                packet.clear()
                // There might be more incoming packets.
                idle = false
                // If we were sending, switch to receiving.
                if (timer > 0) {
                    timer = 0
                }
            }
            // If we are idle or waiting for the network, sleep for a
            // fraction of time to avoid busy looping.
            if (idle) {
                Thread.sleep(100)
                // Increase the timer. This is inaccurate but good enough,
                // since everything is operated in non-blocking mode.
                timer += if (timer > 0) 100 else -100
                // We are receiving for a long time but not sending.
                if (timer < -15000) {
                    // Send empty control messages.
                    packet.put(0.toByte()).limit(1)
                    for (i in 0..2) {
                        packet.position(0)
                        tunnel.write(packet)
                    }
                    packet.clear()
                    // Switch to sending.
                    timer = 1
                }
                // We are sending for a long time but not receiving.
                if (timer > 20000) {
                    throw IllegalStateException("Timed out")
                }
            }
            Thread.sleep(50)
        }
    }
}

记录 CAT 输出

在我的 LogCat 面板中,当应用程序崩溃时,我得到了以下跟踪:

   FATAL EXCEPTION: main
    java.lang.RuntimeException: Unable to start service com.example.username.wifictrl.model.VpnFilter@41ebbfb8 with null: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter intent
          at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2950)
          at android.app.ActivityThread.access$1900(ActivityThread.java:151)
          at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1442)
          at android.os.Handler.dispatchMessage(Handler.java:99)
          at android.os.Looper.loop(Looper.java:155)
          at android.app.ActivityThread.main(ActivityThread.java:5520)
          at java.lang.reflect.Method.invokeNative(Native Method)
          at java.lang.reflect.Method.invoke(Method.java:511)                                                                                   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029)
          at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)
          at dalvik.system.NativeStart.main(Native Method)
    Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter intent
              at com.example.skogs.wifictrl.model.VpnFilter.onStartCommand(VpnFilter.kt)
              at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2916)
              at android.app.ActivityThread.access$1900(ActivityThread.java:151) 
              at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1442) 
              at android.os.Handler.dispatchMessage(Handler.java:99) 
              at android.os.Looper.loop(Looper.java:155) 
              at android.app.ActivityThread.main(ActivityThread.java:5520) 
              at java.lang.reflect.Method.invokeNative(Native Method) 
              at java.lang.reflect.Method.invoke(Method.java:511) 
              at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796) 
              at dalvik.system.NativeStart.main(Native Method) 

最佳答案

logcat 中记录的错误:

Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter intent
              at com.example.skogs.wifictrl.model.VpnFilter.onStartCommand(VpnFilter.kt)

表示问题所在。

onStartCommand 的文档指出(强调我的):

Intent: The Intent supplied to startService(Intent), as given. This may be null if the service is being restarted after its process has gone away, and it had previously returned anything except START_STICKY_COMPATIBILITY.

因此,您至少应该相应地处理 null 情况,方法是将 Kotlin 中的 onStartCommand 签名更改为:

override fun onStartCommand(intent:Intent?, flags:Int, startId:Int) {

关于java - Android本地VPN服务: can't get response,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37779720/

相关文章:

java - 在 Java 桌面应用程序中使用 Google map 的替代方案

java - 如何(安全地)删除 Eclipse 中不必要的 Maven 依赖项?

java - 在泛型 Java 类中对泛型变量使用数学运算符

java - 实现 "Donate"功能

java - 如何将位图附加到电子邮件android

docker - 通过 VPN 容器路由 Docker 容器流量

c# - 如何使用预先保存的 VPN 凭据启动 VPN?

android - Play 商店登录失败

java - 如何动态更改状态栏中的文本

c# - 无法通过 VPN 以编程方式访问网络路径