From 792190b5f406edc8a8a11eae4f6f7bc37055b266 Mon Sep 17 00:00:00 2001 From: Kr328 <39107975+Kr328@users.noreply.github.com> Date: Wed, 20 Nov 2019 18:13:28 +0800 Subject: [PATCH] split ui / service --- .../kr328/clash/core/ClashProcessStatus.aidl | 3 + .../kr328/clash/service/IClashObserver.aidl | 7 ++ .../kr328/clash/service/IClashService.aidl | 12 +++ .../kr328/clash/service/ClashService.kt | 100 ++++++++++++++++++ .../github/kr328/clash/service/TunService.kt | 84 +++++++++++++++ 5 files changed, 206 insertions(+) create mode 100644 service/src/main/aidl/com/github/kr328/clash/core/ClashProcessStatus.aidl create mode 100644 service/src/main/aidl/com/github/kr328/clash/service/IClashObserver.aidl create mode 100644 service/src/main/aidl/com/github/kr328/clash/service/IClashService.aidl create mode 100644 service/src/main/java/com/github/kr328/clash/service/ClashService.kt create mode 100644 service/src/main/java/com/github/kr328/clash/service/TunService.kt diff --git a/service/src/main/aidl/com/github/kr328/clash/core/ClashProcessStatus.aidl b/service/src/main/aidl/com/github/kr328/clash/core/ClashProcessStatus.aidl new file mode 100644 index 0000000..4d17b7c --- /dev/null +++ b/service/src/main/aidl/com/github/kr328/clash/core/ClashProcessStatus.aidl @@ -0,0 +1,3 @@ +package com.github.kr328.clash.core; + +parcelable ClashProcessStatus; \ No newline at end of file diff --git a/service/src/main/aidl/com/github/kr328/clash/service/IClashObserver.aidl b/service/src/main/aidl/com/github/kr328/clash/service/IClashObserver.aidl new file mode 100644 index 0000000..298285a --- /dev/null +++ b/service/src/main/aidl/com/github/kr328/clash/service/IClashObserver.aidl @@ -0,0 +1,7 @@ +package com.github.kr328.clash.service; + +import com.github.kr328.clash.core.ClashProcessStatus; + +interface IClashObserver { + void onStatusChanged(in ClashProcessStatus status); +} diff --git a/service/src/main/aidl/com/github/kr328/clash/service/IClashService.aidl b/service/src/main/aidl/com/github/kr328/clash/service/IClashService.aidl new file mode 100644 index 0000000..18f67a6 --- /dev/null +++ b/service/src/main/aidl/com/github/kr328/clash/service/IClashService.aidl @@ -0,0 +1,12 @@ +package com.github.kr328.clash.service; + +import com.github.kr328.clash.service.IClashObserver; + +interface IClashService { + void start(); + void stop(); + void registerObserver(String id, boolean notifyCurrent, in IClashObserver observer); + void unregisterObserver(String id); + void startTunDevice(in ParcelFileDescriptor fd, int mtu); + void stopTunDevice(); +} diff --git a/service/src/main/java/com/github/kr328/clash/service/ClashService.kt b/service/src/main/java/com/github/kr328/clash/service/ClashService.kt new file mode 100644 index 0000000..584ae07 --- /dev/null +++ b/service/src/main/java/com/github/kr328/clash/service/ClashService.kt @@ -0,0 +1,100 @@ +package com.github.kr328.clash.service + +import android.app.Service +import android.content.Intent +import android.os.IBinder +import android.os.ParcelFileDescriptor +import android.os.RemoteException +import com.github.kr328.clash.core.Clash +import com.github.kr328.clash.core.ClashProcessStatus +import java.io.IOException + +class ClashService : Service() { + companion object { + private val TAG = "ClashForAndroid" + } + + private lateinit var clash: Clash + + private var status = ClashProcessStatus(ClashProcessStatus.STATUS_STOPPED) + set(value) { + field = value + + for ( observer in observers.values ) { + runCatching { + observer.onStatusChanged(status) + } + } + } + private val observers = mutableMapOf() + + private inner class ClashServiceImpl : IClashService.Stub() { + override fun registerObserver(id: String?, notifyCurrent: Boolean, observer: IClashObserver?) { + if (id == null || observer == null) + throw RemoteException() + + observers[id] = observer + + if ( notifyCurrent ) + observer.onStatusChanged(status) + + + observer.asBinder().linkToDeath({ + observers.remove(id) + }, 0) + } + + override fun unregisterObserver(id: String?) { + if (id == null) + throw RemoteException() + + observers.remove(id) + } + + override fun stopTunDevice() { + clash.stop() + } + + override fun start() { + try { + clash.start() + } catch (e: IOException) { + throw RemoteException(e.message) + } + } + + override fun stop() { + clash.stop() + } + + override fun startTunDevice(fd: ParcelFileDescriptor, mtu: Int) { + try { + clash.startTunDevice(fd.fileDescriptor, mtu) + } catch (e: IOException) { + throw RemoteException(e.message) + } + } + } + + override fun onCreate() { + super.onCreate() + + clash = Clash( + this, + filesDir.resolve("clash"), + cacheDir.resolve("clash_controller") + ) { + this.status = it + } + } + + override fun onBind(intent: Intent?): IBinder? { + return ClashServiceImpl() + } + + override fun onDestroy() { + clash.stop() + + super.onDestroy() + } +} \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/TunService.kt b/service/src/main/java/com/github/kr328/clash/service/TunService.kt new file mode 100644 index 0000000..59d8596 --- /dev/null +++ b/service/src/main/java/com/github/kr328/clash/service/TunService.kt @@ -0,0 +1,84 @@ +package com.github.kr328.clash.service + +import android.app.Service +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.net.VpnService +import android.os.IBinder +import android.os.ParcelFileDescriptor +import com.github.kr328.clash.core.ClashProcessStatus + +class TunService : VpnService() { + companion object { + // from https://github.com/shadowsocks/shadowsocks-android/blob/master/core/src/main/java/com/github/shadowsocks/bg/VpnService.kt + private const val VPN_MTU = 1500 + private const val PRIVATE_VLAN4_CLIENT = "172.19.0.1" + private const val PRIVATE_VLAN4_ROUTER = "172.19.0.2" + private const val PRIVATE_VLAN6_CLIENT = "fdfe:dcba:9876::1" + private const val PRIVATE_VLAN6_ROUTER = "fdfe:dcba:9876::2" + } + + private lateinit var fileDescriptor: ParcelFileDescriptor + private val connection = object: ServiceConnection { + override fun onServiceDisconnected(name: ComponentName?) { + stopSelf() + } + + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { + val clash = IClashService.Stub.asInterface( + service + ) ?: throw NullPointerException() + + clash.start() + clash.registerObserver("tun", false, object: IClashObserver.Stub() { + override fun onStatusChanged(status: ClashProcessStatus?) { + if ( status == null ) + return + + when ( status.status ) { + ClashProcessStatus.STATUS_STOPPED -> + stopSelf() + ClashProcessStatus.STATUS_STARTED -> + clash.startTunDevice(fileDescriptor, VPN_MTU) + } + } + }) + } + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + super.onStartCommand(intent, flags, startId) + + if ( prepare(this) != null ) { + startActivity(Intent(this, VpnRequestActivity::class.java)) + stopSelf() + return Service.START_NOT_STICKY + } + + fileDescriptor = Builder() + .addAddress(PRIVATE_VLAN4_CLIENT, 30) + .addDnsServer(PRIVATE_VLAN4_ROUTER) + .addAddress(PRIVATE_VLAN6_CLIENT, 126) + .addDnsServer(PRIVATE_VLAN6_ROUTER) + .addDisallowedApplication(packageName) + .addRoute("0.0.0.0", 0) + .addRoute("::", 0) + .setMtu(VPN_MTU) + .setBlocking(false) + .establish() ?: throw NullPointerException("Unable to establish VPN") + + bindService(Intent(this, ClashService::class.java), connection, Context.BIND_AUTO_CREATE) + + return Service.START_NOT_STICKY + } + + override fun onDestroy() { + fileDescriptor.close() + + unbindService(connection) + + super.onDestroy() + } +} \ No newline at end of file