2

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

问题

我的 VPN 服务取自ToyVpn Google 示例,并结合了123中的示例中的示例在本地使用(不连接到远程服务器)不起作用。


我的应用原则

我看到了这个这个SO 问题,但那里的答案不是很有见地,我找不到我的问题的解决方案。

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

我的VPN应用的原理

我所有的代码都是用 Kotlin 语言编写的,但并不复杂,对于 JAVA 开发人员来说非常清楚。所以我希望上面的代码很清楚,因为它取自这里(谷歌提供的 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();
}

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

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

从而:

  1. VPN配置的设置与非常相似示例以及上述 SO 问题中的两个示例
  2. 我的数据包转发取自ToyVpn示例。

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

所以我不知道我实际上做错了什么!如果我使用来自 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)
        }
    }
}

日志猫输出

在我的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) 
4

1 回答 1

5

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:提供给 startService(Intent) 的 Intent,如给定的。如果服务在其进程消失后重新启动,并且它之前返回了除 START_STICKY_COMPATIBILITY 之外的任何内容,则这可能为 null 。

因此,您至少应该通过将Kotlin 中null的签名更改为:onStartCommand

override fun onStartCommand(intent:Intent?, flags:Int, startId:Int) {
于 2016-06-13T06:48:43.100 回答