mirror of
https://github.com/MetaCubeX/ClashMetaForAndroid.git
synced 2026-05-09 18:11:26 +08:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6a71267c6 | ||
|
|
0f4a46188c | ||
|
|
5917b90837 | ||
|
|
a222e90d1f | ||
|
|
3f60d713f8 | ||
|
|
9cb8433f3b | ||
|
|
428ca53532 | ||
|
|
6c4d7e537b | ||
|
|
58ab89736a | ||
|
|
5e34221a09 | ||
|
|
ac35f2a5f4 | ||
|
|
73992dca54 | ||
|
|
53dc20109d | ||
|
|
e7fef0a767 | ||
|
|
c73beabf7e | ||
|
|
c7409d7ac6 | ||
|
|
5e238ab5d3 | ||
|
|
a8f502ef4f | ||
|
|
c48ce82640 | ||
|
|
5594485bec | ||
|
|
f967bd299a | ||
|
|
a75c8f5dfc | ||
|
|
271d56c01c | ||
|
|
12220789a3 |
@@ -41,7 +41,8 @@
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="true"
|
||||
android:label="@string/launch_name">
|
||||
android:label="@string/launch_name"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ class AppCrashedActivity : BaseActivity<AppCrashedDesign>() {
|
||||
SystemLogcat.dumpCrash()
|
||||
}
|
||||
|
||||
Tracker.uploadLogcat(logs)
|
||||
|
||||
design.setAppLogs(logs)
|
||||
|
||||
while (isActive) {
|
||||
|
||||
@@ -20,9 +20,9 @@ import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.core.model.LogMessage
|
||||
import com.github.kr328.clash.log.LogcatCache
|
||||
import com.github.kr328.clash.log.LogcatWriter
|
||||
import com.github.kr328.clash.service.ClashManager
|
||||
import com.github.kr328.clash.service.remote.IClashManager
|
||||
import com.github.kr328.clash.service.RemoteService
|
||||
import com.github.kr328.clash.service.remote.ILogObserver
|
||||
import com.github.kr328.clash.service.remote.IRemoteService
|
||||
import com.github.kr328.clash.service.remote.unwrap
|
||||
import com.github.kr328.clash.util.logsDir
|
||||
import kotlinx.coroutines.*
|
||||
@@ -52,7 +52,7 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
|
||||
|
||||
showNotification()
|
||||
|
||||
bindService(ClashManager::class.intent, connection, Context.BIND_AUTO_CREATE)
|
||||
bindService(RemoteService::class.intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -88,7 +88,7 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
|
||||
return stopSelf()
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
val service = binder.unwrap(IClashManager::class)
|
||||
val service = binder.unwrap(IRemoteService::class).clash()
|
||||
val channel = Channel<LogMessage>(CACHE_CAPACITY)
|
||||
|
||||
try {
|
||||
|
||||
@@ -8,7 +8,8 @@ object SystemLogcat {
|
||||
"Go",
|
||||
"DEBUG",
|
||||
"AndroidRuntime",
|
||||
"ClashForAndroid"
|
||||
"ClashForAndroid",
|
||||
"LwIP",
|
||||
)
|
||||
|
||||
fun dumpCrash(): String {
|
||||
|
||||
@@ -15,7 +15,7 @@ import kotlinx.coroutines.launch
|
||||
|
||||
object Remote {
|
||||
val broadcasts: Broadcasts = Broadcasts(Global.application)
|
||||
val services: Services = Services(Global.application) {
|
||||
val service: Service = Service(Global.application) {
|
||||
ApplicationObserver.createdActivities.forEach { it.finish() }
|
||||
|
||||
val intent = AppCrashedActivity::class.intent
|
||||
@@ -56,10 +56,10 @@ object Remote {
|
||||
|
||||
while (true) {
|
||||
if (visible.receive()) {
|
||||
services.bind()
|
||||
service.bind()
|
||||
broadcasts.register()
|
||||
} else {
|
||||
services.unbind()
|
||||
service.unbind()
|
||||
broadcasts.unregister()
|
||||
}
|
||||
}
|
||||
|
||||
64
app/src/main/java/com/github/kr328/clash/remote/Service.kt
Normal file
64
app/src/main/java/com/github/kr328/clash/remote/Service.kt
Normal file
@@ -0,0 +1,64 @@
|
||||
package com.github.kr328.clash.remote
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import com.github.kr328.clash.Tracker
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.log.SystemLogcat
|
||||
import com.github.kr328.clash.service.RemoteService
|
||||
import com.github.kr328.clash.service.remote.IRemoteService
|
||||
import com.github.kr328.clash.service.remote.unwrap
|
||||
import com.github.kr328.clash.util.unbindServiceSilent
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class Service(private val context: Application, val crashed: () -> Unit) {
|
||||
val remote = Resource<IRemoteService>()
|
||||
|
||||
private val connection = object : ServiceConnection {
|
||||
private var lastCrashed: Long = -1
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder) {
|
||||
remote.set(service.unwrap(IRemoteService::class))
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
remote.set(null)
|
||||
|
||||
Tracker.uploadLogcat(SystemLogcat.dumpCrash())
|
||||
|
||||
if (System.currentTimeMillis() - lastCrashed < TOGGLE_CRASHED_INTERVAL) {
|
||||
unbind()
|
||||
|
||||
crashed()
|
||||
}
|
||||
|
||||
lastCrashed = System.currentTimeMillis()
|
||||
|
||||
Log.w("RemoteManager crashed")
|
||||
}
|
||||
}
|
||||
|
||||
fun bind() {
|
||||
try {
|
||||
context.bindService(RemoteService::class.intent, connection, Context.BIND_AUTO_CREATE)
|
||||
} catch (e: Exception) {
|
||||
unbind()
|
||||
|
||||
crashed()
|
||||
}
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
context.unbindServiceSilent(connection)
|
||||
|
||||
remote.set(null)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TOGGLE_CRASHED_INTERVAL = TimeUnit.SECONDS.toMillis(10)
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package com.github.kr328.clash.remote
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.service.ClashManager
|
||||
import com.github.kr328.clash.service.ProfileService
|
||||
import com.github.kr328.clash.service.remote.IClashManager
|
||||
import com.github.kr328.clash.service.remote.IProfileManager
|
||||
import com.github.kr328.clash.service.remote.unwrap
|
||||
import com.github.kr328.clash.util.unbindServiceSilent
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class Services(private val context: Application, val crashed: () -> Unit) {
|
||||
val clash = Resource<IClashManager>()
|
||||
val profile = Resource<IProfileManager>()
|
||||
|
||||
private val clashConnection = object : ServiceConnection {
|
||||
private var lastCrashed: Long = -1
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
clash.set(service?.unwrap(IClashManager::class))
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
clash.set(null)
|
||||
|
||||
if (System.currentTimeMillis() - lastCrashed < TOGGLE_CRASHED_INTERVAL) {
|
||||
unbind()
|
||||
|
||||
crashed()
|
||||
}
|
||||
|
||||
lastCrashed = System.currentTimeMillis()
|
||||
|
||||
Log.w("ClashManager crashed")
|
||||
}
|
||||
}
|
||||
|
||||
private val profileConnection = object : ServiceConnection {
|
||||
private var lastCrashed: Long = -1
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
profile.set(service?.unwrap(IProfileManager::class))
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
profile.set(null)
|
||||
|
||||
if (System.currentTimeMillis() - lastCrashed < TOGGLE_CRASHED_INTERVAL) {
|
||||
unbind()
|
||||
|
||||
crashed()
|
||||
}
|
||||
|
||||
lastCrashed = System.currentTimeMillis()
|
||||
|
||||
Log.w("ProfileService crashed")
|
||||
}
|
||||
}
|
||||
|
||||
fun bind() {
|
||||
try {
|
||||
context.bindService(ClashManager::class.intent, clashConnection, Context.BIND_AUTO_CREATE)
|
||||
context.bindService(ProfileService::class.intent, profileConnection, Context.BIND_AUTO_CREATE)
|
||||
} catch (e: Exception) {
|
||||
unbind()
|
||||
|
||||
crashed()
|
||||
}
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
context.unbindServiceSilent(clashConnection)
|
||||
context.unbindServiceSilent(profileConnection)
|
||||
|
||||
clash.set(null)
|
||||
profile.set(null)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TOGGLE_CRASHED_INTERVAL = TimeUnit.SECONDS.toMillis(10)
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,15 @@ suspend fun <T> withClash(
|
||||
block: suspend IClashManager.() -> T
|
||||
): T {
|
||||
while (true) {
|
||||
val client = Remote.services.clash.get()
|
||||
val remote = Remote.service.remote.get()
|
||||
val client = remote.clash()
|
||||
|
||||
try {
|
||||
return withContext(context) { client.block() }
|
||||
} catch (e: DeadObjectException) {
|
||||
Log.w("Remote services panic")
|
||||
|
||||
Remote.services.clash.reset(client)
|
||||
Remote.service.remote.reset(remote)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,14 +32,15 @@ suspend fun <T> withProfile(
|
||||
block: suspend IProfileManager.() -> T
|
||||
): T {
|
||||
while (true) {
|
||||
val client = Remote.services.profile.get()
|
||||
val remote = Remote.service.remote.get()
|
||||
val client = remote.profile()
|
||||
|
||||
try {
|
||||
return withContext(context) { client.block() }
|
||||
} catch (e: DeadObjectException) {
|
||||
Log.w("Remote services panic")
|
||||
|
||||
Remote.services.profile.reset(client)
|
||||
Remote.service.remote.reset(remote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.5.0"
|
||||
kotlin("jvm") version "1.5.10"
|
||||
`java-gradle-plugin`
|
||||
}
|
||||
|
||||
@@ -9,20 +9,10 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
|
||||
compileOnly(gradleApi())
|
||||
|
||||
api(kotlin("gradle-plugin"))
|
||||
api(kotlin("serialization"))
|
||||
api("com.android.tools.build:gradle:4.2.1") {
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk7")
|
||||
exclude("org.jetbrains.kotlin", "kotlin-reflect")
|
||||
}
|
||||
api("com.google.devtools.ksp:symbol-processing-gradle-plugin:1.5.0-1.0.0-alpha10") {
|
||||
exclude("com.android.tools.build", "gradle")
|
||||
}
|
||||
implementation(kotlin("gradle-plugin"))
|
||||
implementation(kotlin("serialization"))
|
||||
implementation("com.android.tools.build:gradle:4.2.1")
|
||||
implementation("com.google.devtools.ksp:symbol-processing-gradle-plugin:1.5.10-1.0.0-beta01")
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import org.gradle.api.Project
|
||||
|
||||
const val buildVersionCode = 204003
|
||||
const val buildVersionName = "2.4.3"
|
||||
const val buildVersionCode = 204006
|
||||
const val buildVersionName = "2.4.6"
|
||||
|
||||
const val buildMinSdkVersion = 21
|
||||
const val buildTargetSdkVersion = 30
|
||||
|
||||
@@ -11,14 +11,12 @@ data class BuildConfig(
|
||||
val minSdkVersion: Int,
|
||||
) : Serializable {
|
||||
companion object {
|
||||
fun of(extension: BaseExtension, variant: BaseVariant): BuildConfig {
|
||||
fun of(abis: List<NativeAbi>, minSdkVersion: Int, variant: BaseVariant): BuildConfig {
|
||||
return BuildConfig(
|
||||
debug = variant.buildType.isDebuggable,
|
||||
premium = variant.flavorName == "premium",
|
||||
abis = extension.defaultConfig.externalNativeBuild.cmake.abiFilters
|
||||
.map { NativeAbi.parse(it) }
|
||||
.distinct(),
|
||||
minSdkVersion = extension.defaultConfig.minSdkVersion!!.apiLevel
|
||||
abis = abis,
|
||||
minSdkVersion = minSdkVersion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,19 @@ class ClashBuildPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
target.afterEvaluate {
|
||||
target.extensions.getByType(LibraryExtension::class.java).apply {
|
||||
val abis = defaultConfig.externalNativeBuild.cmake.abiFilters
|
||||
.map { NativeAbi.parse(it) }
|
||||
.distinct()
|
||||
val minSdkVersion = defaultConfig.minSdkVersion!!.apiLevel
|
||||
|
||||
target.tasks.register("cleanGolang", ClashCleanTask::class.java) {
|
||||
it.applyFrom(target, abis)
|
||||
|
||||
target.tasks.getByName("clean").dependsOn(it)
|
||||
}
|
||||
|
||||
libraryVariants.forEach { variant ->
|
||||
val config = BuildConfig.of(this, variant)
|
||||
val config = BuildConfig.of(abis, minSdkVersion, variant)
|
||||
val buildDir = target.golangBuild.resolve(variant.name)
|
||||
val capitalize = variant.name.capitalize(Locale.getDefault())
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ abstract class ClashBuildTask : DefaultTask() {
|
||||
config.abis.forEach {
|
||||
Command.ofGoRun(
|
||||
"make/make.go",
|
||||
listOf("bridge", "native", "build", "android", it.goArch),
|
||||
input.resolve("tun2socket/bridge"),
|
||||
listOf("tun2socket", ".", "android", it.goArch),
|
||||
input.resolve("tun2socket"),
|
||||
environment.ofLwipBuild(it)
|
||||
).exec()
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.github.kr328.clash.tools
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.tasks.Delete
|
||||
import golangSource
|
||||
|
||||
abstract class ClashCleanTask : Delete() {
|
||||
fun applyFrom(project: Project, abis: List<NativeAbi>) {
|
||||
val bridge = project.golangSource.resolve("tun2socket")
|
||||
|
||||
delete(bridge.resolve("build"))
|
||||
|
||||
abis.forEach {
|
||||
delete(bridge.resolve("build_android_${it.goArch}.go"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,11 +34,25 @@ class Environment(
|
||||
}
|
||||
|
||||
fun ofLwipBuild(abi: NativeAbi): Map<String, String> {
|
||||
val host = when {
|
||||
Os.isFamily(Os.FAMILY_WINDOWS) ->
|
||||
"windows"
|
||||
Os.isFamily(Os.FAMILY_MAC) ->
|
||||
"darwin"
|
||||
Os.isFamily(Os.FAMILY_UNIX) ->
|
||||
"linux"
|
||||
else ->
|
||||
throw GradleException("Unsupported host: ${System.getProperty("os.name")}")
|
||||
}
|
||||
|
||||
val compiler = ndkDirectory.resolve("toolchains/llvm/prebuilt/$host-x86_64/bin")
|
||||
.resolve("${abi.compiler}${minSdkVersion}-clang")
|
||||
val ar = ndkDirectory.resolve("toolchains/llvm/prebuilt/$host-x86_64/bin")
|
||||
.resolve("${abi.archiver}-ar")
|
||||
|
||||
return mapOf(
|
||||
"CMAKE_SYSTEM_NAME" to "Android",
|
||||
"CMAKE_ANDROID_NDK" to ndkDirectory.absolutePath,
|
||||
"CMAKE_ANDROID_ARCH_ABI" to abi.value,
|
||||
"CMAKE_SYSTEM_VERSION" to minSdkVersion.toString()
|
||||
"CC" to compiler.absolutePath,
|
||||
"AR" to ar.absolutePath,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,14 @@ package com.github.kr328.clash.tools
|
||||
enum class NativeAbi(
|
||||
val value: String,
|
||||
val compiler: String,
|
||||
val archiver: String,
|
||||
val goArch: String,
|
||||
val goArm: String
|
||||
) {
|
||||
ArmeabiV7a("armeabi-v7a", "armv7a-linux-androideabi", "arm", "7"),
|
||||
Arm64V8a("arm64-v8a", "aarch64-linux-android", "arm64", ""),
|
||||
X86("x86", "i686-linux-android", "386", ""),
|
||||
X64("x86_64", "x86_64-linux-android", "amd64", "");
|
||||
ArmeabiV7a("armeabi-v7a", "armv7a-linux-androideabi", "arm-linux-androideabi", "arm", "7"),
|
||||
Arm64V8a("arm64-v8a", "aarch64-linux-android", "aarch64-linux-android", "arm64", ""),
|
||||
X86("x86", "i686-linux-android", "i686-linux-android", "386", ""),
|
||||
X64("x86_64", "x86_64-linux-android", "x86_64-linux-android", "amd64", "");
|
||||
|
||||
companion object {
|
||||
fun parse(value: String): NativeAbi {
|
||||
|
||||
@@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
project(clash-bridge C)
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE on)
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
|
||||
|
||||
set(GO_OUTPUT_BASE ${GO_OUTPUT}/${FLAVOR_NAME})
|
||||
|
||||
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
|
||||
@@ -96,15 +96,17 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeNotifyInstalledAppChanged(J
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartTun(JNIEnv *env, jobject thiz, jint fd,
|
||||
jint mtu, jstring dns,
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartTun(JNIEnv *env, jobject thiz,
|
||||
jint fd, jint mtu,
|
||||
jstring dns, jstring blocking,
|
||||
jobject cb) {
|
||||
TRACE_METHOD();
|
||||
|
||||
scoped_string _blocking = get_string(blocking);
|
||||
scoped_string _dns = get_string(dns);
|
||||
jobject _interface = new_global(cb);
|
||||
|
||||
startTun(fd, mtu, _dns, _interface);
|
||||
startTun(fd, mtu, _dns, _blocking, _interface);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"cfa/tun"
|
||||
|
||||
"golang.org/x/sync/semaphore"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
type remoteTun struct {
|
||||
@@ -23,7 +21,7 @@ type remoteTun struct {
|
||||
}
|
||||
|
||||
func (t *remoteTun) markSocket(fd int) {
|
||||
_ = t.limit.Acquire(context.Background(), 1)
|
||||
_ = t.limit.Acquire(context.TODO(), 1)
|
||||
defer t.limit.Release(1)
|
||||
|
||||
if t.closed {
|
||||
@@ -34,7 +32,7 @@ func (t *remoteTun) markSocket(fd int) {
|
||||
}
|
||||
|
||||
func (t *remoteTun) querySocketUid(protocol int, source, target string) int {
|
||||
_ = t.limit.Acquire(context.Background(), 1)
|
||||
_ = t.limit.Acquire(context.TODO(), 1)
|
||||
defer t.limit.Release(1)
|
||||
|
||||
if t.closed {
|
||||
@@ -50,26 +48,27 @@ func (t *remoteTun) stop() {
|
||||
|
||||
t.closed = true
|
||||
|
||||
C.release_object(t.callback)
|
||||
app.ApplyTunContext(nil, nil)
|
||||
|
||||
log.Infoln("Android tun device destroyed")
|
||||
C.release_object(t.callback)
|
||||
}
|
||||
|
||||
//export startTun
|
||||
func startTun(fd, mtu C.int, dns C.c_string, callback unsafe.Pointer) C.int {
|
||||
func startTun(fd, mtu C.int, gateway, dns C.c_string, callback unsafe.Pointer) C.int {
|
||||
f := int(fd)
|
||||
m := int(mtu)
|
||||
g := C.GoString(gateway)
|
||||
d := C.GoString(dns)
|
||||
|
||||
remote := &remoteTun{callback: callback, closed: false, limit: semaphore.NewWeighted(4)}
|
||||
|
||||
if tun.Start(f, m, d) != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
app.ApplyTunContext(remote.markSocket, remote.querySocketUid)
|
||||
|
||||
log.Infoln("Android tun device created")
|
||||
if tun.Start(f, m, g, d, remote.stop) != nil {
|
||||
app.ApplyTunContext(nil, nil)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/kr328/tun2socket/bridge"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const defaultDnsReadTimeout = time.Second * 30
|
||||
|
||||
func shouldHijackDns(dns net.IP, target net.IP, targetPort int) bool {
|
||||
if targetPort != 53 {
|
||||
return false
|
||||
@@ -22,58 +15,7 @@ func shouldHijackDns(dns net.IP, target net.IP, targetPort int) bool {
|
||||
return net.IPv4zero.Equal(dns) || target.Equal(dns)
|
||||
}
|
||||
|
||||
func hijackUDPDns(pkt []byte, lAddr, rAddr net.Addr, udp bridge.UDP) {
|
||||
go func() {
|
||||
answer, err := relayDnsPacket(pkt)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = udp.WriteTo(answer, lAddr, rAddr)
|
||||
|
||||
recycleUDP(pkt)
|
||||
}()
|
||||
}
|
||||
|
||||
func hijackTCPDns(conn net.Conn) {
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
|
||||
for {
|
||||
if err := conn.SetReadDeadline(time.Now().Add(defaultDnsReadTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var length uint16
|
||||
if binary.Read(conn, binary.BigEndian, &length) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data := make([]byte, length)
|
||||
|
||||
_, err := io.ReadFull(conn, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rb, err := relayDnsPacket(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if binary.Write(conn, binary.BigEndian, uint16(len(rb))) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := conn.Write(rb); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func relayDnsPacket(payload []byte) ([]byte, error) {
|
||||
func relayDns(payload []byte) ([]byte, error) {
|
||||
msg := &D.Msg{}
|
||||
if err := msg.Unpack(payload); err != nil {
|
||||
return nil, err
|
||||
@@ -84,14 +26,6 @@ func relayDnsPacket(payload []byte) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ans := range r.Answer {
|
||||
header := ans.Header()
|
||||
|
||||
if header.Class == D.ClassINET && (header.Rrtype == D.TypeA || header.Rrtype == D.TypeAAAA) {
|
||||
header.Ttl = 1
|
||||
}
|
||||
}
|
||||
|
||||
r.SetRcode(msg, r.Rcode)
|
||||
|
||||
return r.Pack()
|
||||
|
||||
39
core/src/main/golang/tun/link.go
Normal file
39
core/src/main/golang/tun/link.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package tun
|
||||
|
||||
import "github.com/Dreamacro/clash/log"
|
||||
|
||||
func (a *adapter) rx() {
|
||||
log.Infoln("[ATUN] Device rx started")
|
||||
defer log.Infoln("[ATUN] Device rx exited")
|
||||
defer a.once.Do(a.stop)
|
||||
defer a.close()
|
||||
|
||||
buf := make([]byte, a.mtu)
|
||||
|
||||
for {
|
||||
n, err := a.device.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = a.stack.Link().Write(buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func (a *adapter) tx() {
|
||||
log.Infoln("[ATUN] Device tx started")
|
||||
defer log.Infoln("[ATUN] Device tx exited")
|
||||
defer a.once.Do(a.stop)
|
||||
defer a.close()
|
||||
|
||||
buf := make([]byte, a.mtu)
|
||||
|
||||
for {
|
||||
n, err := a.stack.Link().Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = a.device.Write(buf[:n])
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,103 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
CTX "github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
func handleTCP(conn net.Conn, source *net.TCPAddr, target *net.TCPAddr) {
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Type: C.SOCKS,
|
||||
SrcIP: source.IP,
|
||||
DstIP: target.IP,
|
||||
SrcPort: strconv.Itoa(source.Port),
|
||||
DstPort: strconv.Itoa(target.Port),
|
||||
AddrType: C.AtypIPv4,
|
||||
Host: "",
|
||||
RawSrcAddr: source,
|
||||
RawDstAddr: target,
|
||||
const defaultDnsReadTimeout = time.Second * 30
|
||||
|
||||
func (a *adapter) tcp() {
|
||||
log.Infoln("[ATUN] TCP listener started")
|
||||
defer log.Infoln("[ATUN] TCP listener exited")
|
||||
defer a.stack.Close()
|
||||
|
||||
accept:
|
||||
for {
|
||||
conn, err := a.stack.TCP().Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sAddr := conn.LocalAddr().(*net.TCPAddr)
|
||||
tAddr := conn.RemoteAddr().(*net.TCPAddr)
|
||||
|
||||
// handle dns messages
|
||||
if a.hijackTCPDNS(conn, tAddr) {
|
||||
continue
|
||||
}
|
||||
|
||||
// drop all connections connect to blocking list
|
||||
for _, b := range a.blocking {
|
||||
if b.Contains(tAddr.IP) {
|
||||
continue accept
|
||||
}
|
||||
}
|
||||
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Type: C.SOCKS,
|
||||
SrcIP: sAddr.IP,
|
||||
DstIP: tAddr.IP,
|
||||
SrcPort: strconv.Itoa(sAddr.Port),
|
||||
DstPort: strconv.Itoa(tAddr.Port),
|
||||
AddrType: C.AtypIPv4,
|
||||
Host: "",
|
||||
RawSrcAddr: sAddr,
|
||||
RawDstAddr: tAddr,
|
||||
}
|
||||
|
||||
tunnel.Add(context.NewConnContext(conn, metadata))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *adapter) hijackTCPDNS(conn net.Conn, tAddr *net.TCPAddr) bool {
|
||||
if !shouldHijackDns(a.dns, tAddr.IP, tAddr.Port) {
|
||||
return false
|
||||
}
|
||||
|
||||
tunnel.Add(CTX.NewConnContext(conn, metadata))
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
|
||||
for {
|
||||
if err := conn.SetReadDeadline(time.Now().Add(defaultDnsReadTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var length uint16
|
||||
if binary.Read(conn, binary.BigEndian, &length) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data := make([]byte, length)
|
||||
|
||||
_, err := io.ReadFull(conn, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rb, err := relayDns(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if binary.Write(conn, binary.BigEndian, uint16(len(rb))) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := conn.Write(rb); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -3,34 +3,42 @@ package tun
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/kr328/tun2socket"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
device *os.File
|
||||
stack tun2socket.Stack
|
||||
type adapter struct {
|
||||
device *os.File
|
||||
stack tun2socket.Stack
|
||||
blocking []*net.IPNet
|
||||
dns net.IP
|
||||
mtu int
|
||||
once sync.Once
|
||||
stop func()
|
||||
}
|
||||
|
||||
var lock sync.Mutex
|
||||
var tun *context
|
||||
var instance *adapter
|
||||
|
||||
func (ctx *context) close() {
|
||||
_ = ctx.stack.Close()
|
||||
_ = ctx.device.Close()
|
||||
func (a *adapter) close() {
|
||||
_ = a.stack.Close()
|
||||
_ = a.device.Close()
|
||||
}
|
||||
|
||||
func Start(fd, mtu int, dns string) error {
|
||||
func Start(fd, mtu int, dns string, blocking string, stop func()) error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
stopLocked()
|
||||
if instance != nil {
|
||||
instance.close()
|
||||
}
|
||||
|
||||
dnsIP := net.ParseIP(dns)
|
||||
_ = syscall.SetNonblock(fd, true)
|
||||
|
||||
device := os.NewFile(uintptr(fd), "/dev/tun")
|
||||
|
||||
stack, err := tun2socket.NewStack(mtu)
|
||||
if err != nil {
|
||||
_ = device.Close()
|
||||
@@ -38,100 +46,35 @@ func Start(fd, mtu int, dns string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := &context{
|
||||
device: device,
|
||||
stack: stack,
|
||||
dn := net.ParseIP(dns)
|
||||
|
||||
var blk []*net.IPNet
|
||||
|
||||
for _, b := range strings.Split(blocking, ";") {
|
||||
_, n, err := net.ParseCIDR(b)
|
||||
if err != nil {
|
||||
device.Close()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
blk = append(blk, n)
|
||||
}
|
||||
|
||||
go func() {
|
||||
// device -> lwip
|
||||
instance = &adapter{
|
||||
device: device,
|
||||
stack: stack,
|
||||
blocking: blk,
|
||||
dns: dn,
|
||||
mtu: mtu,
|
||||
once: sync.Once{},
|
||||
stop: stop,
|
||||
}
|
||||
|
||||
defer ctx.close()
|
||||
|
||||
buf := make([]byte, mtu)
|
||||
|
||||
for {
|
||||
n, err := device.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = stack.Link().Write(buf[:n])
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// lwip -> device
|
||||
|
||||
defer ctx.close()
|
||||
|
||||
buf := make([]byte, mtu)
|
||||
|
||||
for {
|
||||
n, err := stack.Link().Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = device.Write(buf[:n])
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// lwip tcp
|
||||
|
||||
defer ctx.close()
|
||||
|
||||
for {
|
||||
conn, err := stack.TCP().Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
source := conn.LocalAddr().(*net.TCPAddr)
|
||||
target := conn.RemoteAddr().(*net.TCPAddr)
|
||||
|
||||
if shouldHijackDns(dnsIP, target.IP, target.Port) {
|
||||
hijackTCPDns(conn)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
handleTCP(conn, source, target)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// lwip udp
|
||||
|
||||
defer ctx.close()
|
||||
|
||||
for {
|
||||
buf := allocUDP(mtu)
|
||||
|
||||
n, lAddr, rAddr, err := stack.UDP().ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
source := lAddr.(*net.UDPAddr)
|
||||
target := rAddr.(*net.UDPAddr)
|
||||
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if shouldHijackDns(dnsIP, target.IP, target.Port) {
|
||||
hijackUDPDns(buf[:n], source, target, stack.UDP())
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
handleUDP(buf[:n], source, target, stack.UDP())
|
||||
}
|
||||
}()
|
||||
|
||||
tun = ctx
|
||||
go instance.rx()
|
||||
go instance.tx()
|
||||
go instance.tcp()
|
||||
go instance.udp()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -140,13 +83,9 @@ func Stop() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
stopLocked()
|
||||
}
|
||||
|
||||
func stopLocked() {
|
||||
if tun != nil {
|
||||
tun.close()
|
||||
if instance != nil {
|
||||
instance.close()
|
||||
}
|
||||
|
||||
tun = nil
|
||||
instance = nil
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ package tun
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
"github.com/kr328/tun2socket/bridge"
|
||||
"github.com/kr328/tun2socket"
|
||||
|
||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
@@ -12,48 +13,89 @@ import (
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
type udpPacket struct {
|
||||
source *net.UDPAddr
|
||||
data []byte
|
||||
udp bridge.UDP
|
||||
type packet struct {
|
||||
stack tun2socket.Stack
|
||||
local *net.UDPAddr
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (u *udpPacket) Data() []byte {
|
||||
return u.data
|
||||
func (pkt *packet) Data() []byte {
|
||||
return pkt.data
|
||||
}
|
||||
|
||||
func (u *udpPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
return u.udp.WriteTo(b, u.source, addr)
|
||||
func (pkt *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
return pkt.stack.UDP().WriteTo(b, pkt.local, addr)
|
||||
}
|
||||
|
||||
func (u *udpPacket) Drop() {
|
||||
recycleUDP(u.data)
|
||||
func (pkt *packet) Drop() {
|
||||
pool.Put(pkt.data)
|
||||
}
|
||||
|
||||
func (u *udpPacket) LocalAddr() net.Addr {
|
||||
func (pkt *packet) LocalAddr() net.Addr {
|
||||
return &net.UDPAddr{
|
||||
IP: u.source.IP,
|
||||
Port: int(u.source.Port),
|
||||
IP: pkt.local.IP,
|
||||
Port: pkt.local.Port,
|
||||
Zone: "",
|
||||
}
|
||||
}
|
||||
|
||||
func handleUDP(payload []byte, source *net.UDPAddr, target *net.UDPAddr, udp bridge.UDP) {
|
||||
pkt := &udpPacket{
|
||||
source: source,
|
||||
data: payload,
|
||||
udp: udp,
|
||||
func (a *adapter) udp() {
|
||||
log.Infoln("[ATUN] UDP receiver started")
|
||||
defer log.Infoln("[ATUN] UDP receiver exited")
|
||||
defer a.stack.Close()
|
||||
|
||||
read:
|
||||
for {
|
||||
buf := pool.Get(a.mtu)
|
||||
|
||||
n, lAddr, rAddr, err := a.stack.UDP().ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sAddr := lAddr.(*net.UDPAddr)
|
||||
tAddr := rAddr.(*net.UDPAddr)
|
||||
|
||||
// handle dns messages
|
||||
if a.hijackUDPDNS(buf[:n], sAddr, tAddr) {
|
||||
continue
|
||||
}
|
||||
|
||||
// drop all packet send to blocking list
|
||||
for _, b := range a.blocking {
|
||||
if b.Contains(tAddr.IP) {
|
||||
continue read
|
||||
}
|
||||
}
|
||||
|
||||
pkt := &packet{
|
||||
stack: a.stack,
|
||||
local: sAddr,
|
||||
data: buf[:n],
|
||||
}
|
||||
|
||||
adapter := adapters.NewPacket(socks5.ParseAddrToSocksAddr(tAddr), pkt, C.SOCKS)
|
||||
|
||||
tunnel.AddPacket(adapter)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *adapter) hijackUDPDNS(pkt []byte, sAddr, tAddr *net.UDPAddr) bool {
|
||||
if !shouldHijackDns(a.dns, tAddr.IP, tAddr.Port) {
|
||||
return false
|
||||
}
|
||||
|
||||
adapter := adapters.NewPacket(socks5.ParseAddrToSocksAddr(target), pkt, C.SOCKS)
|
||||
go func() {
|
||||
answer, err := relayDns(pkt)
|
||||
|
||||
tunnel.AddPacket(adapter)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
func allocUDP(size int) []byte {
|
||||
return pool.Get(size)
|
||||
}
|
||||
_, _ = a.stack.UDP().WriteTo(answer, sAddr, tAddr)
|
||||
|
||||
func recycleUDP(payload []byte) {
|
||||
_ = pool.Put(payload)
|
||||
pool.Put(pkt)
|
||||
}()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
Submodule core/src/main/golang/tun2socket updated: 958ecb352f...587e1100a6
@@ -62,10 +62,11 @@ object Clash {
|
||||
fd: Int,
|
||||
mtu: Int,
|
||||
dns: String,
|
||||
blocking: String,
|
||||
markSocket: (Int) -> Boolean,
|
||||
querySocketUid: (protocol: Int, source: InetSocketAddress, target: InetSocketAddress) -> Int
|
||||
) {
|
||||
Bridge.nativeStartTun(fd, mtu, dns, object : TunInterface {
|
||||
Bridge.nativeStartTun(fd, mtu, dns, blocking, object : TunInterface {
|
||||
override fun markSocket(fd: Int) {
|
||||
markSocket(fd)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ object Bridge {
|
||||
external fun nativeQueryTrafficTotal(): Long
|
||||
external fun nativeNotifyDnsChanged(dnsList: String)
|
||||
external fun nativeNotifyInstalledAppChanged(uidList: String)
|
||||
external fun nativeStartTun(fd: Int, mtu: Int, dns: String, cb: TunInterface)
|
||||
external fun nativeStartTun(fd: Int, mtu: Int, dns: String, blocking: String, cb: TunInterface)
|
||||
external fun nativeStopTun()
|
||||
external fun nativeStartHttp(listenAt: String): String?
|
||||
external fun nativeStopHttp()
|
||||
|
||||
@@ -70,6 +70,13 @@ class NetworkSettingsDesign(
|
||||
configure = vpnDependencies::add,
|
||||
)
|
||||
|
||||
switch(
|
||||
value = srvStore::blockLoopback,
|
||||
title = R.string.block_loopback,
|
||||
summary = R.string.block_loopback_summary,
|
||||
configure = vpnDependencies::add,
|
||||
)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 29) {
|
||||
switch(
|
||||
value = srvStore::systemProxy,
|
||||
|
||||
@@ -211,4 +211,6 @@
|
||||
<string name="sources">源代碼</string>
|
||||
<string name="clash_core">Clash 核心</string>
|
||||
<string name="name_server_policy">Name Server 策略</string>
|
||||
<string name="block_loopback">阻止本地迴環</string>
|
||||
<string name="block_loopback_summary">阻止本地迴環連接</string>
|
||||
</resources>
|
||||
@@ -211,4 +211,6 @@
|
||||
<string name="sources">源代碼</string>
|
||||
<string name="clash_core">Clash 核心</string>
|
||||
<string name="name_server_policy">Name Server 策略</string>
|
||||
<string name="block_loopback">阻止本地迴環</string>
|
||||
<string name="block_loopback_summary">阻止本地迴環連接</string>
|
||||
</resources>
|
||||
@@ -211,4 +211,6 @@
|
||||
<string name="sources">源代码</string>
|
||||
<string name="clash_core">Clash 核心</string>
|
||||
<string name="name_server_policy">Name Server 策略</string>
|
||||
<string name="block_loopback">阻止本地回环</string>
|
||||
<string name="block_loopback_summary">阻止本地回环连接</string>
|
||||
</resources>
|
||||
@@ -119,6 +119,8 @@
|
||||
<string name="bypass_private_network_summary">Bypass private network addresses</string>
|
||||
<string name="dns_hijacking">DNS Hijacking</string>
|
||||
<string name="dns_hijacking_summary">Handle all dns packet</string>
|
||||
<string name="block_loopback">Block Loopback</string>
|
||||
<string name="block_loopback_summary">Block loopback connections</string>
|
||||
<string name="system_proxy">System Proxy</string>
|
||||
<string name="system_proxy_summary">Attach http proxy to VpnService</string>
|
||||
<string name="access_control_mode">Access Control Mode</string>
|
||||
|
||||
@@ -238,7 +238,7 @@ class OverrideSettingsDesign(
|
||||
summary = R.string.sideload_geoip_summary
|
||||
) {
|
||||
clicked {
|
||||
requests.offer(Request.EditSideloadGeoip)
|
||||
requests.trySend(Request.EditSideloadGeoip)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ class OverrideSettingsDesign(
|
||||
placeholder = R.string.dont_modify,
|
||||
configure = dnsDependencies::add,
|
||||
)
|
||||
|
||||
|
||||
editableTextMap(
|
||||
value = configuration.dns::nameserverPolicy,
|
||||
keyAdapter = TextAdapter.String,
|
||||
@@ -396,6 +396,6 @@ class OverrideSettingsDesign(
|
||||
}
|
||||
|
||||
fun requestClear() {
|
||||
requests.offer(Request.ResetOverride)
|
||||
requests.trySend(Request.ResetOverride)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,51 +32,51 @@ class ProxyMenu(
|
||||
R.id.not_selectable -> {
|
||||
uiStore.proxyExcludeNotSelectable = item.isChecked
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReLaunch)
|
||||
requests.trySend(ProxyDesign.Request.ReLaunch)
|
||||
}
|
||||
R.id.single -> {
|
||||
uiStore.proxySingleLine = true
|
||||
|
||||
updateConfig()
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.multiple -> {
|
||||
uiStore.proxySingleLine = false
|
||||
|
||||
updateConfig()
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.default_ -> {
|
||||
uiStore.proxySort = ProxySort.Default
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.name -> {
|
||||
uiStore.proxySort = ProxySort.Title
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.delay -> {
|
||||
uiStore.proxySort = ProxySort.Delay
|
||||
|
||||
requests.offer(ProxyDesign.Request.ReloadAll)
|
||||
requests.trySend(ProxyDesign.Request.ReloadAll)
|
||||
}
|
||||
R.id.dont_modify -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(null))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(null))
|
||||
}
|
||||
R.id.direct_mode -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(TunnelState.Mode.Direct))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(TunnelState.Mode.Direct))
|
||||
}
|
||||
R.id.global_mode -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(TunnelState.Mode.Global))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(TunnelState.Mode.Global))
|
||||
}
|
||||
R.id.rule_mode -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(TunnelState.Mode.Rule))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(TunnelState.Mode.Rule))
|
||||
}
|
||||
R.id.script_mode -> {
|
||||
requests.offer(ProxyDesign.Request.PatchMode(TunnelState.Mode.Script))
|
||||
requests.trySend(ProxyDesign.Request.PatchMode(TunnelState.Mode.Script))
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
|
||||
2
kaidl
2
kaidl
Submodule kaidl updated: 963190ac8e...16da2e83b7
@@ -48,15 +48,6 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
named("debug") {
|
||||
java.srcDir(buildDir.resolve("generated/ksp/debug/kotlin"))
|
||||
}
|
||||
named("release") {
|
||||
java.srcDir(buildDir.resolve("generated/ksp/release/kotlin"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -74,4 +65,12 @@ dependencies {
|
||||
implementation("androidx.core:core-ktx:$coreVersion")
|
||||
implementation("dev.rikka.rikkax.preference:multiprocess:$muiltprocessVersion")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion")
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
android {
|
||||
libraryVariants.forEach {
|
||||
sourceSets[it.name].java.srcDir(buildDir.resolve("generated/ksp/${it.name}/kotlin"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,7 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name=".ClashManager"
|
||||
android:exported="false"
|
||||
android:process=":background" />
|
||||
<service
|
||||
android:name=".ProfileService"
|
||||
android:name=".RemoteService"
|
||||
android:exported="false"
|
||||
android:process=":background" />
|
||||
<service
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.content.Context
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.model.*
|
||||
@@ -9,22 +8,16 @@ import com.github.kr328.clash.service.data.Selection
|
||||
import com.github.kr328.clash.service.data.SelectionDao
|
||||
import com.github.kr328.clash.service.remote.IClashManager
|
||||
import com.github.kr328.clash.service.remote.ILogObserver
|
||||
import com.github.kr328.clash.service.remote.wrap
|
||||
import com.github.kr328.clash.service.store.ServiceStore
|
||||
import com.github.kr328.clash.service.util.sendOverrideChanged
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import java.util.*
|
||||
|
||||
class ClashManager : BaseService(), IClashManager {
|
||||
private val store by lazy { ServiceStore(this) }
|
||||
private val binder = this.wrap()
|
||||
class ClashManager(private val context: Context) : IClashManager,
|
||||
CoroutineScope by CoroutineScope(Dispatchers.IO) {
|
||||
private val store = ServiceStore(context)
|
||||
private var logReceiver: ReceiveChannel<LogMessage>? = null
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun queryTunnelState(): TunnelState {
|
||||
return Clash.queryTunnelState()
|
||||
}
|
||||
@@ -68,7 +61,7 @@ class ClashManager : BaseService(), IClashManager {
|
||||
override fun patchOverride(slot: Clash.OverrideSlot, configuration: ConfigurationOverride) {
|
||||
Clash.patchOverride(slot, configuration)
|
||||
|
||||
sendOverrideChanged()
|
||||
context.sendOverrideChanged()
|
||||
}
|
||||
|
||||
override fun clearOverride(slot: Clash.OverrideSlot) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.content.Context
|
||||
import com.github.kr328.clash.service.data.Database
|
||||
import com.github.kr328.clash.service.data.ImportedDao
|
||||
import com.github.kr328.clash.service.data.Pending
|
||||
@@ -9,34 +8,27 @@ import com.github.kr328.clash.service.data.PendingDao
|
||||
import com.github.kr328.clash.service.model.Profile
|
||||
import com.github.kr328.clash.service.remote.IFetchObserver
|
||||
import com.github.kr328.clash.service.remote.IProfileManager
|
||||
import com.github.kr328.clash.service.remote.wrap
|
||||
import com.github.kr328.clash.service.store.ServiceStore
|
||||
import com.github.kr328.clash.service.util.directoryLastModified
|
||||
import com.github.kr328.clash.service.util.generateProfileUUID
|
||||
import com.github.kr328.clash.service.util.importedDir
|
||||
import com.github.kr328.clash.service.util.pendingDir
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.FileNotFoundException
|
||||
import java.util.*
|
||||
|
||||
class ProfileService : BaseService(), IProfileManager {
|
||||
private val service = this
|
||||
private val store by lazy { ServiceStore(this) }
|
||||
private val binder = this.wrap()
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
Database.database //.init
|
||||
class ProfileManager(private val context: Context) : IProfileManager,
|
||||
CoroutineScope by CoroutineScope(Dispatchers.IO) {
|
||||
private val store = ServiceStore(context)
|
||||
|
||||
init {
|
||||
launch {
|
||||
ProfileReceiver.rescheduleAll(service)
|
||||
Database.database //.init
|
||||
|
||||
ProfileReceiver.rescheduleAll(context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +44,7 @@ class ProfileService : BaseService(), IProfileManager {
|
||||
|
||||
PendingDao().insert(pending)
|
||||
|
||||
pendingDir.resolve(uuid.toString()).apply {
|
||||
context.pendingDir.resolve(uuid.toString()).apply {
|
||||
deleteRecursively()
|
||||
mkdirs()
|
||||
|
||||
@@ -119,21 +111,21 @@ class ProfileService : BaseService(), IProfileManager {
|
||||
}
|
||||
|
||||
override suspend fun commit(uuid: UUID, callback: IFetchObserver?) {
|
||||
ProfileProcessor.apply(service, uuid, callback)
|
||||
ProfileProcessor.apply(context, uuid, callback)
|
||||
|
||||
scheduleUpdate(uuid, false)
|
||||
}
|
||||
|
||||
override suspend fun release(uuid: UUID) {
|
||||
ProfileProcessor.release(this, uuid)
|
||||
ProfileProcessor.release(context, uuid)
|
||||
}
|
||||
|
||||
override suspend fun delete(uuid: UUID) {
|
||||
ImportedDao().queryByUUID(uuid)?.also {
|
||||
ProfileReceiver.cancelNext(service, it)
|
||||
ProfileReceiver.cancelNext(context, it)
|
||||
}
|
||||
|
||||
ProfileProcessor.delete(service, uuid)
|
||||
ProfileProcessor.delete(context, uuid)
|
||||
}
|
||||
|
||||
override suspend fun queryByUUID(uuid: UUID): Profile? {
|
||||
@@ -159,7 +151,7 @@ class ProfileService : BaseService(), IProfileManager {
|
||||
}
|
||||
|
||||
override suspend fun setActive(profile: Profile) {
|
||||
ProfileProcessor.active(this, profile.uuid)
|
||||
ProfileProcessor.active(context, profile.uuid)
|
||||
}
|
||||
|
||||
private suspend fun resolveProfile(uuid: UUID): Profile? {
|
||||
@@ -186,14 +178,14 @@ class ProfileService : BaseService(), IProfileManager {
|
||||
}
|
||||
|
||||
private fun resolveUpdatedAt(uuid: UUID): Long {
|
||||
return pendingDir.resolve(uuid.toString()).directoryLastModified
|
||||
?: importedDir.resolve(uuid.toString()).directoryLastModified
|
||||
return context.pendingDir.resolve(uuid.toString()).directoryLastModified
|
||||
?: context.importedDir.resolve(uuid.toString()).directoryLastModified
|
||||
?: -1
|
||||
}
|
||||
|
||||
private fun cloneImportedFiles(source: UUID, target: UUID = source) {
|
||||
val s = importedDir.resolve(source.toString())
|
||||
val t = pendingDir.resolve(target.toString())
|
||||
val s = context.importedDir.resolve(source.toString())
|
||||
val t = context.pendingDir.resolve(target.toString())
|
||||
|
||||
if (!s.exists())
|
||||
throw FileNotFoundException("profile $source not found")
|
||||
@@ -207,9 +199,9 @@ class ProfileService : BaseService(), IProfileManager {
|
||||
val imported = ImportedDao().queryByUUID(uuid) ?: return
|
||||
|
||||
if (startImmediately) {
|
||||
ProfileReceiver.schedule(service, imported)
|
||||
ProfileReceiver.schedule(context, imported)
|
||||
} else {
|
||||
ProfileReceiver.scheduleNext(service, imported)
|
||||
ProfileReceiver.scheduleNext(context, imported)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ import com.github.kr328.clash.common.compat.pendingIntentFlags
|
||||
import com.github.kr328.clash.common.constants.Components
|
||||
import com.github.kr328.clash.common.constants.Intents
|
||||
import com.github.kr328.clash.common.id.UndefinedIds
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.common.util.setUUID
|
||||
import com.github.kr328.clash.common.util.uuid
|
||||
import com.github.kr328.clash.service.data.ImportedDao
|
||||
@@ -148,17 +147,12 @@ class ProfileWorker : BaseService() {
|
||||
|
||||
NotificationManagerCompat.from(applicationContext)
|
||||
.notify(id, notification)
|
||||
|
||||
Log.d("notify processing $name: id = $id")
|
||||
|
||||
try {
|
||||
block()
|
||||
} finally {
|
||||
withContext(NonCancellable) {
|
||||
NotificationManagerCompat.from(applicationContext)
|
||||
.cancel(id)
|
||||
|
||||
Log.d("notify processed $name: id = $id")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,8 +184,6 @@ class ProfileWorker : BaseService() {
|
||||
|
||||
NotificationManagerCompat.from(this)
|
||||
.notify(id, notification)
|
||||
|
||||
Log.d("notify completed $name: id = $id")
|
||||
}
|
||||
|
||||
private fun failed(uuid: UUID, name: String, reason: String) {
|
||||
@@ -207,8 +199,6 @@ class ProfileWorker : BaseService() {
|
||||
|
||||
NotificationManagerCompat.from(this)
|
||||
.notify(id, notification)
|
||||
|
||||
Log.d("notify failed $name: id = $id")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import com.github.kr328.clash.service.remote.IClashManager
|
||||
import com.github.kr328.clash.service.remote.IRemoteService
|
||||
import com.github.kr328.clash.service.remote.IProfileManager
|
||||
import com.github.kr328.clash.service.remote.wrap
|
||||
import com.github.kr328.clash.service.util.cancelAndJoinBlocking
|
||||
|
||||
class RemoteService : BaseService(), IRemoteService {
|
||||
private val binder = this.wrap()
|
||||
|
||||
private var clash: ClashManager? = null
|
||||
private var profile: ProfileManager? = null
|
||||
private var clashBinder: IClashManager? = null
|
||||
private var profileBinder: IProfileManager? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
clash = ClashManager(this)
|
||||
profile = ProfileManager(this)
|
||||
clashBinder = clash?.wrap() as IClashManager?
|
||||
profileBinder = profile?.wrap() as IProfileManager?
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
clash?.cancelAndJoinBlocking()
|
||||
profile?.cancelAndJoinBlocking()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun clash(): IClashManager {
|
||||
return clashBinder!!
|
||||
}
|
||||
|
||||
override fun profile(): IProfileManager {
|
||||
return profileBinder!!
|
||||
}
|
||||
}
|
||||
@@ -138,6 +138,9 @@ class TunService : VpnService(), CoroutineScope by CoroutineScope(Dispatchers.De
|
||||
resources.getStringArray(R.array.bypass_private_route).map(::parseCIDR).forEach {
|
||||
addRoute(it.ip, it.prefix)
|
||||
}
|
||||
|
||||
// Route of virtual DNS
|
||||
addRoute(TUN_DNS, 32)
|
||||
} else {
|
||||
addRoute(NET_ANY, 0)
|
||||
}
|
||||
@@ -213,10 +216,16 @@ class TunService : VpnService(), CoroutineScope by CoroutineScope(Dispatchers.De
|
||||
}
|
||||
}
|
||||
|
||||
val blocking = mutableListOf("$TUN_GATEWAY/$TUN_SUBNET_PREFIX")
|
||||
if (store.blockLoopback) {
|
||||
blocking.add(NET_SUBNET_LOOPBACK)
|
||||
}
|
||||
|
||||
TunModule.TunDevice(
|
||||
fd = establish()?.detachFd()
|
||||
?: throw NullPointerException("Establish VPN rejected by system"),
|
||||
mtu = TUN_MTU,
|
||||
blocking = blocking.joinToString(";"),
|
||||
dns = if (store.dnsHijacking) NET_ANY else TUN_DNS,
|
||||
)
|
||||
}
|
||||
@@ -227,8 +236,9 @@ class TunService : VpnService(), CoroutineScope by CoroutineScope(Dispatchers.De
|
||||
companion object {
|
||||
private const val TUN_MTU = 9000
|
||||
private const val TUN_SUBNET_PREFIX = 30
|
||||
private const val TUN_GATEWAY = "172.31.255.253"
|
||||
private const val TUN_DNS = "198.18.0.1"
|
||||
private const val TUN_GATEWAY = "172.19.0.1"
|
||||
private const val TUN_DNS = "172.19.0.2"
|
||||
private const val NET_ANY = "0.0.0.0"
|
||||
private const val NET_SUBNET_LOOPBACK = "127.0.0.0/8"
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,8 @@ class TunModule(private val vpn: VpnService) : Module<Unit>(vpn) {
|
||||
data class TunDevice(
|
||||
val fd: Int,
|
||||
val mtu: Int,
|
||||
val dns: String
|
||||
val blocking: String,
|
||||
val dns: String,
|
||||
)
|
||||
|
||||
private val connectivity = service.getSystemService<ConnectivityManager>()!!
|
||||
@@ -57,6 +58,7 @@ class TunModule(private val vpn: VpnService) : Module<Unit>(vpn) {
|
||||
fd = device.fd,
|
||||
mtu = device.mtu,
|
||||
dns = device.dns,
|
||||
blocking = device.blocking,
|
||||
markSocket = vpn::protect,
|
||||
querySocketUid = this::queryUid
|
||||
)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.github.kr328.clash.service.remote
|
||||
|
||||
import com.github.kr328.kaidl.BinderInterface
|
||||
|
||||
@BinderInterface
|
||||
interface IRemoteService {
|
||||
fun clash(): IClashManager
|
||||
fun profile(): IProfileManager
|
||||
}
|
||||
@@ -46,6 +46,11 @@ class ServiceStore(context: Context) {
|
||||
defaultValue = false
|
||||
)
|
||||
|
||||
var blockLoopback by store.boolean(
|
||||
key = "block_loopback",
|
||||
defaultValue = true
|
||||
)
|
||||
|
||||
var dynamicNotification by store.boolean(
|
||||
key = "dynamic_notification",
|
||||
defaultValue = true
|
||||
|
||||
Reference in New Issue
Block a user