From cf4e1c5b95e30730493bac69673e3b649bf72467 Mon Sep 17 00:00:00 2001
From: Kr328 <39107975+Kr328@users.noreply.github.com>
Date: Tue, 19 Nov 2019 00:06:19 +0800
Subject: [PATCH] new impl for clash core
---
app/src/main/AndroidManifest.xml | 15 +-
.../com/github/kr328/clash/IClashService.aidl | 1 +
.../com/github/kr328/clash/ClashService.kt | 27 ++--
.../com/github/kr328/clash/MainActivity.kt | 47 +++---
.../java/com/github/kr328/clash/TunService.kt | 47 ++++++
buildSrc/src/main/java/GolangBuildTask.kt | 2 +-
core/build.gradle | 4 +-
core/src/main/golang/go.mod | 10 +-
core/src/main/golang/go.sum | 9 ++
core/src/main/golang/init.go | 44 ------
core/src/main/golang/load.go | 55 -------
core/src/main/golang/log/log.go | 35 -----
core/src/main/golang/main.go | 30 +++-
core/src/main/golang/profile/load.go | 16 ++-
core/src/main/golang/server/server.go | 61 ++++++++
core/src/main/golang/server/tun.go | 36 +++++
core/src/main/golang/server/utils.go | 23 +++
core/src/main/golang/tun/dev.go | 66 +++++++++
core/src/main/golang/tun/tun.go | 136 ++++++++++++++++++
.../com/github/kr328/clash/core/Clash.java | 5 +-
.../kr328/clash/core/ClashException.java | 2 +-
21 files changed, 494 insertions(+), 177 deletions(-)
create mode 100644 app/src/main/java/com/github/kr328/clash/TunService.kt
delete mode 100644 core/src/main/golang/init.go
delete mode 100644 core/src/main/golang/load.go
delete mode 100644 core/src/main/golang/log/log.go
create mode 100644 core/src/main/golang/server/server.go
create mode 100644 core/src/main/golang/server/tun.go
create mode 100644 core/src/main/golang/server/utils.go
create mode 100644 core/src/main/golang/tun/dev.go
create mode 100644 core/src/main/golang/tun/tun.go
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7d3574c..ce5c234 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -20,6 +20,19 @@
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/aidl/com/github/kr328/clash/IClashService.aidl b/app/src/main/aidl/com/github/kr328/clash/IClashService.aidl
index e22570f..aa47b66 100644
--- a/app/src/main/aidl/com/github/kr328/clash/IClashService.aidl
+++ b/app/src/main/aidl/com/github/kr328/clash/IClashService.aidl
@@ -3,4 +3,5 @@ package com.github.kr328.clash;
interface IClashService {
void loadProfile(String path);
+ void startTunDevice(int fd, int mtu);
}
diff --git a/app/src/main/java/com/github/kr328/clash/ClashService.kt b/app/src/main/java/com/github/kr328/clash/ClashService.kt
index b4ee905..9fa1bd3 100644
--- a/app/src/main/java/com/github/kr328/clash/ClashService.kt
+++ b/app/src/main/java/com/github/kr328/clash/ClashService.kt
@@ -3,39 +3,36 @@ package com.github.kr328.clash
import android.app.Service
import android.content.Intent
import android.os.IBinder
-import android.os.RemoteException
import android.util.Log
import com.github.kr328.clash.core.Clash
-import com.github.kr328.clash.core.ClashException
-import java.io.File
+import java.lang.Exception
import kotlin.concurrent.thread
class ClashService : Service() {
- private val binder = object: IClashService.Stub() {
+ private inner class ClashServiceImpl : IClashService.Stub() {
override fun loadProfile(path: String?) {
- if ( path == null || !File(path).exists() )
- throw RemoteException("Invalid path")
-
thread {
- try {
- Clash.loadProfileFromPath(path)
- }
- catch (e: ClashException) {
- Log.w("ClashForAndroid", "Load failure", e)
- }
+ Clash.loadProfileFromPath(path)
}
}
+
+ override fun startTunDevice(fd: Int, mtu: Int) {
+ Clash.startTun(fd, mtu)
+ }
}
override fun onCreate() {
super.onCreate()
- Clash.init(filesDir.resolve("clash").absolutePath)
+ Clash.init(
+ applicationInfo.nativeLibraryDir + "/libclash.so",
+ filesDir.resolve("clash").absolutePath
+ )
Clash.loadDefault()
}
override fun onBind(intent: Intent?): IBinder? {
- return binder
+ return ClashServiceImpl()
}
override fun onDestroy() {
diff --git a/app/src/main/java/com/github/kr328/clash/MainActivity.kt b/app/src/main/java/com/github/kr328/clash/MainActivity.kt
index 484313b..191d578 100644
--- a/app/src/main/java/com/github/kr328/clash/MainActivity.kt
+++ b/app/src/main/java/com/github/kr328/clash/MainActivity.kt
@@ -5,6 +5,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
+import android.net.VpnService
import android.os.Bundle
import android.os.IBinder
import android.util.Log
@@ -19,6 +20,31 @@ import kotlin.concurrent.thread
class MainActivity : AppCompatActivity() {
companion object {
private const val REQUEST_CODE = 233
+ private const val VPN_REQUEST = 234
+ }
+
+ private val connection = object: ServiceConnection {
+ override fun onServiceDisconnected(name: ComponentName?) {
+ Toast.makeText(this@MainActivity, "Disconnected", Toast.LENGTH_LONG).show()
+ }
+
+ override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+ try {
+ val clash = IClashService.Stub.asInterface(service)
+
+ clash.loadProfile(cacheDir.resolve("config.yaml").absolutePath)
+
+// VpnService.prepare(this@MainActivity)?.apply {
+// startActivityForResult(this, VPN_REQUEST)
+// } ?: startService(Intent(this@MainActivity, TunService::class.java))
+ }
+ catch (e: Exception) {
+ runOnUiThread {
+ Toast.makeText(this@MainActivity, e.toString(), Toast.LENGTH_LONG).show()
+ }
+ }
+ }
+
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -39,25 +65,14 @@ class MainActivity : AppCompatActivity() {
contentResolver.openInputStream(data?.data ?: throw RuntimeException())?.apply { copyTo(it) }?.close()
}
- bindService(Intent(this, ClashService::class.java), object: ServiceConnection {
- override fun onServiceDisconnected(name: ComponentName?) {
- Toast.makeText(this@MainActivity, "Disconnected", Toast.LENGTH_LONG).show()
- }
+ //startService(Intent(this, ClashService::class.java))
- override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
- try {
- val clash = IClashService.Stub.asInterface(service)
-
- clash.loadProfile(cacheDir.resolve("config.yaml").absolutePath)
- }
- catch (e: Exception) {
- Log.e("ClashForAndroid", "Load", e)
- }
- }
-
- }, Context.BIND_AUTO_CREATE)
+ bindService(Intent(this, ClashService::class.java), connection, Context.BIND_AUTO_CREATE)
}
}
+ else if ( requestCode == VPN_REQUEST && resultCode == Activity.RESULT_OK ) {
+ startService(Intent(this, TunService::class.java))
+ }
else {
super.onActivityResult(requestCode, resultCode, data)
}
diff --git a/app/src/main/java/com/github/kr328/clash/TunService.kt b/app/src/main/java/com/github/kr328/clash/TunService.kt
new file mode 100644
index 0000000..a2364e0
--- /dev/null
+++ b/app/src/main/java/com/github/kr328/clash/TunService.kt
@@ -0,0 +1,47 @@
+package com.github.kr328.clash
+
+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 java.lang.RuntimeException
+
+class TunService : VpnService() {
+ private lateinit var fileDescriptor: ParcelFileDescriptor
+ private val connection = object: ServiceConnection {
+ override fun onServiceDisconnected(name: ComponentName?) {
+ stopSelf()
+ }
+
+ override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+ IClashService.Stub.asInterface(service)?.startTunDevice(fileDescriptor.fd, 1500) ?: stopSelf()
+ }
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ super.onStartCommand(intent, flags, startId)
+
+ fileDescriptor = Builder()
+ .addAddress("10.0.0.1", 24)
+ .addDisallowedApplication(packageName)
+ .addRoute("0.0.0.0", 0)
+ .setMtu(1500)
+ .setBlocking(false)
+ .establish() ?: throw RuntimeException("Unable to establish VPN")
+
+ bindService(Intent(this, ClashService::class.java), connection, Context.BIND_AUTO_CREATE)
+
+ return Service.START_STICKY
+ }
+
+ override fun onDestroy() {
+ fileDescriptor.close()
+ unbindService(connection)
+
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/java/GolangBuildTask.kt b/buildSrc/src/main/java/GolangBuildTask.kt
index 4353150..444cec5 100644
--- a/buildSrc/src/main/java/GolangBuildTask.kt
+++ b/buildSrc/src/main/java/GolangBuildTask.kt
@@ -28,7 +28,7 @@ open class GolangBuildTask : Exec() {
environment.put("CC", toolchainRoot.resolve(compilerPrefix + "-clang".exe()).absolutePath)
environment.put("LD", toolchainRoot.resolve(linkerPrefix + "ld".exe()).absolutePath)
- commandLine = listOf("go".exe(), "build", "-buildmode", "c-shared", "-o", options.outputDir.resolve("$abi/libclash.so").absolutePath)
+ commandLine = listOf("go".exe(), "build", "-o", options.outputDir.resolve("$abi/libclash.so").absolutePath)
super.exec()
}
diff --git a/core/build.gradle b/core/build.gradle
index f9c3c8a..5f25909 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -22,7 +22,7 @@ android {
sourceSets {
main {
- jniLibs.srcDirs = ["$buildDir/intermediates/native_output"]
+ jniLibs.srcDirs += ["$buildDir/intermediates/native_output"]
}
}
}
@@ -37,7 +37,7 @@ afterEvaluate {
def options = new GolangBuildOptions(file("src/main/golang"), file("$buildDir/intermediates/native_output"), "23")
def ts = compileArch.stream().map { abi ->
- tasks.register("golangBuildFor" + abi.replace("-", ""), GolangBuildTask.class) {
+ tasks.register("golangBuildFor" + abi.replace("-", "").replace("_", ""), GolangBuildTask.class) {
setOptions options, abi
}
}.toArray()
diff --git a/core/src/main/golang/go.mod b/core/src/main/golang/go.mod
index f127708..8ef9038 100644
--- a/core/src/main/golang/go.mod
+++ b/core/src/main/golang/go.mod
@@ -1,5 +1,11 @@
-module github.com/kr328/clash-for-android
+module github.com/kr328/clash
go 1.13
-require github.com/Dreamacro/clash v0.16.1-0.20191112020912-8e10e67b8925
+require (
+ github.com/Dreamacro/clash v0.16.1-0.20191112020912-8e10e67b8925
+ github.com/google/btree v1.0.0 // indirect
+ github.com/google/netstack v0.0.0-20191116005144-95bf25ab4723
+ golang.org/x/sys v0.0.0-20191118133127-cf1e2d577169
+ golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
+)
diff --git a/core/src/main/golang/go.sum b/core/src/main/golang/go.sum
index 736f2cf..f5c0f53 100644
--- a/core/src/main/golang/go.sum
+++ b/core/src/main/golang/go.sum
@@ -1,3 +1,4 @@
+github.com/Dreamacro/clash v0.16.0 h1:ZvV9apOrDHC0s5ff4YCHTRANd/XyOuLWvpyLWOkwS6U=
github.com/Dreamacro/clash v0.16.1-0.20191112020912-8e10e67b8925 h1:MukeaV6O94OWvqzcd3C635+xQNa8spHqKEI0E+ovXnA=
github.com/Dreamacro/clash v0.16.1-0.20191112020912-8e10e67b8925/go.mod h1:4ZBtABBmIGqISniBtu9fpYORrE5mKqIP8SMCTrNNVNY=
github.com/Dreamacro/go-shadowsocks2 v0.1.5-0.20191012162057-46254afc8b68 h1:UBDLpj1IGVkUcUBuZWE6DmZApPTZcnmcV6AfyDN/yhg=
@@ -16,6 +17,10 @@ github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/netstack v0.0.0-20191116005144-95bf25ab4723 h1:z92yYpQg57ql9n96PBLFhT+N5X44uQ2f3+CNp9Osmu8=
+github.com/google/netstack v0.0.0-20191116005144-95bf25ab4723/go.mod h1:r/rILWg3r1Qy9G1IFMhsqWLq2GjwuYoTuPgG7ckMAjk=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
@@ -51,8 +56,12 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191118133127-cf1e2d577169 h1:LPLFLulk2vyM7yI3CwNW64O6e8AxBmr9opfv14yI7HI=
+golang.org/x/sys v0.0.0-20191118133127-cf1e2d577169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/core/src/main/golang/init.go b/core/src/main/golang/init.go
deleted file mode 100644
index 4a53df2..0000000
--- a/core/src/main/golang/init.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package main
-
-/*
-#ifndef JNI_COMPILE
-#define JNI_COMPILE
-#include
-#include
-
-#define CLASH_EXCEPTION_CLASS "com/github/kr328/clash/core/ClashException"
-
-extern const char *_GoStringPtr(_GoString_ s);
-
-static void jni_throw_clash_exception(JNIEnv *env, _GoString_ message) {
- (*env)->ThrowNew(env, CLASH_EXCEPTION_CLASS, _GoStringPtr(message));
-}
-
-static char *jni_get_string(JNIEnv *env, jstring string) {
- const char *jni_string = (*env)->GetStringUTFChars(env, string, NULL);
- char *result = strdup(jni_string);
- (*env)->ReleaseStringUTFChars(env, string, jni_string);
- return result;
-}
-#endif //JNI_COMPILE
-*/
-import "C"
-
-import (
- "github.com/Dreamacro/clash/constant"
-)
-
-//export Java_com_github_kr328_clash_core_Clash_init
-func Java_com_github_kr328_clash_core_Clash_init(env *C.JNIEnv, class C.jclass, home C.jstring) {
- cs := C.jni_get_string(env, home)
- if cs == nil {
- C.jni_throw_clash_exception(env, "Unable to get path")
- return
- }
-
- p := C.GoString(cs)
-
- C.free(unsafe.Pointer(cs))
-
- constant.SetHomeDir(p)
-}
diff --git a/core/src/main/golang/load.go b/core/src/main/golang/load.go
deleted file mode 100644
index 37b58e1..0000000
--- a/core/src/main/golang/load.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package main
-
-/*
-#ifndef JNI_COMPILE
-#define JNI_COMPILE
-#include
-#include
-
-#define CLASH_EXCEPTION_CLASS "com/github/kr328/clash/core/ClashException"
-
-extern const char *_GoStringPtr(_GoString_ s);
-
-static void jni_throw_clash_exception(JNIEnv *env, _GoString_ message) {
- (*env)->ThrowNew(env, CLASH_EXCEPTION_CLASS, _GoStringPtr(message));
-}
-
-static char *jni_get_string(JNIEnv *env, jstring string) {
- const char *jni_string = (*env)->GetStringUTFChars(env, string, NULL);
- char *result = strdup(jni_string);
- (*env)->ReleaseStringUTFChars(env, string, jni_string);
- return result;
-}
-#endif //JNI_COMPILE
-*/
-import "C"
-import "unsafe"
-
-import (
- "github.com/kr328/clash-for-android/log"
- "github.com/kr328/clash-for-android/profile"
-)
-
-//export Java_com_github_kr328_clash_core_Clash_loadDefault
-func Java_com_github_kr328_clash_core_Clash_loadDefault(env *C.JNIEnv, class C.jclass) {
- log.Info("Clash load default")
-
- profile.LoadDefault()
-}
-
-//export Java_com_github_kr328_clash_core_Clash_loadProfileFromPath
-func Java_com_github_kr328_clash_core_Clash_loadProfileFromPath(env *C.JNIEnv, class C.jclass, path C.jstring) {
- cs := C.jni_get_string(env, path)
- if cs == nil {
- C.jni_throw_clash_exception(env, "Unable to get path")
- return
- }
-
- p := C.GoString(cs)
-
- C.free(unsafe.Pointer(cs))
-
- if err := profile.LoadFromFile(p); err != nil {
- C.jni_throw_clash_exception(env, err.Error())
- }
-}
diff --git a/core/src/main/golang/log/log.go b/core/src/main/golang/log/log.go
deleted file mode 100644
index 2384515..0000000
--- a/core/src/main/golang/log/log.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package log
-
-/*
-#cgo LDFLAGS: -llog
-#include
-#include
-#include
-
-#define TAG "ClashForAndroid"
-
-static void log_info(_GoString_ msg) {
- __android_log_print(ANDROID_LOG_INFO, "ClashForAndroid", "%s", _GoStringPtr(msg));
-}
-
-static void log_error(_GoString_ msg) {
- __android_log_print(ANDROID_LOG_ERROR, "ClashForAndroid", "%s", _GoStringPtr(msg));
-}
-
-static void log_warn(_GoString_ msg) {
- __android_log_print(ANDROID_LOG_WARN, "ClashForAndroid", "%s", _GoStringPtr(msg));
-}
-*/
-import "C"
-
-func Info(msg string) {
- C.log_info(msg)
-}
-
-func Error(msg string) {
- C.log_error(msg)
-}
-
-func Warn(msg string) {
- C.log_warn(msg)
-}
diff --git a/core/src/main/golang/main.go b/core/src/main/golang/main.go
index 7905807..2e94b15 100644
--- a/core/src/main/golang/main.go
+++ b/core/src/main/golang/main.go
@@ -1,5 +1,33 @@
package main
-func main() {
+import (
+ "fmt"
+ "os"
+ "os/signal"
+ "syscall"
+ "github.com/Dreamacro/clash/constant"
+
+ "github.com/kr328/clash/profile"
+)
+
+func main() int {
+ if len(os.Args) != 1 {
+ fmt.Println("Invalid argument")
+ return -1
+ }
+
+ if cwd, err := os.Getwd(); err == nil {
+ constant.SetHomeDir(cwd)
+ } else {
+ return -1
+ }
+
+ profile.LoadDefault()
+
+ sigCh := make(chan os.Signal, 1)
+ signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
+ <-sigCh
+
+ return 0
}
diff --git a/core/src/main/golang/profile/load.go b/core/src/main/golang/profile/load.go
index 8034292..2084919 100644
--- a/core/src/main/golang/profile/load.go
+++ b/core/src/main/golang/profile/load.go
@@ -3,7 +3,9 @@ package profile
import (
"net"
+ adapters "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/component/auth"
+ trie "github.com/Dreamacro/clash/component/domain-trie"
"github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
@@ -12,6 +14,7 @@ import (
"github.com/Dreamacro/clash/tunnel"
)
+// LoadDefault - load default configure
func LoadDefault() {
defaultC := &config.Config{
General: &config.General{
@@ -43,21 +46,28 @@ func LoadDefault() {
Experimental: &config.Experimental{
IgnoreResolveFail: false,
},
- Hosts: nil,
+ Hosts: trie.New(),
Rules: []constant.Rule{},
Users: []auth.AuthUser{},
Proxies: map[string]constant.Proxy{},
}
+ global, _ := adapters.NewSelector("GLOBAL", make([]constant.Proxy, 0))
+
+ defaultC.Proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect())
+ defaultC.Proxies["REJECT"] = adapters.NewProxy(adapters.NewReject())
+ defaultC.Proxies["GLOBAL"] = adapters.NewProxy(global)
+
executor.ApplyConfig(defaultC, true)
}
+// LoadFromFile - load file
func LoadFromFile(path string) error {
- cfg, err := config.Parse(path)
+ cfg, err := executor.ParseWithPath(path)
if err != nil {
return err
}
-
+
executor.ApplyConfig(cfg, true)
return nil
}
diff --git a/core/src/main/golang/server/server.go b/core/src/main/golang/server/server.go
new file mode 100644
index 0000000..e008672
--- /dev/null
+++ b/core/src/main/golang/server/server.go
@@ -0,0 +1,61 @@
+package server
+
+import (
+ "encoding/binary"
+ "net"
+
+ "github.com/Dreamacro/clash/log"
+)
+
+const (
+ commandTunStart = 1
+ commandTunStop = 2
+)
+
+var handlers map[int]func(*net.UnixConn) = make(map[int]func(*net.UnixConn))
+
+func init() {
+ handlers[commandTunStart] = handleTunStart // tun.go
+ handlers[commandTunStop] = handleTunStop // tun.go
+}
+
+// Start local control server
+func Start(path string) error {
+ address, _ := net.ResolveUnixAddr("unix", path)
+
+ listener, err := net.ListenUnix("unix", address)
+ if err != nil {
+ return err
+ }
+
+ go func() {
+ for {
+ client, err := listener.AcceptUnix()
+ if err != nil {
+ log.Fatalln("Unix socket accept failure: %s", err.Error())
+ listener.Close()
+ client.Close()
+ return
+ }
+
+ go handleConnection(client)
+ }
+ }()
+
+ return nil
+}
+
+func handleConnection(client *net.UnixConn) {
+ var command int32
+
+ if err := binary.Read(client, binary.BigEndian, &command); err != nil {
+ log.Warnln("Unix read command failure %s", err.Error())
+ }
+
+ handler := handlers[int(command)]
+ if handler == nil {
+ log.Warnln("Invalid command failure %d", command)
+ }
+
+ handler(client)
+}
diff --git a/core/src/main/golang/server/tun.go b/core/src/main/golang/server/tun.go
new file mode 100644
index 0000000..9bbd46f
--- /dev/null
+++ b/core/src/main/golang/server/tun.go
@@ -0,0 +1,36 @@
+package server
+
+import (
+ "net"
+
+ "golang.org/x/sys/unix"
+
+ "github.com/Dreamacro/clash/log"
+
+ "github.com/kr328/clash/tun"
+)
+
+func handleTunStart(client *net.UnixConn) {
+ buffer, err := readCommandPackage(client)
+ if err != nil {
+ log.Warnln("Read packet from unix socket failure %s", err.Error())
+ return
+ }
+
+ msg, err := unix.ParseSocketControlMessage(buffer)
+ if err != nil || len(msg) != 1 {
+ log.Warnln("Parse control package failure %s", err.Error())
+ return
+ }
+
+ fds, err := unix.ParseUnixRights(&msg[0])
+ if err != nil {
+ log.Warnln("Parse control package failure %s", err.Error())
+ }
+
+ tun.NewTunProxy(fds[0])
+}
+
+func handleTunStop(client *net.UnixConn) {
+
+}
diff --git a/core/src/main/golang/server/utils.go b/core/src/main/golang/server/utils.go
new file mode 100644
index 0000000..62a48a8
--- /dev/null
+++ b/core/src/main/golang/server/utils.go
@@ -0,0 +1,23 @@
+package server
+
+import (
+ "encoding/binary"
+ "io"
+ "net"
+)
+
+func readCommandPackage(client *net.UnixConn) ([]byte, error) {
+ var packageSize int32
+
+ if err := binary.Read(client, binary.BigEndian, &packageSize); err != nil {
+ return nil, err
+ }
+
+ buffer := make([]byte, packageSize)
+
+ if readSize, err := io.ReadFull(client, buffer); err != nil || int32(readSize) != packageSize {
+ return nil, err
+ }
+
+ return buffer, nil
+}
diff --git a/core/src/main/golang/tun/dev.go b/core/src/main/golang/tun/dev.go
new file mode 100644
index 0000000..dc97232
--- /dev/null
+++ b/core/src/main/golang/tun/dev.go
@@ -0,0 +1,66 @@
+package tun
+
+import (
+ "errors"
+ "syscall"
+ "unsafe"
+
+ "github.com/google/netstack/tcpip/link/fdbased"
+ "github.com/google/netstack/tcpip/stack"
+
+ "github.com/Dreamacro/clash/log"
+)
+
+type TunDevice struct {
+ mtu int
+ fd int
+ linkCache *stack.LinkEndpoint
+}
+
+func OpenTunDevice(fd int, mtu int) (*TunDevice, error) {
+ var ifr struct {
+ name [16]byte
+ flags uint16
+ _ [22]byte
+ }
+
+ _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TUNGETIFF, uintptr(unsafe.Pointer(&ifr)))
+ if errno != 0 {
+ return nil, errno
+ }
+
+ if ifr.flags&syscall.IFF_TUN == 0 || ifr.flags&syscall.IFF_NO_PI == 0 {
+ return nil, errors.New("Only tun device and no pi mode supported")
+ }
+
+ log.Infoln("Open tun fd = %d mtu = %d", fd, mtu)
+
+ return &TunDevice{
+ fd: fd,
+ mtu: mtu,
+ }, nil
+}
+
+func (t TunDevice) AsLinkEndpoint() (result stack.LinkEndpoint, err error) {
+ if t.linkCache != nil {
+ return *t.linkCache, nil
+ }
+
+ if err != nil {
+ return nil, errors.New("Unable to get device mtu")
+ }
+
+ result, err = fdbased.New(&fdbased.Options{
+ FDs: []int{t.fd},
+ MTU: uint32(t.mtu),
+ EthernetHeader: false,
+ })
+
+ t.linkCache = &result
+
+ return result, nil
+}
+
+func (t TunDevice) Close() {
+ syscall.Close(t.fd)
+}
diff --git a/core/src/main/golang/tun/tun.go b/core/src/main/golang/tun/tun.go
new file mode 100644
index 0000000..34dc605
--- /dev/null
+++ b/core/src/main/golang/tun/tun.go
@@ -0,0 +1,136 @@
+// Package tun from https://github.com/comzyh/clash/tree/add-water/proxy/tun
+package tun
+
+import (
+ "fmt"
+ "net"
+ "strings"
+
+ adapters "github.com/Dreamacro/clash/adapters/inbound"
+ "github.com/Dreamacro/clash/component/socks5"
+ C "github.com/Dreamacro/clash/constant"
+ "github.com/Dreamacro/clash/log"
+ "github.com/Dreamacro/clash/tunnel"
+
+ "encoding/binary"
+
+ "github.com/google/netstack/tcpip"
+ "github.com/google/netstack/tcpip/adapters/gonet"
+ "github.com/google/netstack/tcpip/network/ipv4"
+ "github.com/google/netstack/tcpip/network/ipv6"
+ "github.com/google/netstack/tcpip/stack"
+ "github.com/google/netstack/tcpip/transport/tcp"
+ "github.com/google/netstack/tcpip/transport/udp"
+ "github.com/google/netstack/waiter"
+)
+
+var (
+ tun = tunnel.Instance()
+ ipstack *stack.Stack
+)
+
+// TunAdapter tun device handler
+type TunAdapter struct {
+ device *TunDevice
+ ipstack *stack.Stack
+}
+
+// NewTunProxy create TunProxy under Linux OS.
+func NewTunProxy(fd int, mtu int) (*TunAdapter, error) {
+ tundev, err := OpenTunDevice(fd, mtu)
+ if err != nil {
+ return nil, fmt.Errorf("Can't open tun: %v", err)
+ }
+
+ ipstack = stack.New(stack.Options{
+ NetworkProtocols: []stack.NetworkProtocol{ipv4.NewProtocol(), ipv6.NewProtocol()},
+ TransportProtocols: []stack.TransportProtocol{tcp.NewProtocol(), udp.NewProtocol()},
+ })
+
+ linkEP, err := tundev.AsLinkEndpoint()
+ if err != nil {
+ return nil, fmt.Errorf("Unable to create virtual endpoint: %v", err)
+ }
+
+ if err := ipstack.CreateNIC(1, linkEP); err != nil {
+ return nil, fmt.Errorf("Fail to create NIC in ipstack: %v", err)
+ }
+
+ // IPv4 0.0.0.0/0
+ subnet, _ := tcpip.NewSubnet(tcpip.Address(strings.Repeat("\x00", 4)), tcpip.AddressMask(strings.Repeat("\x00", 4)))
+ ipstack.AddAddressRange(1, ipv4.ProtocolNumber, subnet)
+
+ // IPv6 [::]/0
+ subnet, _ = tcpip.NewSubnet(tcpip.Address(strings.Repeat("\x00", 16)), tcpip.AddressMask(strings.Repeat("\x00", 16)))
+ ipstack.AddAddressRange(1, ipv4.ProtocolNumber, subnet)
+
+ // TCP handler
+ tcpFwd := tcp.NewForwarder(ipstack, 0, 16, func(r *tcp.ForwarderRequest) {
+ var wq waiter.Queue
+ ep, err := r.CreateEndpoint(&wq)
+ if err != nil {
+ log.Warnln("Can't create TCP Endpoint in ipstack: %v", err)
+ return
+ }
+ r.Complete(false)
+
+ conn := gonet.NewConn(&wq, ep)
+ target := getAddr(ep.Info().(*tcp.EndpointInfo).ID)
+ tun.Add(adapters.NewSocket(target, conn, C.REDIR, C.TCP))
+
+ })
+ ipstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpFwd.HandlePacket)
+
+ // UDP handler
+ udpFwd := udp.NewForwarder(ipstack, func(r *udp.ForwarderRequest) {
+ var wq waiter.Queue
+ ep, err := r.CreateEndpoint(&wq)
+ if err != nil {
+ log.Warnln("Can't create UDP Endpoint in ipstack: %v", err)
+ }
+
+ conn := gonet.NewConn(&wq, ep)
+ target := getAddr(ep.Info().(*stack.TransportEndpointInfo).ID)
+ tun.Add(adapters.NewSocket(target, conn, C.REDIR, C.UDP))
+
+ })
+ ipstack.SetTransportProtocolHandler(udp.ProtocolNumber, udpFwd.HandlePacket)
+
+ tl := &TunAdapter{
+ device: tundev,
+ ipstack: ipstack,
+ }
+
+ android.Info("Tun device opened")
+ log.Infoln("Tun device opened")
+
+ return tl, nil
+}
+
+// Close close the TunAdapter
+func (t *TunAdapter) Close() {
+ t.device.Close()
+ ipstack.Close()
+}
+
+func getAddr(id stack.TransportEndpointID) socks5.Addr {
+ ipv4 := id.LocalAddress.To4()
+
+ // get the big-endian binary represent of port
+ port := make([]byte, 2)
+ binary.BigEndian.PutUint16(port, id.LocalPort)
+
+ if ipv4 != "" {
+ addr := make([]byte, 1+net.IPv4len+2)
+ addr[0] = socks5.AtypIPv4
+ copy(addr[1:1+net.IPv4len], []byte(ipv4))
+ addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]
+ return addr
+ } else {
+ addr := make([]byte, 1+net.IPv6len+2)
+ addr[0] = socks5.AtypIPv6
+ copy(addr[1:1+net.IPv6len], []byte(id.LocalAddress))
+ addr[1+net.IPv6len], addr[1+net.IPv6len+1] = port[0], port[1]
+ return addr
+ }
+}
diff --git a/core/src/main/java/com/github/kr328/clash/core/Clash.java b/core/src/main/java/com/github/kr328/clash/core/Clash.java
index 08f6b8b..4566d28 100644
--- a/core/src/main/java/com/github/kr328/clash/core/Clash.java
+++ b/core/src/main/java/com/github/kr328/clash/core/Clash.java
@@ -1,11 +1,14 @@
package com.github.kr328.clash.core;
public final class Clash {
- public static native void init(String home);
+ public static native void init(String clashLibrary, String home);
public static native void loadDefault();
public static native void loadProfileFromPath(String path) throws ClashException;
+ public static native void startTun(int fd, int mtu) throws ClashException;
+ public static native void stopTun() throws ClashException;
static {
System.loadLibrary("clash");
+ System.loadLibrary("bridge");
}
}
diff --git a/core/src/main/java/com/github/kr328/clash/core/ClashException.java b/core/src/main/java/com/github/kr328/clash/core/ClashException.java
index 3dda390..c66742a 100644
--- a/core/src/main/java/com/github/kr328/clash/core/ClashException.java
+++ b/core/src/main/java/com/github/kr328/clash/core/ClashException.java
@@ -1,6 +1,6 @@
package com.github.kr328.clash.core;
-public class ClashException extends Exception {
+public class ClashException extends RuntimeException {
public ClashException(String message) {
super(message);
}