我对Android及其服务很陌生。我正在尝试在我的应用中使用本地 VPN服务(使用Kotlin和Java)。
从ToyVpn Google示例中获取的我的VPN服务,结合1,2,3中的示例,以便在本地使用它(无需连接到远程服务器)工作
我看到了this和this SO问题,但是那里的答案非常有见地,我无法找到解决问题的方法。
所以应用程序非常简单:当用户点击主要活动上的#34; YES" -button时,它应该转发所有数据包,当点击" NO&时#34; - 阻止它。目的:将其用作防火墙 ,如下所示:
我的所有代码都是用Kotlin语言编写的,但它并不复杂,对于JAVA开发人员来说非常清楚。所以我希望上面的代码非常清楚,因为它取自here(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服务类非常相似,但必须在没有任何身份验证,握手等的情况下在本地工作,所以我使用这样的设置编辑了示例:< / 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))
由此:
我知道我的VPN正在运行,因为如果我更改 addRoute 配置,我将无法访问互联网。
所以我不知道我实际上做错了什么!如果我使用代码从ToyVpn转发数据包,则每次新数据包时,应用崩溃。
更新
上面的问题已经解决了,但我看到,数据包正在发送,但我无法得到任何回应。我无法弄清楚原因。
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)
答案 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) {