Android本地VPN服务:无法获得响应

我对Android及其服务相当陌生。 我试图在我的应用程序(与Kotlin和Java)实现本地 VPN服务。

我的VPN服务采取从ToyVpn谷歌的例子,结合从1,2,3的例子来使用它在本地(没有连接到远程服务器)不起作用。


我的APP PRINCIPE

我看到这个和这个问题,但答案不是很有见地,我找不到解决方案。

所以这个应用程序非常简单 :当用户点击主要活动的“是”按钮时,它应该转发所有的数据包,而当点击“否”时 – 阻止它。 目的是: 将其用作防火墙 ,就像这样:

我的VPN应用程序的原则

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

而在我的运行功能,我刚刚配置隧道连接到本地IP地址:

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

从而:

  1. VPN配置的设置与这个例子非常相似,并且两个例子都来自上面提到的SO问题,用于本地使用。
  2. 我的包转发是从ToyVpn的例子。

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

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

更新

以上是解决,但我看到,包正在发送,但我无法得到任何回应。 我无法弄清楚为什么。


完整的JAVA代码我的VPN服务

 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) } } } 

LOG 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的文档声明(重点是我的):

意图:提供给startService(Intent)的Intent,如同给出的。 如果服务在进程退出后重新启动,则可能为空 ,并且之前已经返回除START_STICKY_COMPATIBILITY以外的任何内容。

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

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