hey*_*unt 2 java vpn android kotlin kotlin-android-extensions
我对 Android 及其服务还很陌生。我正在尝试在我的应用程序中实现本地VPN 服务(使用 Kotlin 和 Java)。
\n\n我的 VPN 服务取自ToyVpn Google 示例,并结合 1、2、3 中的示例在本地使用(无需连接到远程服务器),但无法正常工作。
\n\n我看到了这个和这个问题,但是那里的答案不是很有见地,我找不到我的问题的解决方案。
\n\n所以该应用程序非常简单:当用户单击主活动上的“是”按钮时,它应该转发所有数据包,当单击“否”时,它应该阻止它。目的:将其用作防火墙,如下所示:
\n\n\n\n我所有的代码都是用 Kotlin 语言编写的,但它并不复杂,对于 JAVA 开发人员来说非常清晰。所以我希望上面的代码非常清晰,因为它取自此处(Google 提供的 ToyVpn 示例)并刚刚转换为 kotlin。
\n\n为了在我的应用程序中启用 VPN 服务,我将AndroidManifest.xml放入<application>标记此设置:
<service android:name="com.example.username.wifictrl.model.VpnFilter"\n android:permission="android.permission.BIND_VPN_SERVICE" >\n <intent-filter>\n <action android:name="android.net.VpnService" />\n </intent-filter>\n</service>\nRun Code Online (Sandbox Code Playgroud)\n\n我的MainActivity代码包含:
\n\noverride fun onCreate(savedInstanceState: Bundle?) {\n\n super.onCreate(savedInstanceState)\n\n ... // omitted for the sake of brevity\n\n val intent = VpnService.prepare(this);\n if (intent != null) {\n startActivityForResult(intent, 0);\n } else {\n onActivityResult(0, RESULT_OK, null);\n }\n\n ... // omitted for the sake of brevity\n }\n\n override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {\n super.onActivityResult(requestCode, resultCode, data)\n if (resultCode == RESULT_OK) {\n val intent = Intent(this, VpnFilter::class.java);\n startService(intent);\n }\n }\nRun Code Online (Sandbox Code Playgroud)\n\n我的VpnFilter 类与ToyVpn服务类非常相似,但必须在本地工作,无需任何身份验证、握手等,因此我使用以下设置编辑了示例:
\n\n private void configure() throws Exception {\n // If the old interface has exactly the same parameters, use it!\n if (mInterface != null) {\n Log.i(TAG, "Using the previous interface");\n return;\n }\n\n // Configure a builder while parsing the parameters.\n Builder builder = new Builder();\n builder.setSession(TAG)\n builder.addAddress("10.0.0.2", 32).addRoute("0.0.0.0", 0)\n try {\n mInterface.close();\n } catch (Exception e) {}\n\n mInterface = builder.establish();\n}\nRun Code Online (Sandbox Code Playgroud)\n\n在我的运行函数中,我刚刚配置了隧道以连接到本地 IP 地址:
\n\ntunnel.connect(InetSocketAddress("127.0.0.1", 8087))\nRun Code Online (Sandbox Code Playgroud)\n\n从而:
\n\n\n\n我知道我的 VPN 正在运行,因为如果我更改addRoute配置,我将无法访问 Internet。
\n\n所以我不知道我实际上做错了什么!如果我使用来自 ToyVpn 的数据包转发代码,则每次新数据包到来时应用程序都会崩溃。
\n\n更新
\n\n上面的问题已解决,但我看到数据包正在发送,但我无法得到任何响应。我不明白为什么。
\n\npublic class VpnFilter extends VpnService implements Handler.Callback, Runnable {\n private static final String TAG = "MyVpnService";\n\n private Handler mHandler;\n private Thread mThread;\n\n private ParcelFileDescriptor mInterface;\n\n @Override\n public int onStartCommand(Intent intent, int flags, int startId) {\n // The handler is only used to show messages.\n if (mHandler == null) {\n mHandler = new Handler(this);\n }\n\n // Stop the previous session by interrupting the thread.\n if (mThread != null) {\n mThread.interrupt();\n }\n\n // Start a new session by creating a new thread.\n mThread = new Thread(this, "ToyVpnThread");\n mThread.start();\n return START_STICKY;\n }\n\n @Override\n public void onDestroy() {\n if (mThread != null) {\n mThread.interrupt();\n }\n }\n\n @Override\n public boolean handleMessage(Message message) {\n if (message != null) {\n Toast.makeText(this, message.what, Toast.LENGTH_SHORT).show();\n }\n return true;\n }\n\n @Override\n public synchronized void run() {\n Log.i(TAG,"running vpnService");\n try {\n runVpnConnection();\n } catch (Exception e) {\n e.printStackTrace();\n //Log.e(TAG, "Got " + e.toString());\n } finally {\n try {\n mInterface.close();\n } catch (Exception e) {\n // ignore\n }\n mInterface = null;\n\n mHandler.sendEmptyMessage(R.string.disconnected);\n Log.i(TAG, "Exiting");\n }\n }\n\n private void configure() throws Exception {\n // If the old interface has exactly the same parameters, use it!\n if (mInterface != null) {\n Log.i(TAG, "Using the previous interface");\n return;\n }\n\n // Configure a builder while parsing the parameters.\n Builder builder = new Builder();\n builder.setSession(TAG)\n builder.addAddress("10.0.0.2", 32).addRoute("0.0.0.0", 0)\n try {\n mInterface.close();\n } catch (Exception e) {\n // ignore\n }\n\n mInterface = builder.establish();\n }\n\n private boolean runVpnConnection() throws Exception {\n\n configure()\n\n val in = new FileInputStream(mInterface.fileDescriptor)\n\n // Packets received need to be written to this output stream.\n val out = new FileOutputStream(mInterface.fileDescriptor)\n\n // The UDP channel can be used to pass/get ip package to/from server\n val tunnel = DatagramChannel.open()\n\n // For simplicity, we use the same thread for both reading and\n // writing. Here we put the tunnel into non-blocking mode.\n tunnel.configureBlocking(false)\n\n // Allocate the buffer for a single packet.\n val packet = ByteBuffer.allocate(32767)\n\n // Connect to the server, localhost is used for demonstration only.\n tunnel.connect(InetSocketAddress("127.0.0.1", 8087))\n\n // Protect this socket, so package send by it will not be feedback to the vpn service.\n protect(tunnel.socket())\n\n // We use a timer to determine the status of the tunnel. It\n // works on both sides. A positive value means sending, and\n // any other means receiving. We start with receiving.\n int timer = 0\n\n // We keep forwarding packets till something goes wrong.\n while (true) {\n // Assume that we did not make any progress in this iteration.\n boolean idle = true\n\n // Read the outgoing packet from the input stream.\n int length = `in`.read(packet.array())\n\n if (length > 0) {\n\n Log.i(TAG, "************new packet")\n\n // Write the outgoing packet to the tunnel.\n packet.limit(length)\n tunnel.write(packet);\n packet.clear()\n // There might be more outgoing packets.\n idle = false\n // If we were receiving, switch to sending.\n if (timer < 1) {\n timer = 1\n }\n\n }\n\n length = tunnel.read(packet)\n\n if (length > 0) {\n // Ignore control messages, which start with zero.\n if (packet.get(0).toInt() !== 0) {\n // Write the incoming packet to the output stream.\n out.write(packet.array(), 0, length)\n }\n packet.clear()\n // There might be more incoming packets.\n idle = false\n // If we were sending, switch to receiving.\n if (timer > 0) {\n timer = 0\n }\n }\n // If we are idle or waiting for the network, sleep for a\n // fraction of time to avoid busy looping.\n if (idle) {\n Thread.sleep(100)\n // Increase the timer. This is inaccurate but good enough,\n // since everything is operated in non-blocking mode.\n timer += if (timer > 0) 100 else -100\n // We are receiving for a long time but not sending.\n if (timer < -15000) {\n // Send empty control messages.\n packet.put(0.toByte()).limit(1)\n for (i in 0..2) {\n packet.position(0)\n tunnel.write(packet)\n }\n packet.clear()\n // Switch to sending.\n timer = 1\n }\n // We are sending for a long time but not receiving.\n if (timer > 20000) {\n throw IllegalStateException("Timed out")\n }\n }\n Thread.sleep(50)\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n在我的LogCat面板中,当应用程序崩溃时,我得到了以下跟踪:
\n\n FATAL EXCEPTION: main\n 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\n at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2950)\n at android.app.ActivityThread.access$1900(ActivityThread.java:151)\n at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1442)\n at android.os.Handler.dispatchMessage(Handler.java:99)\n at android.os.Looper.loop(Looper.java:155)\n at android.app.ActivityThread.main(ActivityThread.java:5520)\n at java.lang.reflect.Method.invokeNative(Native Method)\n at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029)\n at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)\n at dalvik.system.NativeStart.main(Native Method)\n Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter intent\n at com.example.skogs.wifictrl.model.VpnFilter.onStartCommand(VpnFilter.kt)\n at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2916)\n at android.app.ActivityThread.access$1900(ActivityThread.java:151)\xc2\xa0\n at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1442)\xc2\xa0\n at android.os.Handler.dispatchMessage(Handler.java:99)\xc2\xa0\n at android.os.Looper.loop(Looper.java:155)\xc2\xa0\n at android.app.ActivityThread.main(ActivityThread.java:5520)\xc2\xa0\n at java.lang.reflect.Method.invokeNative(Native Method)\xc2\xa0\n at java.lang.reflect.Method.invoke(Method.java:511)\xc2\xa0\n at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)\xc2\xa0\n at dalvik.system.NativeStart.main(Native Method)\xc2\xa0\nRun Code Online (Sandbox Code Playgroud)\n
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)
Run Code Online (Sandbox Code Playgroud)
表明问题所在。
文件onStartCommand指出(强调我的):
Intent:提供给 startService(Intent) 的 Intent,如给定。如果服务在其进程消失后重新启动,并且之前已返回除 START_STICKY_COMPATIBILITY 之外的任何内容,则该值可能为 null 。
null因此,您至少应该通过将onStartCommandKotlin 中的签名更改为:
override fun onStartCommand(intent:Intent?, flags:Int, startId:Int) {
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
8176 次 |
| 最近记录: |