【问题标题】:Android local VPN Service: can't get responseAndroid本地VPN服务:无法得到响应
【发布时间】:2016-10-13 06:43:00
【问题描述】:

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

问题

我的 VPN 服务取自 ToyVpn Google 示例,结合来自 123 的示例在本地使用(不连接到远程服务器)无法正常工作。


我的应用原则

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

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

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

【问题讨论】:

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


【解决方案1】:

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 中 onStartCommand 的签名更改为:

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

【讨论】:

  • 我已经更新了问题。我遇到了另一个问题。我无法得到任何回应,但我看到字节正在发送出去。我的界面有这样的配置: builder.addAddress("10.0.0.2", 32).addRoute("0.0.0.0", 0) 我的隧道连接到 tunnel.connect(InetSocketAddress("127.0.0.1", 55555)) .我想,我什么都不懂。但正如我看到的其他示例,他们的数据包转发正在使用这样的配置。可以给点建议吗,有什么问题吗?非常感谢!
  • @heyjohnnyfunt 更新后的问题似乎是一个具有不同根本原因的问题——我回答的问题是关于语言使用的——而您现在遇到的问题似乎与 Android API 使用和网络配置有关。我想用网络节点(带有地址)的图像和你想要实现的目标的描述创建一个单独的问题是有意义的。
  • 我认为我们大多数人都忽略了完成解决方案所需的ToyVpnServer.cpp。无论如何,我不确定如何在没有 root 访问权限的情况下在 Android 上实现该部分。
猜你喜欢
  • 2021-09-05
  • 1970-01-01
  • 1970-01-01
  • 2015-11-18
  • 2018-06-11
  • 2015-11-13
  • 1970-01-01
  • 2015-11-14
  • 1970-01-01
相关资源
最近更新 更多