Android本地VPN服务:无法获得回复

时间:2016-06-12 22:32:33

标签: java android vpn kotlin kotlin-android-extensions

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

问题

ToyVpn Google示例中获取的我的VPN服务,结合123中的示例,以便在本地使用它(无需连接到远程服务器)工作

MY APP PRINCIPE

我看到了thisthis SO问题,但是那里的答案非常有见地,我无法找到解决问题的方法。

所以应用程序非常简单:当用户点击主要活动上的#34; YES" -button时,它应该转发所有数据包,当点击" NO&时#34; - 阻止它。目的:将其用作防火墙 ,如下所示:

The principle of my VPN app

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

MY CONFIGURATION& CODE

要在我的应用中启用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服务类非常相似,但必须在没有任何身份验证,握手等的情况下在本地工作,所以我使用这样的设置编辑了示例:< / p>

 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配置的设置非常类似于this示例以及上述SO问题的两个示例,供本地使用。
  2. 数据包转发取自ToyVpn示例。
  3. 我知道我的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)
            }
        }
    }
    

    LOG CAT OUTPUT

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

1 个答案:

答案 0 :(得分:3)

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,如给定的。 :此   如果服务在其进程之后重新启动,则可以为null   走了,之前它已经退回了   START_STICKY_COMPATIBILITY。

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

来处理onStartCommand案例。
override fun onStartCommand(intent:Intent?, flags:Int, startId:Int) {