new impl for clash core
This commit is contained in:
21 files changed
+494
-177
No files matched your search
@@ -20,6 +20,19 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service android:name=".ClashService" android:exported="false" android:process=":clash" />
|
||||
|
||||
<service
|
||||
android:name=".ClashService"
|
||||
android:exported="false"
|
||||
android:process=":clash" />
|
||||
<service
|
||||
android:name=".TunService"
|
||||
android:exported="false"
|
||||
android:process=":clash"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -3,4 +3,5 @@ package com.github.kr328.clash;
|
||||
|
||||
interface IClashService {
|
||||
void loadProfile(String path);
|
||||
void startTunDevice(int fd, int mtu);
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
+2
-2
@@ -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()
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#ifndef JNI_COMPILE
|
||||
#define JNI_COMPILE
|
||||
#include <jni.h>
|
||||
#include <string.h>
|
||||
|
||||
#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)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#ifndef JNI_COMPILE
|
||||
#define JNI_COMPILE
|
||||
#include <jni.h>
|
||||
#include <string.h>
|
||||
|
||||
#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())
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package log
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -llog
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user