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); }