refactor clash bridge

This commit is contained in:
Kr328 committed 2020-06-10 21:24:38 +08:00
1 parent 123bf65c55
commit d24496a01e
36 files changed
+1051 -561

No files matched your search

+2 -3
View File
@@ -94,8 +94,7 @@ fun generateGolangBuildEnvironment(abi: String): Map<String, String> {
"GOOS" to "android",
"GOARCH" to golangArch,
"CGO_ENABLED" to "1",
"LDFLAGS" to "-lpthread",
"CFLAGS" to "-O3"
"CFLAGS" to "-O3 -Werror"
)
}
@@ -128,7 +127,7 @@ fun String.exec(pwd: File = buildDir, env: Map<String, String> = System.getenv()
task("compileClashCore") {
onlyIf {
val sourceModified = golangSource.walk()
.filter { it.extension == "go" || it.name == "go.mod" }
.filter { it.extension == "go" || it.extension == "c" || it.extension == "h" || it.name == "go.mod" }
.map { it.lastModified() }
.max() ?: Long.MAX_VALUE
val targetModified = golangOutput.walk()
+1 -1
View File
@@ -8,4 +8,4 @@ include_directories(${CLASH_OUTPUT}/${ANDROID_ABI} ${CLASH_SOURCE})
link_directories(${CLASH_OUTPUT}/${CMAKE_ANDROID_ARCH_ABI})
link_libraries(log clash)
add_library(bridge SHARED main.cpp main.h init.cpp query.cpp patch.cpp)
add_library(bridge SHARED main.cpp main.h init.cpp query.cpp patch.cpp tun.cpp defer.cpp)
+175
View File
@@ -0,0 +1,175 @@
#include "main.h"
#include <android/log.h>
struct deferContext {
jobject completableFuture;
};
static void downloadContextSuccess(void *context, void *argument) {
UNUSED(argument);
auto *ctx = reinterpret_cast<deferContext*>(context);
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
Master::runWithContext<void>(env, [&](Master::Context *context) {
context->completeCompletableFuture(ctx->completableFuture, nullptr);
context->removeGlobalReference(ctx->completableFuture);
});
return 0;
});
delete ctx;
}
static void downloadContextFailure(void *context, void *argument) {
auto *ctx = reinterpret_cast<deferContext*>(context);
auto *err = reinterpret_cast<char *>(argument);
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
Master::runWithContext<void>(env, [&](Master::Context *context) {
if (err != nullptr) {
context->completeExceptionallyCompletableFuture(ctx->completableFuture, context->newClashException(err));
} else {
context->completeExceptionallyCompletableFuture(ctx->completableFuture, context->newClashException("Unknown"));
}
context->removeGlobalReference(ctx->completableFuture);
});
return 0;
});
free(err);
delete ctx;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_downloadProfile__ILjava_lang_String_2Ljava_lang_String_2(
JNIEnv *env, jclass clazz, jint fd, jstring base, jstring output) {
UNUSED(clazz);
return Master::runWithContext<jobject>(env, [&](Master::Context *context) -> jobject {
const char *b = context->getString(base);
const char *o = context->getString(output);
auto *ctx = new deferContext();
ctx->completableFuture = context->newGlobalReference(context->newCompletableFuture());
ccall_t onSuccess = {
.context = ctx,
.function = &downloadContextSuccess
};
ccall_t onFailure = {
.context = ctx,
.function = &downloadContextFailure
};
downloadProfileFromFd(fd, b, o, onSuccess, onFailure);
context->releaseString(base, b);
context->releaseString(output, o);
return ctx->completableFuture;
});
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_downloadProfile__Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2(
JNIEnv *env, jclass clazz, jstring url, jstring base, jstring output) {
UNUSED(clazz);
return Master::runWithContext<jobject>(env, [&](Master::Context *context) -> jobject {
const char *u = context->getString(url);
const char *b = context->getString(base);
const char *o = context->getString(output);
auto *ctx = new deferContext();
ctx->completableFuture = context->newGlobalReference(context->newCompletableFuture());
ccall_t onSuccess = {
.context = ctx,
.function = &downloadContextSuccess
};
ccall_t onFailure = {
.context = ctx,
.function = &downloadContextFailure
};
downloadProfileFromUrl(u, b, o, onSuccess, onFailure);
context->releaseString(url, u);
context->releaseString(base, b);
context->releaseString(output, o);
return ctx->completableFuture;
});
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_loadProfile(JNIEnv *env, jclass clazz, jstring path,
jstring base) {
UNUSED(clazz);
return Master::runWithContext<jobject>(env, [&](Master::Context *context) -> jobject {
const char *p = context->getString(path);
const char *b = context->getString(base);
auto *ctx = new deferContext();
ctx->completableFuture = context->newGlobalReference(context->newCompletableFuture());
ccall_t onSuccess = {
.context = ctx,
.function = &downloadContextSuccess
};
ccall_t onFailure = {
.context = ctx,
.function = &downloadContextFailure
};
loadProfile(p, b, onSuccess, onFailure);
context->releaseString(path, p);
context->releaseString(base, b);
return ctx->completableFuture;
});
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_performHealthCheck(JNIEnv *env, jclass clazz,
jstring group) {
UNUSED(clazz);
return Master::runWithContext<jobject>(env, [&](Master::Context *context) -> jobject {
const char *g = context->getString(group);
auto *ctx = new deferContext();
ctx->completableFuture = context->newGlobalReference(context->newCompletableFuture());
ccall_t onSuccess = {
.context = ctx,
.function = &downloadContextSuccess
};
ccall_t onFailure = {
.context = ctx,
.function = &downloadContextFailure
};
performHealthCheck(g, onSuccess, onFailure);
context->releaseString(group, g);
return ctx->completableFuture;
});
}
+133 -13
View File
@@ -2,23 +2,45 @@
Master *Master::master = nullptr;
Master::Master(JNIEnv *env) {
Master::Master(JavaVM *vm, JNIEnv *env): vm(vm) {
master = this;
cClashException = env->FindClass("com/github/kr328/clash/core/bridge/ClashException");
cTraffic = env->FindClass("com/github/kr328/clash/core/model/Traffic");
cGeneral = env->FindClass("com/github/kr328/clash/core/model/General");
cCompletableFuture = env->FindClass("java/util/concurrent/CompletableFuture");
cProxyGroup = env->FindClass("com/github/kr328/clash/core/model/ProxyGroup");
cProxy = env->FindClass("com/github/kr328/clash/core/model/Proxy");
iTunCallback = env->FindClass("com/github/kr328/clash/core/bridge/TunCallback");
cClashExceptionConstructor = env->GetMethodID(cClashException, "<init>",
"(Ljava/lang/String;)V");
cTrafficConstructor = env->GetMethodID(cTraffic, "<init>", "(JJ)V");
cGeneralConstructor = env->GetMethodID(cGeneral, "<init>", "(Ljava/lang/String;IIII)V");
cCompletableFutureConstructor = env->GetMethodID(cCompletableFuture, "<init>", "()V");
cProxyGroupConstructor = env->GetMethodID(cProxyGroup, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Lcom/github/kr328/clash/core/model/Proxy;)V");
cProxyConstructor = env->GetMethodID(cProxy, "<init>", "(Ljava/lang/String;Ljava/lang/String;J)V");
mCompletableFutureComplete = env->GetMethodID(cCompletableFuture, "complete",
"(Ljava/lang/Object;)Z");
mCompletableFutureCompleteExceptionally = env->GetMethodID(cCompletableFuture,
"completeExceptionally",
"(Ljava/lang/Throwable;)Z");
mTunCallbackOnNewSocket = env->GetMethodID(iTunCallback, "onNewSocket", "(I)V");
mTunCallbackOnStop = env->GetMethodID(iTunCallback, "onStop", "()V");
sDirect = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("Direct")));
sReject = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("Reject")));
sShadowsocks = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("Shadowsocks")));
sSnell = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("Snell")));
sSocks5 = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("Socks5")));
sHttp = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("Http")));
sVmess = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("Vmess")));
sTrojan = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("Trojan")));
sRelay = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("Relay")));
sSelector = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("Selector")));
sFallback = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("Fallback")));
sURLTest = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("URLTest")));
sLoadBalance = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("LoadBalance")));
sUnknown = reinterpret_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("Unknown")));
}
Master::Context::Context(JNIEnv *env) {
@@ -69,17 +91,6 @@ Master::Context::completeExceptionallyCompletableFuture(jobject completable, jth
throwable);
}
buffer_t Master::Context::createBufferFromByteArray(jbyteArray array) {
return {
.buffer = env->GetByteArrayElements(array, nullptr),
.length = env->GetArrayLength(array)
};
}
void Master::Context::releaseBufferFromByteArray(jbyteArray array, buffer_t &buffer) {
env->ReleaseByteArrayElements(array, reinterpret_cast<jbyte*>(buffer.buffer), JNI_COMMIT);
}
const_buffer_t Master::Context::createConstBufferFromByteArray(jbyteArray array) {
return {
.buffer = env->GetByteArrayElements(array, nullptr),
@@ -99,6 +110,115 @@ void Master::Context::releaseString(jstring str, const char *c) {
env->ReleaseStringUTFChars(str, c);
}
void Master::Context::tunCallbackNewSocket(jobject callback, int fd) {
env->CallVoidMethod(callback, master->mTunCallbackOnNewSocket, fd);
}
void Master::Context::tunCallbackStop(jobject callback) {
env->CallVoidMethod(callback, master->mTunCallbackOnStop);
}
jobjectArray Master::Context::createProxyGroupArray(int size, jobject elements[]) {
jobjectArray result = env->NewObjectArray(size, master->cProxyGroup, nullptr);
for ( int i = 0 ; i < size ; i++ )
env->SetObjectArrayElement(result, i, elements[i]);
return result;
}
jobjectArray Master::Context::createProxyArray(int size, jobject elements[]) {
jobjectArray result = env->NewObjectArray(size, master->cProxy, nullptr);
for ( int i = 0 ; i < size ; i++ )
env->SetObjectArrayElement(result, i, elements[i]);
return result;
}
jobject Master::Context::createProxy(std::string const &name, proxy_type_t type, long delay) {
jstring ts = nullptr;
switch (type) {
case Direct:
ts = master->sDirect;
break;
case Reject:
ts = master->sReject;
break;
case Socks5:
ts = master->sSocks5;
break;
case Http:
ts = master->sHttp;
break;
case Shadowsocks:
ts = master->sShadowsocks;
break;
case Vmess:
ts = master->sVmess;
break;
case Snell:
ts = master->sSnell;
break;
case Trojan:
ts = master->sTrojan;
break;
case Selector:
ts = master->sSelector;
break;
case Fallback:
ts = master->sFallback;
break;
case LoadBalance:
ts = master->sLoadBalance;
break;
case URLTest:
ts = master->sURLTest;
break;
case Relay:
ts = master->sRelay;
break;
case Unknown:
ts = master->sUnknown;
break;
default:
ts = master->sUnknown;
}
return env->NewObject(master->cProxy, master->cProxyConstructor, env->NewStringUTF(name.c_str()), ts, delay);
}
jobject Master::Context::createProxyGroup(std::string const &name, proxy_type_t type,
std::string const &current, jobjectArray proxies) {
jstring ts = nullptr;
switch (type) {
case Selector:
ts = master->sSelector;
break;
case Fallback:
ts = master->sFallback;
break;
case LoadBalance:
ts = master->sLoadBalance;
break;
case URLTest:
ts = master->sURLTest;
break;
case Relay:
ts = master->sRelay;
break;
case Unknown:
ts = master->sUnknown;
break;
default:
ts = master->sUnknown;
}
return env->NewObject(master->cProxyGroup, master->cProxyGroupConstructor, env->NewStringUTF(name.c_str()), ts, env->NewStringUTF(current.c_str()), proxies);
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *unused) {
UNUSED(unused);
@@ -107,7 +227,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *unused) {
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
return -1;
new Master(env);
new Master(vm, env);
return JNI_VERSION_1_6;
}
+50 -5
View File
@@ -11,23 +11,49 @@ public:
class Context;
public:
Master(JNIEnv *env);
Master(JavaVM *vm, JNIEnv *env);
public:
template <class R>
static R runWithContext(JNIEnv *env, const std::function<R (Context *)>& func);
template <class R> static R runWithContext(JNIEnv *env, const std::function<R (Context *)>& func);
template <class R> static R runWithAttached(const std::function<R (JNIEnv *)> &func);
private:
jclass cClashException;
jclass cTraffic;
jclass cGeneral;
jclass cCompletableFuture;
jclass cProxyGroup;
jclass cProxy;
jclass iTunCallback;
jmethodID cClashExceptionConstructor;
jmethodID cTrafficConstructor;
jmethodID cGeneralConstructor;
jmethodID cCompletableFutureConstructor;
jmethodID cProxyGroupConstructor;
jmethodID cProxyConstructor;
jmethodID mCompletableFutureComplete;
jmethodID mCompletableFutureCompleteExceptionally;
jmethodID mTunCallbackOnNewSocket;
jmethodID mTunCallbackOnStop;
private:
jstring sDirect;
jstring sReject;
jstring sShadowsocks;
jstring sSnell;
jstring sSocks5;
jstring sHttp;
jstring sVmess;
jstring sTrojan;
jstring sRelay;
jstring sSelector;
jstring sFallback;
jstring sURLTest;
jstring sLoadBalance;
jstring sUnknown;
private:
JavaVM *vm;
private:
static Master *master;
@@ -37,6 +63,7 @@ private:
};
class Master::Context {
public:
public:
Context(JNIEnv *env);
@@ -56,10 +83,14 @@ public:
public:
bool completeCompletableFuture(jobject completable, jobject object);
bool completeExceptionallyCompletableFuture(jobject completable, jthrowable throwable);
void tunCallbackNewSocket(jobject callback, int fd);
void tunCallbackStop(jobject callback);
public:
buffer_t createBufferFromByteArray(jbyteArray array);
void releaseBufferFromByteArray(jbyteArray array, buffer_t &buffer);
jobject createProxy(std::string const &name, proxy_type_t type, long delay);
jobject createProxyGroup(std::string const &name, proxy_type_t type, std::string const &current, jobjectArray proxies);
jobjectArray createProxyArray(int size, jobject elements[]);
jobjectArray createProxyGroupArray(int size, jobject elements[]);
const_buffer_t createConstBufferFromByteArray(jbyteArray array);
void releaseConstBufferFromByteArray(jbyteArray array, const_buffer_t &buffer);
const char *getString(jstring str);
@@ -74,4 +105,18 @@ R Master::runWithContext(JNIEnv *env, const std::function<R (Master::Context *)>
Master::Context context(env);
return func(&context);
}
template <class R> R Master::runWithAttached(const std::function<R (JNIEnv *)> &func) {
Master *m = Master::master;
JNIEnv *env;
m->vm->AttachCurrentThread(&env, nullptr);
R result = func(env);
m->vm->DetachCurrentThread();
return result;
}
+66
View File
@@ -1,2 +1,68 @@
#include "main.h"
#include <cstring>
extern "C"
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_setProxyMode(JNIEnv *env, jclass clazz,
jstring proxy_mode) {
Master::runWithContext<void>(env, [&](Master::Context *context) {
const char *m = context->getString(proxy_mode);
int mode;
if ( strcmp(m, "Direct") == 0 )
mode = MODE_DIRECT;
else if ( strcmp(m, "Global") == 0 )
mode = MODE_GLOBAL;
else if ( strcmp(m, "Rule") == 0 )
mode = MODE_RULE;
else if ( strcmp(m, "Script") == 0 )
mode = MODE_SCRIPT;
else
mode = MODE_UNKNOWN;
setProxyMode(mode);
});
}
extern "C"
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_setDnsOverride(JNIEnv *env, jclass clazz,
jboolean override_dns,
jstring append_dns) {
Master::runWithContext<void>(env, [&](Master::Context *context) {
const char *appendDns = context->getString(append_dns);
int override = 1;
if ( override_dns )
override = 1;
else
override = 0;
dns_override_t dns = {
.override_dns = override,
.append_dns = appendDns
};
setDnsOverride(&dns);
context->releaseString(append_dns, appendDns);
});
}
extern "C"
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_setSelector(JNIEnv *env, jclass clazz, jstring group,
jstring selected) {
UNUSED(clazz);
Master::runWithContext<void>(env, [&](Master::Context *context) {
const char *g = context->getString(group);
const char *s = context->getString(selected);
setSelector(g, s);
context->releaseString(group, g);
context->releaseString(selected, s);
});
}
+47
View File
@@ -59,4 +59,51 @@ Java_com_github_kr328_clash_core_bridge_Bridge_querySpeed(JNIEnv *env, jclass cl
return context->newTraffic(traffic.upload, traffic.download);
});
}
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_queryProxyGroups(JNIEnv *env, jclass clazz) {
UNUSED(clazz);
return Master::runWithContext<jobjectArray>(env, [&](Master::Context *context) -> jobjectArray {
proxy_group_list_t *list = queryProxyGroups();
auto *jgroups = new jobject[list->size];
for (int group_index = 0 ; group_index < list->size ; group_index++ ) {
std::string now;
proxy_group_t *group = list->groups[group_index];
auto *jproxies = new jobject[group->proxies_size];
for ( int proxy_index = 0 ; proxy_index < group->proxies_size ; proxy_index++ ) {
proxy_t *proxy = &group->proxies[proxy_index];
jproxies[proxy_index] = context->createProxy(proxy->name, proxy->proxy_type, proxy->delay);
if ( proxy_index == group->now )
now = proxy->name;
free(proxy->name);
}
jgroups[group_index] = context->createProxyGroup(
group->base.name,
group->base.proxy_type,
now,
context->createProxyArray(group->proxies_size, jproxies));
delete[] jproxies;
free(group->base.name);
free(group);
}
jobjectArray result = context->createProxyGroupArray(list->size, jgroups);
delete[] jgroups;
free(list);
return result;
});
}
+83
View File
@@ -0,0 +1,83 @@
#include "main.h"
struct tunContext {
jobject callback;
};
static void tunContextOnNewSocket(void *context, void *argument) {
auto *ctx = reinterpret_cast<tunContext*>(context);
auto fd = reinterpret_cast<long>(argument);
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
Master::runWithContext<void>(env, [&](Master::Context *context) {
context->tunCallbackNewSocket(ctx->callback, fd);
});
return 0;
});
}
static void tunContextOnStop(void *context, void *argument) {
UNUSED(argument);
auto *ctx = reinterpret_cast<tunContext*>(context);
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
Master::runWithContext<void>(env, [&](Master::Context *context) {
context->tunCallbackStop(ctx->callback);
context->removeGlobalReference(ctx->callback);
});
return 0;
});
delete ctx;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_startTunDevice(JNIEnv *env, jclass clazz, jint fd,
jint mtu, jstring gateway,
jstring mirror, jstring dns,
jobject callback) {
UNUSED(clazz);
Master::runWithContext<void>(env, [&](Master::Context *context) {
const char *gatewayString = context->getString(gateway);
const char *mirrorString = context->getString(mirror);
const char *dnsString = context->getString(dns);
jobject callbackGlobal = context->newGlobalReference(callback);
auto *callbackContext = new tunContext();
callbackContext->callback = callbackGlobal;
ccall_t onNewSocket = {
.function = &tunContextOnNewSocket,
.context = callbackContext
};
ccall_t onStop = {
.function = &tunContextOnStop,
.context = callbackContext
};
char *exception = startTunDevice(fd, mtu, gatewayString, mirrorString, dnsString, onNewSocket, onStop);
context->releaseString(gateway, gatewayString);
context->releaseString(mirror, mirrorString);
context->releaseString(dns, dnsString);
if ( exception != nullptr ) {
context->throwThrowable(context->newClashException(exception));
context->removeGlobalReference(callbackContext->callback);
free(exception);
delete callbackContext;
}
});
}
extern "C"
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_stopTunDevice(JNIEnv *env, jclass clazz) {
stopTunDevice();
}
-6
View File
@@ -1,6 +0,0 @@
package bridge
type DoneCallback interface {
Done()
DoneWithError(error)
}
-38
View File
@@ -1,38 +0,0 @@
package bridge
import (
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/tunnel"
)
type TunnelGeneral struct {
Mode string
HTTPPort int
SocksPort int
RedirectPort int
}
func QueryGeneral() *TunnelGeneral {
result := &TunnelGeneral{}
g := executor.GetGeneral()
m := tunnel.Mode()
result.Mode = m.String()
result.HTTPPort = g.Port
result.SocksPort = g.SocksPort
result.RedirectPort = g.RedirPort
return result
}
func SetProxyMode(mode string) {
switch mode {
case "Direct":
tunnel.SetMode(tunnel.Direct)
case "Global":
tunnel.SetMode(tunnel.Global)
case "Rule":
tunnel.SetMode(tunnel.Rule)
}
}
-61
View File
@@ -1,61 +0,0 @@
package bridge
import (
"github.com/Dreamacro/clash/component/mmdb"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
"github.com/kr328/cfa/config"
"sync"
)
var (
logCallback LogCallback
logSubscribe sync.Once
)
type LogCallback interface {
OnLogEvent(level, payload string)
}
func InitCore(geoipDatabase[] byte, homeDir string, version string) {
dataClone := make([]byte, len(geoipDatabase))
copy(dataClone, geoipDatabase)
mmdb.LoadFromBytes(dataClone)
C.SetHomeDir(homeDir)
config.ApplicationVersion = version
Reset()
log.Infoln("Initialed")
}
func Reset() {
config.LoadDefault()
tunnel.DefaultManager.ResetStatistic()
}
func SetLogCallback(callback LogCallback) {
logSubscribe.Do(func() {
go func() {
sub := log.Subscribe()
defer log.UnSubscribe(sub)
for {
elm := <-sub
l := elm.(*log.Event)
if l.LogLevel < log.Level() {
continue
}
if cb := logCallback; cb != nil {
cb.OnLogEvent(l.LogLevel.String(), l.Payload)
}
}
}()
})
logCallback = callback
}
-49
View File
@@ -1,49 +0,0 @@
package bridge
import (
"strings"
"github.com/kr328/cfa/config"
)
func ResetDnsAppend(dns string) {
if len(dns) == 0 {
config.NameServersAppend = make([]string, 0)
} else {
config.NameServersAppend = strings.Split(dns, ",")
}
}
func SetDnsOverrideEnabled(enabled bool) {
if enabled {
config.DnsPatch = config.OptionalDnsPatch
} else {
config.DnsPatch = nil
}
}
func LoadProfileFile(path, baseDir string, callback DoneCallback) {
go func() {
call(config.LoadFromFile(path, baseDir), callback)
}()
}
func DownloadProfileAndCheck(url, output, baseDir string, callback DoneCallback) {
go func() {
call(config.PullRemote(url, output, baseDir), callback)
}()
}
func ReadProfileAndCheck(fd int, output, baseDir string, callback DoneCallback) {
go func() {
call(config.PullLocal(fd, output, baseDir), callback)
}()
}
func call(err error, callback DoneCallback) {
if err != nil {
callback.DoneWithError(err)
} else {
callback.Done()
}
}
-148
View File
@@ -1,148 +0,0 @@
package bridge
import (
"sync"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/outboundgroup"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
)
type ProxyItem struct {
Name string
Type string
Delay int
}
type ProxyGroupItem struct {
Name string
Type string
Current string
Delay int
providers []provider.ProxyProvider
}
type ProxyGroupCollection interface {
Add(proxy *ProxyGroupItem) bool
}
type ProxyCollection interface {
Add(proxy *ProxyItem) bool
}
func (p *ProxyGroupItem) QueryAllProxies(collection ProxyCollection) {
for _, v := range p.providers {
for _, p := range v.Proxies() {
collection.Add(
&ProxyItem{
Name: p.Name(),
Type: p.Type().String(),
Delay: int(p.LastDelay()),
},
)
}
}
}
func StartUrlTest(group string, callback DoneCallback) {
go func() {
defer callback.Done()
p := tunnel.Proxies()[group]
pi, ok := p.(*outbound.Proxy)
if !ok {
return
}
group, ok := pi.ProxyAdapter.(outboundgroup.ProxyGroup)
if !ok {
return
}
providers := group.GetProxyProviders()
wg := &sync.WaitGroup{}
wg.Add(len(providers))
for _, v := range providers {
go func(p provider.ProxyProvider) {
p.HealthCheck()
wg.Done()
}(v)
}
wg.Wait()
}()
}
func QueryAllProxyGroups(collection ProxyGroupCollection) {
ps := tunnel.Proxies()
for _, p := range ps {
pi, ok := p.(*outbound.Proxy)
if !ok {
continue
}
group, ok := pi.ProxyAdapter.(outboundgroup.ProxyGroup)
if !ok {
continue
}
collection.Add(&ProxyGroupItem{
Name: group.Name(),
Type: group.Type().String(),
Current: group.Now(),
Delay: 0,
providers: group.GetProxyProviders(),
})
}
}
func SetSelectedProxy(name, proxy string) bool {
p := tunnel.Proxies()[name]
if p == nil {
log.Infoln("Set %s: Not such proxy group", name)
return false
}
pb, ok := p.(*outbound.Proxy)
if !ok {
log.Infoln("Set %s: Not a proxy object", name)
return false
}
selector, ok := pb.ProxyAdapter.(*outboundgroup.Selector)
if !ok {
log.Infoln("Set %s: Not a selector group", name)
return false
}
selected := selector.Now()
if selected == proxy {
log.Infoln("Set " + name + " -> " + proxy)
return true
}
if err := selector.Set(proxy); err != nil {
log.Infoln("Set %s: %s", name, err.Error())
return false
}
for _, conn := range tunnel.DefaultManager.Snapshot().Connections {
for _, p := range conn.Chain() {
if p == name {
_ = conn.Close()
break
}
}
}
log.Infoln("Set " + name + " -> " + proxy)
return true
}
-47
View File
@@ -1,47 +0,0 @@
package bridge
import (
"sync"
"github.com/Dreamacro/clash/tunnel"
)
type EventPoll struct {
stop sync.Once
onStop func()
}
func (e *EventPoll) Stop() {
e.stop.Do(func() {
e.onStop()
})
}
type Traffic struct {
Download int64
Upload int64
}
type Logs interface {
OnEvent(level, payload string)
}
func QueryBandwidth() *Traffic {
upload := tunnel.DefaultManager.UploadTotal()
download := tunnel.DefaultManager.DownloadTotal()
return &Traffic{
Upload: upload,
Download: download,
}
}
func QueryTraffic() *Traffic {
up, down := tunnel.DefaultManager.Now()
return &Traffic{
Upload: up,
Download: down,
}
}
-57
View File
@@ -1,57 +0,0 @@
package bridge
import (
"net"
"syscall"
"github.com/Dreamacro/clash/component/dialer"
"github.com/kr328/cfa/tun"
)
type TunCallback interface {
OnCreateSocket(fd int)
OnStop()
}
var callback TunCallback
func init() {
dialer.DialerHook = onNewDialer
dialer.ListenConfigHook = onNewListenConfig
}
func onNewDialer(dialer *net.Dialer) error {
dialer.Control = onNewSocket
return nil
}
func onNewListenConfig(listen *net.ListenConfig) error {
listen.Control = onNewSocket
return nil
}
func onNewSocket(_, _ string, c syscall.RawConn) error {
if cb := callback; cb != nil {
_ = c.Control(func(fd uintptr) {
cb.OnCreateSocket(int(fd))
})
}
return nil
}
func StartTunDevice(fd, mtu int, gateway, mirror, dns string, cb TunCallback) error {
callback = cb
return tun.StartTunDevice(fd, mtu, gateway, mirror, dns)
}
func StopTunDevice() {
tun.StopTunDevice()
if c := callback; c != nil {
c.OnStop()
}
callback = nil
}
+11 -7
View File
@@ -2,8 +2,9 @@
#include <pthread.h>
#include <stdlib.h>
#include <stdint.h>
extern void nextCcall(ccall_t *call, void **argument);
extern void nextCcall(ccall_t *call, void **argument, uint64_t *index);
extern void finishCcall();
static void *run(void *arg) {
@@ -12,20 +13,23 @@ static void *run(void *arg) {
while ( 1 ) {
ccall_t call;
void *argument;
uint64_t index;
nextCcall(&call, &argument);
nextCcall(&call, &argument, &index);
call.function(call.context, argument);
finishCcall();
finishCcall(index);
}
return NULL;
}
void initialize_ccall() {
pthread_t tid;
void initialize_ccall(int pool_size) {
for ( int i = 0 ; i < pool_size ; i++ ) {
pthread_t tid = 0;
if ( pthread_create(&tid, NULL, &run, NULL) < 0 )
abort();
if ( pthread_create(&tid, NULL, &run, NULL) < 0 )
abort();
}
}
+20 -14
View File
@@ -3,10 +3,15 @@ package main
/*
#include "ccall.h"
extern void initialize_ccall();
extern void initialize_ccall(int pool_size);
*/
import "C"
import "unsafe"
import (
"runtime"
"sync"
"sync/atomic"
"unsafe"
)
type nativeCcall C.ccall_t
@@ -17,10 +22,11 @@ type ccallContext struct {
}
var ccallQueue chan *ccallContext
var current *ccallContext
var ccallMap sync.Map
var currentIndex uint64
func init() {
C.initialize_ccall()
C.initialize_ccall(C.int(runtime.NumCPU()))
}
func callCcall(call nativeCcall, argument unsafe.Pointer) {
@@ -36,22 +42,22 @@ func callCcall(call nativeCcall, argument unsafe.Pointer) {
}
//export nextCcall
func nextCcall(ccall *nativeCcall, argument *unsafe.Pointer) {
func nextCcall(ccall *nativeCcall, argument *unsafe.Pointer, index *uint64) {
ctx := <- ccallQueue
current = ctx
*index = atomic.AddUint64(&currentIndex, 1)
*ccall = ctx.call
*argument = ctx.argument
ccallMap.Store(*index, ctx)
}
//export finishCcall
func finishCcall() {
ctx := current
current = nil
if ctx != nil {
ctx.result <- struct{}{}
func finishCcall(index uint64) {
ctx, ok := ccallMap.Load(index)
if !ok {
return
}
ctx.(*ccallContext).result <- struct{}{}
}
+6 -5
View File
@@ -1,6 +1,7 @@
package main
//#include "config.h"
//#include "buffer.h"
import "C"
import (
@@ -10,18 +11,18 @@ import (
//export setDnsOverride
func setDnsOverride(override *C.dns_override_t) {
dnsOverride := override.override_dns != 0
appendNameservers := C.GoString(override.append_nameservers)
overrideDns := override.override_dns != 0
appendDns := C.GoString(override.append_dns)
if dnsOverride {
if overrideDns {
config.DnsPatch = config.OptionalDnsPatch
} else {
config.DnsPatch = nil
}
if len(appendNameservers) == 0 {
if len(appendDns) == 0 {
config.NameServersAppend = make([]string, 0)
} else {
config.NameServersAppend = strings.Split(appendNameservers, ",")
config.NameServersAppend = strings.Split(appendDns, ",")
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ extern "C" {
typedef struct dns_override_t {
int override_dns;
const char *append_nameservers;
const char *append_dns;
} dns_override_t;
#if __cplusplus
+2 -2
View File
@@ -78,7 +78,7 @@ func fetchLocal(fd int) ([]byte, error) {
return ioutil.ReadAll(file)
}
func PullRemote(url, output, baseDir string) error {
func DownloadUrl(url, output, baseDir string) error {
data, err := fetchRemote(url)
if err != nil {
return err
@@ -87,7 +87,7 @@ func PullRemote(url, output, baseDir string) error {
return save(data, output, baseDir)
}
func PullLocal(fd int, output, baseDir string) error {
func DownloadFd(fd int, output, baseDir string) error {
data, err := fetchLocal(fd)
if err != nil {
return err
+112
View File
@@ -0,0 +1,112 @@
package main
import (
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/outboundgroup"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
"github.com/kr328/cfa/config"
"sync"
"unsafe"
)
//#include "buffer.h"
import "C"
//export downloadProfileFromFd
func downloadProfileFromFd(fd int, base C.const_string_t, output C.const_string_t, onSuccess nativeCcall, onFailure nativeCcall) {
b := C.GoString(base)
o := C.GoString(output)
go func() {
err := config.DownloadFd(fd, o, b)
if err != nil {
callCcall(onFailure, unsafe.Pointer(C.CString(err.Error())))
} else {
callCcall(onSuccess, nil)
}
}()
}
//export downloadProfileFromUrl
func downloadProfileFromUrl(url C.const_string_t, base C.const_string_t, output C.const_string_t, onSuccess nativeCcall, onFailure nativeCcall) {
u := C.GoString(url)
b := C.GoString(base)
o := C.GoString(output)
go func() {
err := config.DownloadUrl(u, o, b)
if err != nil {
callCcall(onFailure, unsafe.Pointer(C.CString(err.Error())))
} else {
callCcall(onSuccess, nil)
}
}()
}
//export loadProfile
func loadProfile(path C.const_string_t, base C.const_string_t, onSuccess nativeCcall, onFailure nativeCcall) {
p := C.GoString(path)
b := C.GoString(base)
go func() {
err := config.LoadFromFile(p, b)
if err != nil {
callCcall(onFailure, unsafe.Pointer(C.CString(err.Error())))
} else {
callCcall(onSuccess, nil)
}
}()
}
//export performHealthCheck
func performHealthCheck(group C.const_string_t, onSuccess nativeCcall, onFailure nativeCcall) {
g := C.GoString(group)
go func() {
p := tunnel.Proxies()[g]
if p == nil {
callCcall(onFailure, unsafe.Pointer(C.CString("No such proxy group")))
log.Warnln("Perform health check failure: %s not found", g)
return
}
pw, ok := p.(*outbound.Proxy)
if !ok {
callCcall(onFailure, unsafe.Pointer(C.CString("Invalid group")))
log.Warnln("Perform health check failure: %s not valid group", g)
return
}
adapter, ok := pw.ProxyAdapter.(outboundgroup.ProxyGroup)
if !ok {
callCcall(onFailure, unsafe.Pointer(C.CString("Invalid group")))
log.Warnln("Perform health check failure: %s not valid group", g)
return
}
providers := adapter.GetProxyProviders()
wg := &sync.WaitGroup{}
wg.Add(len(providers))
for _, p := range providers {
go func(p provider.ProxyProvider) {
p.HealthCheck()
wg.Done()
}(p)
}
wg.Wait()
callCcall(onSuccess, nil)
}()
}
+1
View File
@@ -4,6 +4,7 @@
extern "C" {
#endif
static const int MODE_UNKNOWN = -1;
static const int MODE_DIRECT = 0;
static const int MODE_GLOBAL = 1;
static const int MODE_RULE = 2;
+24
View File
@@ -0,0 +1,24 @@
#include <android/log.h>
#define TAG "ClashForAndroid"
void log_info(const char *msg) {
__android_log_write(ANDROID_LOG_INFO, TAG, msg);
}
void log_error(const char *msg) {
__android_log_write(ANDROID_LOG_ERROR, TAG, msg);
}
void log_warn(const char *msg) {
__android_log_write(ANDROID_LOG_WARN, TAG, msg);
}
void log_debug(const char *msg) {
__android_log_write(ANDROID_LOG_DEBUG, TAG, msg);
}
void log_verbose(const char *msg) {
__android_log_write(ANDROID_LOG_VERBOSE, TAG, msg);
}
+52 -1
View File
@@ -3,15 +3,66 @@ package main
import (
"github.com/Dreamacro/clash/component/mmdb"
"github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
"github.com/kr328/cfa/config"
"unsafe"
)
//#include "buffer.h"
/*
#cgo CFLAGS: -O3
#cgo LDFLAGS: -llog
#include "buffer.h"
#include "malloc.h"
extern void log_info(const char *msg);
extern void log_error(const char *msg);
extern void log_warn(const char *msg);
extern void log_debug(const char *msg);
extern void log_verbose(const char *msg);
*/
import "C"
func main() {}
func init() {
r := make(chan struct{})
go func() {
sub := log.Subscribe()
defer log.UnSubscribe(sub)
for item := range sub {
msg := item.(*log.Event)
if msg.LogLevel < log.Level() {
continue
}
cPayload := C.CString(msg.Payload)
switch msg.LogLevel {
case log.INFO:
C.log_info(cPayload)
case log.ERROR:
C.log_error(cPayload)
case log.WARNING:
C.log_warn(cPayload)
case log.DEBUG:
C.log_debug(cPayload)
case log.SILENT:
C.log_verbose(cPayload)
}
C.free(unsafe.Pointer(cPayload))
}
r <- struct{}{}
}()
<- r
}
//export initialize
func initialize(database *C.const_buffer_t, home, version C.const_string_t) {
databaseData := C.GoBytes(database.buffer, database.length)
+176
View File
@@ -0,0 +1,176 @@
package main
import (
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/outboundgroup"
"github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
)
/*
#cgo CFLAGS: -Werror
#include "buffer.h"
#include "proxies.h"
#include <malloc.h>
static proxy_group_list_t *new_proxy_group_list_t(int size) {
proxy_group_list_t *result = malloc(sizeof(proxy_group_list_t) + size * sizeof(proxy_group_t*));
result->size = size;
return result;
}
static proxy_group_t *new_proxy_group_t(int proxies_size) {
proxy_group_t *result = malloc(sizeof(proxy_group_t) + proxies_size * sizeof(proxy_t));
result->proxies_size = proxies_size;
return result;
}
static proxy_t *proxy_group_get_proxy(proxy_group_t *group, int index) {
return &group->proxies[index];
}
static void proxy_group_list_set(proxy_group_list_t *list, int index, proxy_group_t *group) {
list->groups[index] = group;
}
*/
import "C"
//export setSelector
func setSelector(group C.const_string_t, selected C.const_string_t) {
g := C.GoString(group)
s := C.GoString(selected)
p := tunnel.Proxies()[g]
if p == nil {
log.Warnln("Set selector failure: %s not found", g)
return
}
pw, ok := p.(*outbound.Proxy)
if !ok {
log.Warnln("Set selector failure: %s not valid group", g)
return
}
adapter, ok := pw.ProxyAdapter.(outboundgroup.ProxyGroup)
if !ok {
log.Warnln("Set selector failure: %s not valid group", g)
return
}
selector, ok := adapter.(*outboundgroup.Selector)
if !ok {
log.Warnln("Set selector failure: %s not selector", g)
return
}
if err := selector.Set(s); err != nil {
log.Warnln("Set selector failure: %s not in %s", s, g)
return
}
log.Infoln("Set %s -> %s", g, s)
}
//export queryProxyGroups
func queryProxyGroups() *C.proxy_group_list_t {
proxies := tunnel.Proxies()
groups := make([]outboundgroup.ProxyGroup, 0, len(proxies))
for _, p := range proxies {
pw, ok := p.(*outbound.Proxy)
if !ok {
continue
}
adapter, ok := pw.ProxyAdapter.(outboundgroup.ProxyGroup)
if !ok {
continue
}
groups = append(groups, adapter)
}
result := C.new_proxy_group_list_t(C.int(len(groups)))
groupIndex := 0
for _, group := range groups {
ps := make([]constant.Proxy, 0)
for _, provider := range group.GetProxyProviders() {
ps = append(ps, provider.Proxies()...)
}
g := C.new_proxy_group_t(C.int(len(proxies)))
g.base.name = C.CString(group.Name())
g.base.proxy_type = typeToProxyTypeC(group.Type())
g.base.delay = 0
proxyIndex := 0
for _, proxy := range proxies {
p := C.proxy_group_get_proxy(g, C.int(proxyIndex))
p.name = C.CString(proxy.Name())
p.proxy_type = typeToProxyTypeC(proxy.Type())
p.delay = C.long(proxy.LastDelay())
if proxy.Name() == group.Now() {
g.now = C.int(proxyIndex)
}
proxyIndex++
}
C.proxy_group_list_set(result, C.int(groupIndex), g)
groupIndex++
}
return result
}
func typeToProxyTypeC(t constant.AdapterType) C.proxy_type_t {
switch t {
case constant.Direct:
return C.Direct
case constant.Reject:
return C.Reject
case constant.Shadowsocks:
return C.Shadowsocks
case constant.Snell:
return C.Snell
case constant.Socks5:
return C.Socks5
case constant.Http:
return C.Http
case constant.Vmess:
return C.Vmess
case constant.Trojan:
return C.Trojan
case constant.Relay:
return C.Relay
case constant.Selector:
return C.Selector
case constant.Fallback:
return C.Fallback
case constant.URLTest:
return C.URLTest
case constant.LoadBalance:
return C.LoadBalance
default:
return C.Unknown
}
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#if __cplusplus
extern "C" {
#endif
typedef enum proxy_type_t {
Direct, Reject, Socks5, Http, Shadowsocks, Vmess, Snell, Trojan, Selector, Fallback, LoadBalance, URLTest, Relay, Unknown
} proxy_type_t;
typedef struct proxy_t {
char *name;
proxy_type_t proxy_type;
long delay;
} proxy_t;
typedef struct proxy_group_t {
proxy_t base;
int now;
int proxies_size;
proxy_t proxies[];
} proxy_group_t;
typedef struct proxy_group_list_t {
int size;
proxy_group_t *groups[];
} proxy_group_list_t;
#if __cplusplus
};
#endif
@@ -2,22 +2,15 @@ package com.github.kr328.clash.core
import android.os.ParcelFileDescriptor
import com.github.kr328.clash.common.Global
import com.github.kr328.clash.common.utils.Log
import com.github.kr328.clash.core.bridge.Bridge
import com.github.kr328.clash.core.bridge.TunCallback
import com.github.kr328.clash.core.event.LogEvent
import com.github.kr328.clash.core.model.General
import com.github.kr328.clash.core.model.Proxy
import com.github.kr328.clash.core.model.ProxyGroup
import com.github.kr328.clash.core.model.Traffic
import com.github.kr328.clash.core.transact.DoneCallbackImpl
import com.github.kr328.clash.core.transact.ProxyCollectionImpl
import com.github.kr328.clash.core.transact.ProxyGroupCollectionImpl
import kotlinx.coroutines.CompletableDeferred
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.File
import java.io.InputStream
import kotlin.concurrent.thread
import java.util.concurrent.CompletableFuture
object Clash {
private val logReceivers = mutableMapOf<String, (LogEvent) -> Unit>()
@@ -49,31 +42,14 @@ object Clash {
onNewSocket: (Int) -> Boolean,
onTunStop: () -> Unit
) {
val sockets = ParcelFileDescriptor.createSocketPair()
thread {
val input = DataInputStream(ParcelFileDescriptor.AutoCloseInputStream(sockets[0]))
val output = DataOutputStream(ParcelFileDescriptor.AutoCloseOutputStream(sockets[0]))
runCatching {
while (true) {
val s = input.readInt()
onNewSocket(s)
output.writeInt(s)
}
Bridge.startTunDevice(fd, mtu, gateway, mirror, dns, object: TunCallback {
override fun onNewSocket(socket: Int) {
onNewSocket(socket)
}
onTunStop()
Log.i("Tun Closed")
sockets[0].close()
sockets[1].close()
}
Bridge.startTunDevice(fd, mtu, gateway, mirror, dns, sockets[1].detachFd())
override fun onStop() {
onTunStop()
}
})
}
fun stopTunDevice() {
@@ -84,48 +60,28 @@ object Clash {
Bridge.setDnsOverride(dnsOverride, appendNameservers.joinToString(","))
}
fun loadProfile(path: File, baseDir: File): CompletableDeferred<Unit> {
return DoneCallbackImpl().apply {
Bridge.loadProfileFile(path.absolutePath, baseDir.absolutePath, this)
}
fun loadProfile(path: File, baseDir: File): CompletableFuture<Unit> {
return Bridge.loadProfile(path.absolutePath, baseDir.absolutePath).thenApply { Unit }
}
fun downloadProfile(url: String, output: File, baseDir: File): CompletableDeferred<Unit> {
return DoneCallbackImpl().apply {
Bridge.downloadProfileAndCheck(url, output.absolutePath, baseDir.absolutePath, this)
}
fun downloadProfile(url: String, output: File, baseDir: File): CompletableFuture<Unit> {
return Bridge.downloadProfile(url, baseDir.absolutePath, output.absolutePath).thenApply { Unit }
}
fun downloadProfile(fd: Int, output: File, baseDir: File): CompletableDeferred<Unit> {
return DoneCallbackImpl().apply {
Bridge.readProfileAndCheck(fd.toLong(), output.absolutePath, baseDir.absolutePath, this)
}
fun downloadProfile(fd: ParcelFileDescriptor, output: File, baseDir: File): CompletableFuture<Unit> {
return Bridge.downloadProfile(fd.detachFd(), baseDir.absolutePath, output.absolutePath).thenApply { Unit }
}
fun queryProxyGroups(): List<ProxyGroup> {
return ProxyGroupCollectionImpl().also { Bridge.queryAllProxyGroups(it) }
.filterNotNull()
.map { group ->
ProxyGroup(group.name,
Proxy.Type.fromString(group.type),
group.delay,
group.current,
ProxyCollectionImpl().also { pc ->
group.queryAllProxies(pc)
}.filterNotNull().map {
Proxy(it.name, Proxy.Type.fromString(it.type), it.delay)
})
}
return Bridge.queryProxyGroups().toList()
}
fun setSelectedProxy(name: String, selected: String): Boolean {
return Bridge.setSelectedProxy(name, selected)
fun setSelector(name: String, selected: String) {
return Bridge.setSelector(name, selected)
}
fun startHealthCheck(name: String): CompletableDeferred<Unit> {
return DoneCallbackImpl().apply {
Bridge.startUrlTest(name, this)
}
fun performHealthCheck(group: String): CompletableFuture<Unit> {
return Bridge.performHealthCheck(group).thenApply { Unit }
}
fun setProxyMode(mode: String) {
@@ -3,8 +3,11 @@ package com.github.kr328.clash.core.bridge;
import androidx.annotation.Keep;
import com.github.kr328.clash.core.model.General;
import com.github.kr328.clash.core.model.ProxyGroup;
import com.github.kr328.clash.core.model.Traffic;
import java.util.concurrent.CompletableFuture;
@Keep
public final class Bridge {
static {
@@ -21,10 +24,18 @@ public final class Bridge {
public static native General queryGeneral();
public static native Traffic querySpeed();
public static native Traffic queryBandwidth();
public static native ProxyGroup[] queryProxyGroups();
public static native void startTunDevice(int fd, int mtu, String gateway, String mirror, String dns, int newSocket) throws ClashException;
public static native void startTunDevice(int fd, int mtu, String gateway, String mirror, String dns, TunCallback callback) throws ClashException;
public static native void stopTunDevice();
public static native void setDnsOverride(boolean overrideDns, String appendNameservers);
public static native void setProxyMode(String mode);
public static native void setSelector(String group, String selected);
public static native CompletableFuture<Object> downloadProfile(String url, String base, String output);
public static native CompletableFuture<Object> downloadProfile(int fd, String base, String output);
public static native CompletableFuture<Object> loadProfile(String path, String base);
public static native CompletableFuture<Object> performHealthCheck(String group);
}
@@ -0,0 +1,9 @@
package com.github.kr328.clash.core.bridge;
import androidx.annotation.Keep;
@Keep
public interface TunCallback {
void onNewSocket(int socket);
void onStop();
}
@@ -4,11 +4,14 @@ import androidx.annotation.Keep
import kotlinx.serialization.Serializable
@Serializable
data class Proxy @Keep constructor(
data class Proxy constructor(
val name: String,
val type: Type,
val delay: Long
) {
@Keep
constructor(name: String, type: String, delay: Long): this(name, Type.fromString(type), delay)
enum class Type(val text: String, val group: Boolean) {
DIRECT("Direct", false),
REJECT("Reject", false),
@@ -7,11 +7,10 @@ import kotlinx.serialization.Serializable
data class ProxyGroup(
val name: String,
val type: Proxy.Type,
val delay: Long,
val current: String,
val proxies: List<Proxy>
) {
@Keep
constructor(name: String, type: String, delay: Long, current: String, proxies: Array<Proxy>) :
this(name, Proxy.Type.fromString(type), delay, current, proxies.toList())
constructor(name: String, type: String, current: String, proxies: Array<Proxy>) :
this(name, Proxy.Type.fromString(type), current, proxies.toList())
}
@@ -6,7 +6,7 @@ import com.github.kr328.clash.common.serialization.MergedParcels
import kotlinx.serialization.Serializable
@Serializable
data class ProxyGroupList(val list: List<ProxyGroup>) : Parcelable {
data class ProxyGroupWrapper(val list: List<ProxyGroup>) : Parcelable {
override fun writeToParcel(parcel: Parcel, flags: Int) {
MergedParcels.dump(serializer(), this, parcel)
}
@@ -15,12 +15,12 @@ data class ProxyGroupList(val list: List<ProxyGroup>) : Parcelable {
return 0
}
companion object CREATOR : Parcelable.Creator<ProxyGroupList> {
override fun createFromParcel(parcel: Parcel): ProxyGroupList {
companion object CREATOR : Parcelable.Creator<ProxyGroupWrapper> {
override fun createFromParcel(parcel: Parcel): ProxyGroupWrapper {
return MergedParcels.load(serializer(), parcel)
}
override fun newArray(size: Int): Array<ProxyGroupList?> {
override fun newArray(size: Int): Array<ProxyGroupWrapper?> {
return arrayOfNulls(size)
}
}
@@ -1,14 +0,0 @@
package com.github.kr328.clash.core.transact
import bridge.DoneCallback
import kotlinx.coroutines.CompletableDeferred
class DoneCallbackImpl : DoneCallback, CompletableDeferred<Unit> by CompletableDeferred() {
override fun doneWithError(e: Exception?) {
completeExceptionally(e ?: return done())
}
override fun done() {
complete(Unit)
}
}
@@ -1,10 +0,0 @@
package com.github.kr328.clash.core.transact
import bridge.ProxyCollection
import bridge.ProxyGroupCollection
import bridge.ProxyGroupItem
import bridge.ProxyItem
import java.util.*
class ProxyCollectionImpl : LinkedList<ProxyItem?>(), ProxyCollection
class ProxyGroupCollectionImpl : LinkedList<ProxyGroupItem?>(), ProxyGroupCollection
@@ -2,7 +2,7 @@ package com.github.kr328.clash.service
import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.core.model.General
import com.github.kr328.clash.core.model.ProxyGroupList
import com.github.kr328.clash.core.model.ProxyGroupWrapper
import com.github.kr328.clash.service.data.ProfileDao
import com.github.kr328.clash.service.data.SelectedProxyDao
import com.github.kr328.clash.service.data.SelectedProxyEntity
@@ -18,8 +18,8 @@ class ClashManager(parent: CoroutineScope) :
Clash.setProxyMode(requireNotNull(mode))
}
override fun queryAllProxies(): ProxyGroupList {
return ProxyGroupList(Clash.queryProxyGroups())
override fun queryAllProxies(): ProxyGroupWrapper {
return ProxyGroupWrapper(Clash.queryProxyGroups())
}
override fun queryGeneral(): General {
@@ -35,7 +35,7 @@ class ClashManager(parent: CoroutineScope) :
SelectedProxyDao.setSelectedForProfile(SelectedProxyEntity(current.id, proxy, selected))
}
return Clash.setSelectedProxy(proxy, selected)
return Clash.setSelector(proxy, selected)
}
override fun queryBandwidth(): Long {
@@ -47,7 +47,7 @@ class ClashManager(parent: CoroutineScope) :
override fun startHealthCheck(group: String?, callback: IStreamCallback?) {
require(group != null && callback != null)
Clash.startHealthCheck(group).invokeOnCompletion { u ->
Clash.performHealthCheck(group).invokeOnCompletion { u ->
if (u != null)
callback.completeExceptionally(u.message)
else
@@ -50,7 +50,7 @@ class ReloadModule(private val context: Context) : Module() {
).await()
val remove = SelectedProxyDao.querySelectedForProfile(active.id)
.filterNot { Clash.setSelectedProxy(it.proxy, it.selected) }
.filterNot { Clash.setSelector(it.proxy, it.selected) }
.map { it.selected }
SelectedProxyDao.removeSelectedForProfile(active.id, remove)