diff --git a/core/clash.gradle.kts b/core/clash.gradle.kts index c89cf32..724ae87 100644 --- a/core/clash.gradle.kts +++ b/core/clash.gradle.kts @@ -94,8 +94,7 @@ fun generateGolangBuildEnvironment(abi: String): Map { "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 = 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() diff --git a/core/src/main/cpp/CMakeLists.txt b/core/src/main/cpp/CMakeLists.txt index 88849a0..a2f2e58 100644 --- a/core/src/main/cpp/CMakeLists.txt +++ b/core/src/main/cpp/CMakeLists.txt @@ -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) \ No newline at end of file +add_library(bridge SHARED main.cpp main.h init.cpp query.cpp patch.cpp tun.cpp defer.cpp) \ No newline at end of file diff --git a/core/src/main/cpp/defer.cpp b/core/src/main/cpp/defer.cpp new file mode 100644 index 0000000..d40310a --- /dev/null +++ b/core/src/main/cpp/defer.cpp @@ -0,0 +1,175 @@ +#include "main.h" + +#include + +struct deferContext { + jobject completableFuture; +}; + +static void downloadContextSuccess(void *context, void *argument) { + UNUSED(argument); + + auto *ctx = reinterpret_cast(context); + + Master::runWithAttached([&](JNIEnv *env) -> int { + Master::runWithContext(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(context); + auto *err = reinterpret_cast(argument); + + Master::runWithAttached([&](JNIEnv *env) -> int { + Master::runWithContext(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(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(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(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(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; + }); +} \ No newline at end of file diff --git a/core/src/main/cpp/main.cpp b/core/src/main/cpp/main.cpp index 3a9711c..cd60471 100644 --- a/core/src/main/cpp/main.cpp +++ b/core/src/main/cpp/main.cpp @@ -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, "", "(Ljava/lang/String;)V"); cTrafficConstructor = env->GetMethodID(cTraffic, "", "(JJ)V"); cGeneralConstructor = env->GetMethodID(cGeneral, "", "(Ljava/lang/String;IIII)V"); cCompletableFutureConstructor = env->GetMethodID(cCompletableFuture, "", "()V"); + cProxyGroupConstructor = env->GetMethodID(cProxyGroup, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Lcom/github/kr328/clash/core/model/Proxy;)V"); + cProxyConstructor = env->GetMethodID(cProxy, "", "(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(env->NewGlobalRef(env->NewStringUTF("Direct"))); + sReject = reinterpret_cast(env->NewGlobalRef(env->NewStringUTF("Reject"))); + sShadowsocks = reinterpret_cast(env->NewGlobalRef(env->NewStringUTF("Shadowsocks"))); + sSnell = reinterpret_cast(env->NewGlobalRef(env->NewStringUTF("Snell"))); + sSocks5 = reinterpret_cast(env->NewGlobalRef(env->NewStringUTF("Socks5"))); + sHttp = reinterpret_cast(env->NewGlobalRef(env->NewStringUTF("Http"))); + sVmess = reinterpret_cast(env->NewGlobalRef(env->NewStringUTF("Vmess"))); + sTrojan = reinterpret_cast(env->NewGlobalRef(env->NewStringUTF("Trojan"))); + sRelay = reinterpret_cast(env->NewGlobalRef(env->NewStringUTF("Relay"))); + sSelector = reinterpret_cast(env->NewGlobalRef(env->NewStringUTF("Selector"))); + sFallback = reinterpret_cast(env->NewGlobalRef(env->NewStringUTF("Fallback"))); + sURLTest = reinterpret_cast(env->NewGlobalRef(env->NewStringUTF("URLTest"))); + sLoadBalance = reinterpret_cast(env->NewGlobalRef(env->NewStringUTF("LoadBalance"))); + sUnknown = reinterpret_cast(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(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 ¤t, 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(&env), JNI_VERSION_1_6) != JNI_OK) return -1; - new Master(env); + new Master(vm, env); return JNI_VERSION_1_6; } \ No newline at end of file diff --git a/core/src/main/cpp/main.h b/core/src/main/cpp/main.h index bbc340f..1cf1bd7 100644 --- a/core/src/main/cpp/main.h +++ b/core/src/main/cpp/main.h @@ -11,23 +11,49 @@ public: class Context; public: - Master(JNIEnv *env); + Master(JavaVM *vm, JNIEnv *env); public: - template - static R runWithContext(JNIEnv *env, const std::function& func); + template static R runWithContext(JNIEnv *env, const std::function& func); + template static R runWithAttached(const std::function &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 ¤t, 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 Master::Context context(env); return func(&context); +} + +template R Master::runWithAttached(const std::function &func) { + Master *m = Master::master; + + JNIEnv *env; + + m->vm->AttachCurrentThread(&env, nullptr); + + R result = func(env); + + m->vm->DetachCurrentThread(); + + return result; } \ No newline at end of file diff --git a/core/src/main/cpp/patch.cpp b/core/src/main/cpp/patch.cpp index ada2e3d..6ea0d16 100644 --- a/core/src/main/cpp/patch.cpp +++ b/core/src/main/cpp/patch.cpp @@ -1,2 +1,68 @@ #include "main.h" +#include + +extern "C" +JNIEXPORT void JNICALL +Java_com_github_kr328_clash_core_bridge_Bridge_setProxyMode(JNIEnv *env, jclass clazz, + jstring proxy_mode) { + Master::runWithContext(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(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(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); + }); +} \ No newline at end of file diff --git a/core/src/main/cpp/query.cpp b/core/src/main/cpp/query.cpp index e65d8ac..2f9af8a 100644 --- a/core/src/main/cpp/query.cpp +++ b/core/src/main/cpp/query.cpp @@ -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(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; + }); } \ No newline at end of file diff --git a/core/src/main/cpp/tun.cpp b/core/src/main/cpp/tun.cpp new file mode 100644 index 0000000..f5652f6 --- /dev/null +++ b/core/src/main/cpp/tun.cpp @@ -0,0 +1,83 @@ +#include "main.h" + +struct tunContext { + jobject callback; +}; + +static void tunContextOnNewSocket(void *context, void *argument) { + auto *ctx = reinterpret_cast(context); + auto fd = reinterpret_cast(argument); + + Master::runWithAttached([&](JNIEnv *env) -> int { + Master::runWithContext(env, [&](Master::Context *context) { + context->tunCallbackNewSocket(ctx->callback, fd); + }); + + return 0; + }); +} + +static void tunContextOnStop(void *context, void *argument) { + UNUSED(argument); + + auto *ctx = reinterpret_cast(context); + + Master::runWithAttached([&](JNIEnv *env) -> int { + Master::runWithContext(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(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(); +} \ No newline at end of file diff --git a/core/src/main/golang/bridge/callback.go b/core/src/main/golang/bridge/callback.go deleted file mode 100644 index 457e114..0000000 --- a/core/src/main/golang/bridge/callback.go +++ /dev/null @@ -1,6 +0,0 @@ -package bridge - -type DoneCallback interface { - Done() - DoneWithError(error) -} diff --git a/core/src/main/golang/bridge/general.go b/core/src/main/golang/bridge/general.go deleted file mode 100644 index 07a77b6..0000000 --- a/core/src/main/golang/bridge/general.go +++ /dev/null @@ -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) - } -} diff --git a/core/src/main/golang/bridge/init.go b/core/src/main/golang/bridge/init.go deleted file mode 100644 index 4895af7..0000000 --- a/core/src/main/golang/bridge/init.go +++ /dev/null @@ -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 -} diff --git a/core/src/main/golang/bridge/profiles.go b/core/src/main/golang/bridge/profiles.go deleted file mode 100644 index 2a4de81..0000000 --- a/core/src/main/golang/bridge/profiles.go +++ /dev/null @@ -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() - } -} diff --git a/core/src/main/golang/bridge/proxies.go b/core/src/main/golang/bridge/proxies.go deleted file mode 100644 index 1277f59..0000000 --- a/core/src/main/golang/bridge/proxies.go +++ /dev/null @@ -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 -} diff --git a/core/src/main/golang/bridge/statistics.go b/core/src/main/golang/bridge/statistics.go deleted file mode 100644 index 265092a..0000000 --- a/core/src/main/golang/bridge/statistics.go +++ /dev/null @@ -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, - } -} diff --git a/core/src/main/golang/bridge/tun.go b/core/src/main/golang/bridge/tun.go deleted file mode 100644 index ae2a05c..0000000 --- a/core/src/main/golang/bridge/tun.go +++ /dev/null @@ -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 -} diff --git a/core/src/main/golang/ccall.c b/core/src/main/golang/ccall.c index 34311e8..03663ba 100644 --- a/core/src/main/golang/ccall.c +++ b/core/src/main/golang/ccall.c @@ -2,8 +2,9 @@ #include #include +#include -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(); + } } diff --git a/core/src/main/golang/ccall.go b/core/src/main/golang/ccall.go index fdd7093..e618152 100644 --- a/core/src/main/golang/ccall.go +++ b/core/src/main/golang/ccall.go @@ -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(¤tIndex, 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{}{} } \ No newline at end of file diff --git a/core/src/main/golang/config.go b/core/src/main/golang/config.go index 107d84f..6fc47c3 100644 --- a/core/src/main/golang/config.go +++ b/core/src/main/golang/config.go @@ -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, ",") } } diff --git a/core/src/main/golang/config.h b/core/src/main/golang/config.h index 7f20ed7..92599ab 100644 --- a/core/src/main/golang/config.h +++ b/core/src/main/golang/config.h @@ -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 diff --git a/core/src/main/golang/config/fetch.go b/core/src/main/golang/config/fetch.go index f6108af..fc8da0c 100644 --- a/core/src/main/golang/config/fetch.go +++ b/core/src/main/golang/config/fetch.go @@ -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 diff --git a/core/src/main/golang/defer.go b/core/src/main/golang/defer.go new file mode 100644 index 0000000..c0cf3e9 --- /dev/null +++ b/core/src/main/golang/defer.go @@ -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) + }() +} \ No newline at end of file diff --git a/core/src/main/golang/general.h b/core/src/main/golang/general.h index f237309..87b60e0 100644 --- a/core/src/main/golang/general.h +++ b/core/src/main/golang/general.h @@ -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; diff --git a/core/src/main/golang/main.c b/core/src/main/golang/main.c new file mode 100644 index 0000000..e511e31 --- /dev/null +++ b/core/src/main/golang/main.c @@ -0,0 +1,24 @@ +#include + +#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); +} + diff --git a/core/src/main/golang/main.go b/core/src/main/golang/main.go index eaedd5c..f4bdcdb 100644 --- a/core/src/main/golang/main.go +++ b/core/src/main/golang/main.go @@ -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) diff --git a/core/src/main/golang/proxies.go b/core/src/main/golang/proxies.go new file mode 100644 index 0000000..ed1ef67 --- /dev/null +++ b/core/src/main/golang/proxies.go @@ -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 + +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 + } +} \ No newline at end of file diff --git a/core/src/main/golang/proxies.h b/core/src/main/golang/proxies.h new file mode 100644 index 0000000..fa34ea8 --- /dev/null +++ b/core/src/main/golang/proxies.h @@ -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 + diff --git a/core/src/main/java/com/github/kr328/clash/core/Clash.kt b/core/src/main/java/com/github/kr328/clash/core/Clash.kt index 464072e..dd22c7a 100644 --- a/core/src/main/java/com/github/kr328/clash/core/Clash.kt +++ b/core/src/main/java/com/github/kr328/clash/core/Clash.kt @@ -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 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 { - return DoneCallbackImpl().apply { - Bridge.loadProfileFile(path.absolutePath, baseDir.absolutePath, this) - } + fun loadProfile(path: File, baseDir: File): CompletableFuture { + return Bridge.loadProfile(path.absolutePath, baseDir.absolutePath).thenApply { Unit } } - fun downloadProfile(url: String, output: File, baseDir: File): CompletableDeferred { - return DoneCallbackImpl().apply { - Bridge.downloadProfileAndCheck(url, output.absolutePath, baseDir.absolutePath, this) - } + fun downloadProfile(url: String, output: File, baseDir: File): CompletableFuture { + return Bridge.downloadProfile(url, baseDir.absolutePath, output.absolutePath).thenApply { Unit } } - fun downloadProfile(fd: Int, output: File, baseDir: File): CompletableDeferred { - return DoneCallbackImpl().apply { - Bridge.readProfileAndCheck(fd.toLong(), output.absolutePath, baseDir.absolutePath, this) - } + fun downloadProfile(fd: ParcelFileDescriptor, output: File, baseDir: File): CompletableFuture { + return Bridge.downloadProfile(fd.detachFd(), baseDir.absolutePath, output.absolutePath).thenApply { Unit } } fun queryProxyGroups(): List { - 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 { - return DoneCallbackImpl().apply { - Bridge.startUrlTest(name, this) - } + fun performHealthCheck(group: String): CompletableFuture { + return Bridge.performHealthCheck(group).thenApply { Unit } } fun setProxyMode(mode: String) { diff --git a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.java b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.java index 958fa46..4c27222 100644 --- a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.java +++ b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.java @@ -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 downloadProfile(String url, String base, String output); + public static native CompletableFuture downloadProfile(int fd, String base, String output); + + public static native CompletableFuture loadProfile(String path, String base); + public static native CompletableFuture performHealthCheck(String group); } diff --git a/core/src/main/java/com/github/kr328/clash/core/bridge/TunCallback.java b/core/src/main/java/com/github/kr328/clash/core/bridge/TunCallback.java new file mode 100644 index 0000000..a43642b --- /dev/null +++ b/core/src/main/java/com/github/kr328/clash/core/bridge/TunCallback.java @@ -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(); +} diff --git a/core/src/main/java/com/github/kr328/clash/core/model/Proxy.kt b/core/src/main/java/com/github/kr328/clash/core/model/Proxy.kt index a9d24b5..bb5f32f 100644 --- a/core/src/main/java/com/github/kr328/clash/core/model/Proxy.kt +++ b/core/src/main/java/com/github/kr328/clash/core/model/Proxy.kt @@ -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), diff --git a/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroup.kt b/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroup.kt index d4d11d1..7fd5685 100644 --- a/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroup.kt +++ b/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroup.kt @@ -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 ) { @Keep - constructor(name: String, type: String, delay: Long, current: String, proxies: Array) : - this(name, Proxy.Type.fromString(type), delay, current, proxies.toList()) + constructor(name: String, type: String, current: String, proxies: Array) : + this(name, Proxy.Type.fromString(type), current, proxies.toList()) } \ No newline at end of file diff --git a/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroupList.kt b/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroupWrapper.kt similarity index 72% rename from core/src/main/java/com/github/kr328/clash/core/model/ProxyGroupList.kt rename to core/src/main/java/com/github/kr328/clash/core/model/ProxyGroupWrapper.kt index 7d68a94..121e83a 100644 --- a/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroupList.kt +++ b/core/src/main/java/com/github/kr328/clash/core/model/ProxyGroupWrapper.kt @@ -6,7 +6,7 @@ import com.github.kr328.clash.common.serialization.MergedParcels import kotlinx.serialization.Serializable @Serializable -data class ProxyGroupList(val list: List) : Parcelable { +data class ProxyGroupWrapper(val list: List) : Parcelable { override fun writeToParcel(parcel: Parcel, flags: Int) { MergedParcels.dump(serializer(), this, parcel) } @@ -15,12 +15,12 @@ data class ProxyGroupList(val list: List) : Parcelable { return 0 } - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): ProxyGroupList { + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): ProxyGroupWrapper { return MergedParcels.load(serializer(), parcel) } - override fun newArray(size: Int): Array { + override fun newArray(size: Int): Array { return arrayOfNulls(size) } } diff --git a/core/src/main/java/com/github/kr328/clash/core/transact/DoneCallbackImpl.kt b/core/src/main/java/com/github/kr328/clash/core/transact/DoneCallbackImpl.kt deleted file mode 100644 index 5319f91..0000000 --- a/core/src/main/java/com/github/kr328/clash/core/transact/DoneCallbackImpl.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.kr328.clash.core.transact - -import bridge.DoneCallback -import kotlinx.coroutines.CompletableDeferred - -class DoneCallbackImpl : DoneCallback, CompletableDeferred by CompletableDeferred() { - override fun doneWithError(e: Exception?) { - completeExceptionally(e ?: return done()) - } - - override fun done() { - complete(Unit) - } -} \ No newline at end of file diff --git a/core/src/main/java/com/github/kr328/clash/core/transact/ProxyCollections.kt b/core/src/main/java/com/github/kr328/clash/core/transact/ProxyCollections.kt deleted file mode 100644 index b43db5f..0000000 --- a/core/src/main/java/com/github/kr328/clash/core/transact/ProxyCollections.kt +++ /dev/null @@ -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(), ProxyCollection -class ProxyGroupCollectionImpl : LinkedList(), ProxyGroupCollection \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/ClashManager.kt b/service/src/main/java/com/github/kr328/clash/service/ClashManager.kt index 463b662..c86b0f9 100644 --- a/service/src/main/java/com/github/kr328/clash/service/ClashManager.kt +++ b/service/src/main/java/com/github/kr328/clash/service/ClashManager.kt @@ -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 diff --git a/service/src/main/java/com/github/kr328/clash/service/clash/module/ReloadModule.kt b/service/src/main/java/com/github/kr328/clash/service/clash/module/ReloadModule.kt index 7b460c7..512512d 100644 --- a/service/src/main/java/com/github/kr328/clash/service/clash/module/ReloadModule.kt +++ b/service/src/main/java/com/github/kr328/clash/service/clash/module/ReloadModule.kt @@ -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)