Add CI/CD configuration and API documentation

This commit is contained in:
2026-07-01 21:40:53 +08:00
commit c590135d68
4168 changed files with 740252 additions and 0 deletions
@@ -0,0 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.kr328.clash.core">
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
@@ -0,0 +1,81 @@
cmake_minimum_required(VERSION 3.0)
# 获取git hash
message(STATUS "CMAKE_CURRENT_SOURCE_DIR= ${CMAKE_CURRENT_SOURCE_DIR}")
execute_process(
COMMAND git submodule foreach git log -1 --format=%H
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE COMMIT_HASH
)
# 判断是否获取到了git hash值(规避如因项目目录非git repository等原因产生的获取异常而中断build)
string(LENGTH ${COMMIT_HASH} OUTPUT_VARIABLE COMMIT_HASH_LENGTH)
string(COMPARE LESS ${COMMIT_HASH_LENGTH} "40" OUTPUT_VARIABLE GIT_HASH_COMPARISON_RESULT)
if(NOT ${GIT_HASH_COMPARISON_RESULT})
string(REPLACE "\n" ";" COMMIT_HASH "${COMMIT_HASH}")
list(GET COMMIT_HASH 1 COMMIT_HASH)
string (REGEX REPLACE "[\n\t\r]" "" COMMIT_HASH ${COMMIT_HASH})
#string(SUBSTRING ${COMMIT_HASH} 0 7 COMMIT_HASH)
message(STATUS "git hash= ${COMMIT_HASH}")
# 获取git分支名称
execute_process(
COMMAND git submodule foreach git branch -r --contains ${COMMIT_HASH}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE CURRENT_BRANCH
)
string(REPLACE "\n" ";" CURRENT_BRANCH "${CURRENT_BRANCH}")
list(GET CURRENT_BRANCH 1 CURRENT_BRANCH)
string (REGEX REPLACE "origin/" "" CURRENT_BRANCH ${CURRENT_BRANCH} "")
string (REGEX REPLACE "[\n\t\r]" "" CURRENT_BRANCH ${CURRENT_BRANCH} "")
string(SUBSTRING ${CURRENT_BRANCH} 0 8 CURRENT_BRANCH)
message(STATUS "git current branch = ${CURRENT_BRANCH}")
# 获取生成时间
string(TIMESTAMP COMPILE_TIME "%y%m%d")
string (REGEX REPLACE "[\n\t\r]" "" COMPILE_TIME ${COMPILE_TIME})
string(REGEX REPLACE "\"" "" COMPILE_TIME ${COMPILE_TIME})
# 生成版本信息
set(GIT_VERSION "${CURRENT_BRANCH}_${COMMIT_HASH}_${COMPILE_TIME}")
message(STATUS "version info = ${GIT_VERSION}")
# 去除空格
string(REGEX REPLACE "[ ]+" "" GIT_VERSION "${GIT_VERSION}")
else()
message(AUTHOR_WARNING "Unable to get git hash, but build will continue.")
endif()
# 保存变量到文件
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/version.h @ONLY)
#设置项目名称与语言类别(必)
project(clash-bridge C)
set(CMAKE_POSITION_INDEPENDENT_CODE on)
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
set(GO_OUTPUT_BASE ${GO_OUTPUT}/${FLAVOR_NAME})
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
set(GO_OUTPUT_BASE "${GO_OUTPUT_BASE}Debug")
elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
set(GO_OUTPUT_BASE "${GO_OUTPUT_BASE}Release")
elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
set(GO_OUTPUT_BASE "${GO_OUTPUT_BASE}Release")
else ()
message(FATAL_ERROR "Unknown build type ${CMAKE_BUILD_TYPE}")
endif ()
include_directories("${GO_OUTPUT_BASE}/${CMAKE_ANDROID_ARCH_ABI}")
include_directories("${GO_SOURCE}")
link_directories("${GO_OUTPUT_BASE}/${CMAKE_ANDROID_ARCH_ABI}")
add_library(bridge SHARED main.c jni_helper.c bridge_helper.c)
target_link_libraries(bridge log clash)
# Ensure 16 KB page alignment for Android 15+ compatibility
target_link_options(bridge PRIVATE "-Wl,-z,max-page-size=16384")
@@ -0,0 +1,12 @@
#include "bridge_helper.h"
uint64_t down_scale_traffic(uint64_t value) {
if (value > 1042 * 1024 * 1024)
return ((value * 100u / 1024u / 1024u / 1024u) & 0x3FFFFFFFu) | (3u << 30u);
if (value > 1024 * 1024)
return ((value * 100u / 1024u / 1024u) & 0x3FFFFFFFu) | (2u << 30u);
if (value > 1024)
return ((value * 100u / 1024u) & 0x3FFFFFFFu) | (1u << 30u);
return value & 0x3FFFFFFFu;
}
@@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
uint64_t down_scale_traffic(uint64_t value);
@@ -0,0 +1,82 @@
#include "jni_helper.h"
#include <malloc.h>
#include <string.h>
static JavaVM *global_vm;
static jclass c_string;
static jmethodID m_new_string;
static jmethodID m_get_bytes;
void initialize_jni(JavaVM *vm, JNIEnv *env) {
global_vm = vm;
c_string = (jclass) new_global(find_class("java/lang/String"));
m_new_string = find_method(c_string, "<init>", "([B)V");
m_get_bytes = find_method(c_string, "getBytes", "()[B");
}
JavaVM *global_java_vm() {
return global_vm;
}
char *jni_get_string(JNIEnv *env, jstring str) {
jbyteArray array = (*env)->CallObjectMethod(env, str, m_get_bytes);
int length = (*env)->GetArrayLength(env, array);
char *content = (char *) malloc(length + 1);
(*env)->GetByteArrayRegion(env, array, 0, length, (jbyte *) content);
content[length] = 0;
return content;
}
jstring jni_new_string(JNIEnv *env, const char *str) {
int length = strlen(str);
jbyteArray array = (*env)->NewByteArray(env, length);
(*env)->SetByteArrayRegion(env, array, 0, length, (const jbyte *) str);
return (jstring) (*env)->NewObject(env, c_string, m_new_string, array);
}
int jni_catch_exception(JNIEnv *env) {
int result = (*env)->ExceptionCheck(env);
if (result) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
return result;
}
void jni_attach_thread(struct _scoped_jni *jni) {
JavaVM *vm = global_java_vm();
if ((*vm)->GetEnv(vm, (void **) &jni->env, JNI_VERSION_1_6) == JNI_OK) {
jni->require_release = 0;
return;
}
if ((*vm)->AttachCurrentThread(vm, &jni->env, NULL) != JNI_OK) {
abort();
}
jni->require_release = 1;
}
void jni_detach_thread(struct _scoped_jni *jni) {
JavaVM *vm = global_java_vm();
if (jni->require_release) {
(*vm)->DetachCurrentThread(vm);
}
}
void release_string(char **str) {
free(*str);
}
@@ -0,0 +1,34 @@
#pragma once
#include <jni.h>
#include <stdint.h>
#include <stdlib.h>
#include <malloc.h>
#include <android/log.h>
struct _scoped_jni {
JNIEnv *env;
int require_release;
};
extern void initialize_jni(JavaVM *vm, JNIEnv *env);
extern jstring jni_new_string(JNIEnv *env, const char *str);
extern char *jni_get_string(JNIEnv *env, jstring str);
extern int jni_catch_exception(JNIEnv *env);
extern void jni_attach_thread(struct _scoped_jni *jni);
extern void jni_detach_thread(struct _scoped_jni *env);
extern void release_string(char **str);
#define ATTACH_JNI() __attribute__((unused, cleanup(jni_detach_thread))) \
struct _scoped_jni _jni; \
jni_attach_thread(&_jni); \
JNIEnv *env = _jni.env
#define scoped_string __attribute__((cleanup(release_string))) char*
#define find_class(name) (*env)->FindClass(env, name)
#define find_method(cls, name, signature) (*env)->GetMethodID(env, cls, name, signature)
#define new_global(obj) (*env)->NewGlobalRef(env, obj)
#define del_global(obj) (*env)->DeleteGlobalRef(env, obj)
#define get_string(jstr) jni_get_string(env, jstr)
#define new_string(cstr) jni_new_string(env, cstr)
@@ -0,0 +1,529 @@
#include <jni.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "bridge_helper.h"
#include "libclash.h"
#include "jni_helper.h"
#include "trace.h"
#include "version.h" // 添加当前编译core版本号变量
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeInit(JNIEnv *env, jobject thiz,
jstring home,
jstring version_name, jint sdk_version) {
TRACE_METHOD();
scoped_string _home = get_string(home);
scoped_string _version_name = get_string(version_name);
char* _git_version = make_String(GIT_VERSION);
coreInit(_home, _version_name, _git_version, sdk_version);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeReset(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
reset();
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeForceGc(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
forceGc();
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeSuspend(JNIEnv *env, jobject thiz,
jboolean suspended) {
TRACE_METHOD();
suspend((int) suspended);
}
JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeQueryTunnelState(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
scoped_string response = queryTunnelState();
return new_string(response);
}
JNIEXPORT jlong JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeQueryTrafficNow(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
uint64_t upload = 0l, download = 0l;
queryNow(&upload, &download);
return (jlong) (down_scale_traffic(upload) << 32u | down_scale_traffic(download));
}
JNIEXPORT jlong JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeQueryTrafficTotal(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
uint64_t upload = 0l, download = 0l;
queryTotal(&upload, &download);
return (jlong) (down_scale_traffic(upload) << 32u | down_scale_traffic(download));
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeNotifyDnsChanged(JNIEnv *env, jobject thiz,
jstring dns_list) {
TRACE_METHOD();
scoped_string _dns_list = get_string(dns_list);
notifyDnsChanged(_dns_list);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeNotifyTimeZoneChanged(JNIEnv *env, jobject thiz,
jstring name, jint offset) {
TRACE_METHOD();
scoped_string _name = get_string(name);
notifyTimeZoneChanged(_name, offset);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeNotifyInstalledAppChanged(JNIEnv *env,
jobject thiz,
jstring uid_list) {
TRACE_METHOD();
scoped_string _uid_list = get_string(uid_list);
notifyInstalledAppsChanged(_uid_list);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartTun(JNIEnv *env, jobject thiz,
jint fd,
jstring stack,
jstring gateway,
jstring portal,
jstring dns,
jobject cb) {
TRACE_METHOD();
scoped_string _stack = get_string(stack);
scoped_string _gateway = get_string(gateway);
scoped_string _portal = get_string(portal);
scoped_string _dns = get_string(dns);
jobject _interface = new_global(cb);
startTun(fd, _stack, _gateway, _portal, _dns, _interface);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeStopTun(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
stopTun();
}
JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartHttp(JNIEnv *env, jobject thiz,
jstring listen_at) {
TRACE_METHOD();
scoped_string _listen_at = get_string(listen_at);
scoped_string listened = startHttp(_listen_at);
if (listened == NULL)
return NULL;
return new_string(listened);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeStopHttp(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
stopHttp();
}
JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeQueryGroupNames(JNIEnv *env, jobject thiz,
jboolean exclude_not_selectable) {
TRACE_METHOD();
scoped_string response = queryGroupNames((int) exclude_not_selectable);
return new_string(response);
}
JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeQueryGroup(JNIEnv *env, jobject thiz,
jstring name, jstring mode) {
TRACE_METHOD();
scoped_string _name = get_string(name);
scoped_string _mode = get_string(mode);
scoped_string response = queryGroup(_name, _mode);
if (response == NULL)
return NULL;
return new_string(response);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeHealthCheck(JNIEnv *env, jobject thiz,
jobject completable,
jstring name) {
TRACE_METHOD();
jobject _completable = new_global(completable);
scoped_string _name = get_string(name);
healthCheck(_completable, _name);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeHealthCheckAll(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
healthCheckAll();
}
JNIEXPORT jboolean JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativePatchSelector(JNIEnv *env, jobject thiz,
jstring selector, jstring name) {
TRACE_METHOD();
scoped_string _selector = get_string(selector);
scoped_string _name = get_string(name);
return (jboolean) patchSelector(_selector, _name);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeLoad(JNIEnv *env, jobject thiz,
jobject completable, jstring path) {
TRACE_METHOD();
jobject _completable = new_global(completable);
scoped_string _path = get_string(path);
load(_completable, _path);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeFetchAndValid(JNIEnv *env, jobject thiz,
jobject callback,
jstring path,
jstring url, jboolean force) {
TRACE_METHOD();
jobject _completable = new_global(callback);
scoped_string _path = get_string(path);
scoped_string _url = get_string(url);
fetchAndValid(_completable, _path, _url, force);
}
JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeQueryProviders(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
scoped_string response = queryProviders();
return new_string(response);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeUpdateProvider(JNIEnv *env, jobject thiz,
jobject completable,
jstring type,
jstring name) {
TRACE_METHOD();
jobject _completable = new_global(completable);
scoped_string _type = get_string(type);
scoped_string _name = get_string(name);
updateProvider(_completable, _type, _name);
}
JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeReadOverride(JNIEnv *env, jobject thiz,
jint slot) {
TRACE_METHOD();
scoped_string response = readOverride(slot);
return new_string(response);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeWriteOverride(JNIEnv *env, jobject thiz,
jint slot,
jstring content) {
TRACE_METHOD();
scoped_string _content = get_string(content);
writeOverride(slot, _content);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeClearOverride(JNIEnv *env, jobject thiz,
jint slot) {
TRACE_METHOD();
clearOverride(slot);
}
JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeQueryConfiguration(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
scoped_string response = queryConfiguration();
return new_string(response);
}
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeSubscribeLogcat(JNIEnv *env, jobject thiz,
jobject callback) {
TRACE_METHOD();
jobject _callback = new_global(callback);
subscribeLogcat(_callback);
}
static jmethodID m_tun_interface_mark_socket;
static jmethodID m_tun_interface_query_socket_uid;
static jmethodID m_completable_complete;
static jmethodID m_completable_complete_exceptionally;
static jmethodID m_logcat_interface_received;
static jmethodID m_clash_exception;
static jmethodID m_fetch_callback_report;
static jmethodID m_fetch_callback_complete;
static jmethodID m_open;
static jmethodID m_get_message;
static jclass c_clash_exception;
static jclass c_content;
static jobject o_unit;
static void call_tun_interface_mark_socket_impl(void *tun_interface, int fd) {
TRACE_METHOD();
ATTACH_JNI();
(*env)->CallVoidMethod(env, (jobject) tun_interface,
(jmethodID) m_tun_interface_mark_socket,
(jint) fd);
}
static int call_tun_interface_query_socket_uid_impl(void *tun_interface, int protocol,
const char *source, const char *target) {
TRACE_METHOD();
ATTACH_JNI();
return (*env)->CallIntMethod(env, (jobject) tun_interface,
(jmethodID) m_tun_interface_query_socket_uid,
(jint) protocol,
(jstring) new_string(source),
(jstring) new_string(target));
}
static void call_completable_complete_impl(void *completable, const char *exception) {
TRACE_METHOD();
ATTACH_JNI();
if (exception == NULL) {
(*env)->CallBooleanMethod(env,
(jobject) completable,
(jmethodID) m_completable_complete,
(jobject) o_unit);
} else {
jthrowable _exception = (jthrowable)
(*env)->NewObject(env,
(jclass) c_clash_exception,
(jmethodID) m_clash_exception,
(jstring) new_string(exception)
);
(*env)->CallBooleanMethod(env,
(jobject) completable,
(jmethodID) m_completable_complete_exceptionally,
(jobject) _exception);
}
}
static void call_fetch_callback_report_impl(void *fetch_callback, const char *status_json) {
TRACE_METHOD();
ATTACH_JNI();
jstring _status_json = new_string(status_json);
(*env)->CallVoidMethod(env,
(jobject) fetch_callback,
(jmethodID) m_fetch_callback_report,
(jstring) _status_json);
}
static void call_fetch_callback_complete_impl(void *fetch_callback, const char *error) {
TRACE_METHOD();
ATTACH_JNI();
jstring _error = NULL;
if (error != NULL)
_error = new_string(error);
(*env)->CallVoidMethod(env,
(jobject) fetch_callback,
(jmethodID) m_fetch_callback_complete,
(jstring) _error);
}
static int call_logcat_interface_received_impl(void *callback, const char *payload) {
TRACE_METHOD();
ATTACH_JNI();
(*env)->CallVoidMethod(env,
(jobject) callback,
(jmethodID) m_logcat_interface_received,
(jstring) new_string(payload));
if (jni_catch_exception(env)) {
return 1;
}
return 0;
}
static int open_content_impl(const char *url, char *error, int error_length) {
TRACE_METHOD();
ATTACH_JNI();
int fd = (*env)->CallStaticIntMethod(env, c_content, m_open, new_string(url));
if ((*env)->ExceptionCheck(env)) {
jthrowable exception = (*env)->ExceptionOccurred(env);
(*env)->ExceptionClear(env);
jstring message = (jstring) (*env)->CallObjectMethod(
env,
(jthrowable) exception,
(jmethodID) m_get_message
);
if (message == NULL) {
strncpy(error, "unknown", error_length - 1);
} else {
scoped_string _message = get_string(message);
strncpy(error, _message, error_length - 1);
}
return -1;
}
return fd;
}
static void release_jni_object_impl(void *obj) {
TRACE_METHOD();
ATTACH_JNI();
del_global((jobject) obj);
}
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
TRACE_METHOD();
JNIEnv *env = NULL;
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
initialize_jni(vm, env);
jclass c_tun_interface = find_class("com/github/kr328/clash/core/bridge/TunInterface");
jclass c_completable = find_class("kotlinx/coroutines/CompletableDeferred");
jclass c_fetch_callback = find_class("com/github/kr328/clash/core/bridge/FetchCallback");
jclass c_logcat_interface = find_class("com/github/kr328/clash/core/bridge/LogcatInterface");
jclass _c_clash_exception = find_class("com/github/kr328/clash/core/bridge/ClashException");
jclass _c_content = find_class("com/github/kr328/clash/core/bridge/Content");
jclass c_throwable = find_class("java/lang/Throwable");
jclass c_unit = find_class("kotlin/Unit");
m_tun_interface_mark_socket = find_method(c_tun_interface, "markSocket",
"(I)V");
m_tun_interface_query_socket_uid = find_method(c_tun_interface, "querySocketUid",
"(ILjava/lang/String;Ljava/lang/String;)I");
m_completable_complete = find_method(c_completable, "complete",
"(Ljava/lang/Object;)Z");
m_fetch_callback_report = find_method(c_fetch_callback, "report",
"(Ljava/lang/String;)V");
m_fetch_callback_complete = find_method(c_fetch_callback, "complete",
"(Ljava/lang/String;)V");
m_completable_complete_exceptionally = find_method(c_completable, "completeExceptionally",
"(Ljava/lang/Throwable;)Z");
m_logcat_interface_received = find_method(c_logcat_interface, "received",
"(Ljava/lang/String;)V");
m_clash_exception = find_method(_c_clash_exception, "<init>",
"(Ljava/lang/String;)V");
m_get_message = find_method(c_throwable, "getMessage",
"()Ljava/lang/String;");
m_open = (*env)->GetStaticMethodID(env, _c_content, "open",
"(Ljava/lang/String;)I");
o_unit = (*env)->GetStaticObjectField(env, c_unit,
(*env)->GetStaticFieldID(env, c_unit, "INSTANCE",
"Lkotlin/Unit;"));
c_clash_exception = (jclass) new_global(_c_clash_exception);
c_content = (jclass) new_global(_c_content);
o_unit = new_global(o_unit);
mark_socket_func = &call_tun_interface_mark_socket_impl;
query_socket_uid_func = &call_tun_interface_query_socket_uid_impl;
complete_func = &call_completable_complete_impl;
fetch_report_func = &call_fetch_callback_report_impl;
fetch_complete_func = &call_fetch_callback_complete_impl;
logcat_received_func = &call_logcat_interface_received_impl;
open_content_func = &open_content_impl;
release_object_func = &release_jni_object_impl;
return JNI_VERSION_1_6;
}
JNIEXPORT jstring JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeCoreVersion(JNIEnv *env, jobject thiz) {
TRACE_METHOD();
char* Version = make_String(GIT_VERSION);
return new_string(Version);
}
@@ -0,0 +1,12 @@
#ifndef VERSION_H_IN
#define VERSION_H_IN
/**
* 当前编译core版本号
*/
#define GIT_VERSION
#define make_Str(x) #x
#define make_String(x) make_Str(x)
#endif
@@ -0,0 +1,12 @@
#ifndef VERSION_H_IN
#define VERSION_H_IN
/**
* 当前编译core版本号
*/
#define GIT_VERSION @GIT_VERSION@
#define make_Str(x) #x
#define make_String(x) make_Str(x)
#endif
@@ -0,0 +1,117 @@
module cfa
go 1.23
require (
github.com/dlclark/regexp2 v1.11.4
github.com/metacubex/mihomo v1.18.10
golang.org/x/sync v0.8.0
gopkg.in/yaml.v3 v3.0.1
)
replace github.com/metacubex/mihomo => ../../foss/golang/clash
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297
require (
github.com/3andne/restls-client-go v0.1.6 // indirect
github.com/RyuaNerin/go-krypto v1.2.4 // indirect
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/coreos/go-iptables v0.8.0 // indirect
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect
github.com/go-chi/render v1.0.3 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/gofrs/uuid/v5 v5.3.0 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 // indirect
github.com/metacubex/chacha v0.1.0 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect
github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4 // indirect
github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4 // indirect
github.com/metacubex/sing-shadowsocks v0.2.8 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.2 // indirect
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 // indirect
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3 // indirect
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa // indirect
github.com/metacubex/utls v1.6.6 // indirect
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/openacid/low v0.1.21 // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/sagernet/cors v1.2.1 // indirect
github.com/sagernet/fswatch v0.1.1 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/sing v0.5.0-alpha.13 // indirect
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/samber/lo v1.47.0 // indirect
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
)
@@ -0,0 +1,285 @@
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
github.com/RyuaNerin/elliptic2 v1.0.0/go.mod h1:wWB8fWrJI/6EPJkyV/r1Rj0hxUgrusmqSj8JN6yNf/A=
github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4b4Go=
github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c/go.mod h1:ETASDWf/FmEb6Ysrtd1QhjNedUU/ZQxBCRLh60bQ/UI=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBEz5nGDMvswiajqh7k8ogWRlhRwKy5mY=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7spovjlY=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 h1:hxST5pwMBEOWmxpkX20w9oZG+hXdhKmAIPQ3NGGAxas=
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc=
github.com/metacubex/chacha v0.1.0/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4 h1:CgdUBRxmNlxEGkp35HwvgQ10jwOOUJKWdOxpi8yWi8o=
github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4/go.mod h1:Y7yRGqFE6UQL/3aKPYmiYdjfVkeujJaStP4+jiZMcN8=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 h1:YG/JkwGPbca5rUtEMHIu8ZuqzR7BSVm1iqY8hNoMeMA=
github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4 h1:HobpULaPK6OoxrHMmgcwLkwwIduXVmwdcznwUfH1GQM=
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 h1:ypfofGDZbP8p3Y4P/m74JYu7sQViesi3c8nbmT6cS0Y=
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE=
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3 h1:xg71VmzLS6ByAzi/57phwDvjE+dLLs+ozH00k4DnOns=
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3/go.mod h1:6nitcmzPDL3MXnLdhu6Hm126Zk4S1fBbX3P7jxUxSFw=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
github.com/openacid/must v0.1.3/go.mod h1:luPiXCuJlEo3UUFQngVQokV0MPGryeYvtCbQPs3U1+I=
github.com/openacid/testkeys v0.1.6/go.mod h1:MfA7cACzBpbiwekivj8StqX0WIRmqlMsci1c37CA3Do=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c/go.mod h1:NV/a66PhhWYVmUMaotlXJ8fIEFB98u+c8l/CQIEFLrU=
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e h1:ur8uMsPIFG3i4Gi093BQITvwH9znsz2VUZmnmwHvpIo=
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e5fBW3bpPyo+3uLo513gIUblc03egGjMM0+5GKbzK8=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiEYzdMlNsVvBYOAJWZjdktqFE9tQh5BT2+4=
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
@@ -0,0 +1,16 @@
package all
import (
_ "cfa/native/app"
_ "cfa/native/common"
_ "cfa/native/config"
_ "cfa/native/delegate"
_ "cfa/native/platform"
_ "cfa/native/proxy"
_ "cfa/native/tun"
_ "cfa/native/tunnel"
_ "golang.org/x/sync/semaphore"
_ "github.com/metacubex/mihomo/log"
)
@@ -0,0 +1,61 @@
package main
//#include "bridge.h"
import "C"
import (
"errors"
"unsafe"
"cfa/native/app"
"github.com/metacubex/mihomo/log"
)
func openRemoteContent(url string) (int, error) {
u := C.CString(url)
e := (*C.char)(C.malloc(1024))
log.Debugln("Open remote url: %s", url)
defer C.free(unsafe.Pointer(e))
fd := C.open_content(u, e, 1024)
if fd < 0 {
return -1, errors.New(C.GoString(e))
}
return int(fd), nil
}
//export notifyDnsChanged
func notifyDnsChanged(dnsList C.c_string) {
d := C.GoString(dnsList)
app.NotifyDnsChanged(d)
}
//export notifyInstalledAppsChanged
func notifyInstalledAppsChanged(uids C.c_string) {
u := C.GoString(uids)
app.NotifyInstallAppsChanged(u)
}
//export notifyTimeZoneChanged
func notifyTimeZoneChanged(name C.c_string, offset C.int) {
app.NotifyTimeZoneChanged(C.GoString(name), int(offset))
}
//export queryConfiguration
func queryConfiguration() *C.char {
response := &struct{}{}
return marshalJson(&response)
}
func init() {
app.ApplyContentContext(openRemoteContent)
}
@@ -0,0 +1,53 @@
package app
import (
"strconv"
"strings"
"time"
)
var appVersionName string
var platformVersion int
var installedAppsUid = map[int]string{}
func ApplyVersionName(versionName string) {
appVersionName = versionName
}
func ApplyPlatformVersion(version int) {
platformVersion = version
}
func VersionName() string {
return appVersionName
}
func PlatformVersion() int {
return platformVersion
}
func NotifyInstallAppsChanged(uidList string) {
uids := map[int]string{}
for _, item := range strings.Split(uidList, ",") {
kv := strings.Split(item, ":")
if len(kv) == 2 {
uid, err := strconv.Atoi(kv[0])
if err != nil {
continue
}
uids[uid] = kv[1]
}
}
installedAppsUid = uids
}
func QueryAppByUid(uid int) string {
return installedAppsUid[uid]
}
func NotifyTimeZoneChanged(name string, offset int) {
time.Local = time.FixedZone(name, offset)
}
@@ -0,0 +1,27 @@
package app
import (
"errors"
"os"
"syscall"
)
var openContentImpl = func(url string) (int, error) {
return -1, errors.New("not implement")
}
func OpenContent(url string) (*os.File, error) {
fd, err := openContentImpl(url)
if err != nil {
return nil, err
}
_ = syscall.SetNonblock(fd, true)
return os.NewFile(uintptr(fd), "fd"), nil
}
func ApplyContentContext(openContent func(string) (int, error)) {
openContentImpl = openContent
}
@@ -0,0 +1,16 @@
package app
import (
"strings"
"github.com/metacubex/mihomo/dns"
)
func NotifyDnsChanged(dnsList string) {
var addr []string
if len(dnsList) > 0 {
addr = strings.Split(dnsList, ",")
}
dns.UpdateSystemDNS(addr)
dns.FlushCacheWithDefaultResolver()
}
@@ -0,0 +1,51 @@
package app
import (
"net"
"syscall"
"cfa/native/platform"
)
var markSocketImpl func(fd int)
var querySocketUidImpl func(protocol int, source, target string) int
func MarkSocket(fd int) {
markSocketImpl(fd)
}
func QuerySocketUid(source, target net.Addr) int {
var protocol int
switch source.Network() {
case "udp", "udp4", "udp6":
protocol = syscall.IPPROTO_UDP
case "tcp", "tcp4", "tcp6":
protocol = syscall.IPPROTO_TCP
default:
return -1
}
if PlatformVersion() < 29 {
return platform.QuerySocketUidFromProcFs(source, target)
}
return querySocketUidImpl(protocol, source.String(), target.String())
}
func ApplyTunContext(markSocket func(fd int), querySocketUid func(int, string, string) int) {
if markSocket == nil {
markSocket = func(fd int) {}
}
if querySocketUid == nil {
querySocketUid = func(int, string, string) int { return -1 }
}
markSocketImpl = markSocket
querySocketUidImpl = querySocketUid
}
func init() {
ApplyTunContext(nil, nil)
}
@@ -0,0 +1,34 @@
package app
import (
"github.com/dlclark/regexp2"
"github.com/metacubex/mihomo/log"
)
var uiSubtitlePattern *regexp2.Regexp
func ApplySubtitlePattern(pattern string) {
if pattern == "" {
uiSubtitlePattern = nil
return
}
if o := uiSubtitlePattern; o != nil && o.String() == pattern {
return
}
reg, err := regexp2.Compile(pattern, regexp2.IgnoreCase|regexp2.Compiled)
if err == nil {
uiSubtitlePattern = reg
} else {
uiSubtitlePattern = nil
log.Warnln("Compile ui-subtitle-pattern: %s", err.Error())
}
}
func SubtitlePattern() *regexp2.Regexp {
return uiSubtitlePattern
}
@@ -0,0 +1,115 @@
#include "bridge.h"
#include "trace.h"
void (*mark_socket_func)(void *tun_interface, int fd);
int (*query_socket_uid_func)(void *tun_interface, int protocol, const char *source, const char *target);
void (*complete_func)(void *completable, const char *exception);
void (*fetch_report_func)(void *fetch_callback, const char *status_json);
void (*fetch_complete_func)(void *fetch_callback, const char *error);
int (*logcat_received_func)(void *logcat_interface, const char *payload);
int (*open_content_func)(const char *url, char *error, int error_length);
void (*release_object_func)(void *obj);
void mark_socket(void *interface, int fd) {
TRACE_METHOD();
mark_socket_func(interface, fd);
}
int query_socket_uid(void *interface, int protocol, char *source, char *target) {
TRACE_METHOD();
int result = query_socket_uid_func(interface, protocol, source, target);
free(source);
free(target);
return result;
}
void complete(void *obj, char *error) {
TRACE_METHOD();
complete_func(obj, error);
free(error);
}
void fetch_complete(void *fetch_callback, char *exception) {
TRACE_METHOD();
fetch_complete_func(fetch_callback, exception);
free(exception);
}
void fetch_report(void *fetch_callback, char *json_status) {
TRACE_METHOD();
fetch_report_func(fetch_callback, json_status);
free(json_status);
}
int logcat_received(void *logcat_interface, char *payload) {
TRACE_METHOD();
int result = logcat_received_func(logcat_interface, payload);
free(payload);
return result;
}
int open_content(char *url, char *error, int error_length) {
TRACE_METHOD();
int result = open_content_func(url, error, error_length);
free(url);
return result;
}
void release_object(void *obj) {
TRACE_METHOD();
release_object_func(obj);
}
void log_info(char *msg) {
__android_log_write(ANDROID_LOG_INFO, TAG, msg);
free(msg);
}
void log_error(char *msg) {
__android_log_write(ANDROID_LOG_ERROR, TAG, msg);
free(msg);
}
void log_warn(char *msg) {
__android_log_write(ANDROID_LOG_WARN, TAG, msg);
free(msg);
}
void log_debug(char *msg) {
__android_log_write(ANDROID_LOG_DEBUG, TAG, msg);
free(msg);
}
void log_verbose(char *msg) {
__android_log_write(ANDROID_LOG_VERBOSE, TAG, msg);
free(msg);
}
@@ -0,0 +1,53 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <malloc.h>
#include <android/log.h>
#define TAG "ClashMetaForAndroid"
typedef const char *c_string;
extern void (*mark_socket_func)(void *tun_interface, int fd);
extern int (*query_socket_uid_func)(void *tun_interface, int protocol, const char *source, const char *target);
extern void (*complete_func)(void *completable, const char *exception);
extern void (*fetch_report_func)(void *fetch_callback, const char *status_json);
extern void (*fetch_complete_func)(void *fetch_callback, const char *error);
extern int (*logcat_received_func)(void *logcat_interface, const char *payload);
extern void (*release_object_func)(void *obj);
extern int (*open_content_func)(const char *url, char *error, int error_length);
// cgo
extern void mark_socket(void *interface, int fd);
extern int query_socket_uid(void *interface, int protocol, char *source, char *target);
extern void complete(void *obj, char *error);
extern void fetch_complete(void *completable, char *exception);
extern void fetch_report(void *fetch_callback, char *status_json);
extern int logcat_received(void *logcat_interface, char *payload);
extern void release_object(void *obj);
extern int open_content(char *url, char *error, int error_length);
extern void log_info(char *msg);
extern void log_error(char *msg);
extern void log_warn(char *msg);
extern void log_debug(char *msg);
extern void log_verbose(char *msg);
@@ -0,0 +1,23 @@
package common
import "strings"
func ResolveAsRoot(path string) string {
directories := strings.Split(path, "/")
result := make([]string, 0, len(directories))
for _, directory := range directories {
switch directory {
case "", ".":
continue
case "..":
if len(result) > 0 {
result = result[:len(result)-1]
}
default:
result = append(result, directory)
}
}
return strings.Join(result, "/")
}
@@ -0,0 +1,62 @@
package main
//#include "bridge.h"
import "C"
import (
"runtime"
"unsafe"
"cfa/native/config"
)
type remoteValidCallback struct {
callback unsafe.Pointer
}
func (r *remoteValidCallback) reportStatus(json string) {
C.fetch_report(r.callback, marshalString(json))
}
//export fetchAndValid
func fetchAndValid(callback unsafe.Pointer, path, url C.c_string, force C.int) {
go func(path, url string, callback unsafe.Pointer) {
cb := &remoteValidCallback{callback: callback}
err := config.FetchAndValid(path, url, force != 0, cb.reportStatus)
C.fetch_complete(callback, marshalString(err))
C.release_object(callback)
runtime.GC()
}(C.GoString(path), C.GoString(url), callback)
}
//export load
func load(completable unsafe.Pointer, path C.c_string) {
go func(path string) {
C.complete(completable, marshalString(config.Load(path)))
C.release_object(completable)
runtime.GC()
}(C.GoString(path))
}
//export readOverride
func readOverride(slot C.int) *C.char {
return C.CString(config.ReadOverride(config.OverrideSlot(slot)))
}
//export writeOverride
func writeOverride(slot C.int, content C.c_string) {
c := C.GoString(content)
config.WriteOverride(config.OverrideSlot(slot), c)
}
//export clearOverride
func clearOverride(slot C.int) {
config.ClearOverride(config.OverrideSlot(slot))
}
@@ -0,0 +1,41 @@
package config
var (
defaultNameServers = []string{
"223.5.5.5",
"119.29.29.29",
"8.8.4.4",
"1.0.0.1",
}
defaultFakeIPFilter = []string{
// Stun Services
"+.stun.*.*",
"+.stun.*.*.*",
"+.stun.*.*.*.*",
"+.stun.*.*.*.*.*",
// Google Voices
"lens.l.google.com",
// Nintendo Switch STUN
"*.n.n.srv.nintendo.net",
// PlayStation STUN
"+.stun.playstation.net",
// XBox
"xbox.*.*.microsoft.com",
"*.*.xboxlive.com",
// Microsoft Captive Portal
"*.msftncsi.com",
"*.msftconnecttest.com",
// Bilibili CDN
"*.mcdn.bilivideo.cn",
// Windows Default LAN WorkGroup
"WORKGROUP",
}
defaultFakeIPRange = "28.0.0.0/8"
)
@@ -0,0 +1,168 @@
package config
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
U "net/url"
"os"
P "path"
"runtime"
"time"
"cfa/native/app"
clashHttp "github.com/metacubex/mihomo/component/http"
)
type Status struct {
Action string `json:"action"`
Args []string `json:"args"`
Progress int `json:"progress"`
MaxProgress int `json:"max"`
}
func openUrl(ctx context.Context, url string) (io.ReadCloser, error) {
response, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"ClashMetaForAndroid/" + app.VersionName()}}, nil)
if err != nil {
return nil, err
}
return response.Body, nil
}
func openContent(url string) (io.ReadCloser, error) {
return app.OpenContent(url)
}
func fetch(url *U.URL, file string) error {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
var reader io.ReadCloser
var err error
switch url.Scheme {
case "http", "https":
reader, err = openUrl(ctx, url.String())
case "content":
reader, err = openContent(url.String())
default:
err = fmt.Errorf("unsupported scheme %s of %s", url.Scheme, url)
}
if err != nil {
return err
}
defer reader.Close()
_ = os.MkdirAll(P.Dir(file), 0700)
f, err := os.OpenFile(file, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, reader)
if err != nil {
_ = os.Remove(file)
}
return err
}
func FetchAndValid(
path string,
url string,
force bool,
reportStatus func(string),
) error {
configPath := P.Join(path, "config.yaml")
if _, err := os.Stat(configPath); os.IsNotExist(err) || force {
url, err := U.Parse(url)
if err != nil {
return err
}
bytes, _ := json.Marshal(&Status{
Action: "FetchConfiguration",
Args: []string{url.Host},
Progress: -1,
MaxProgress: -1,
})
reportStatus(string(bytes))
if err := fetch(url, configPath); err != nil {
return err
}
}
defer runtime.GC()
rawCfg, err := UnmarshalAndPatch(path)
if err != nil {
return err
}
forEachProviders(rawCfg, func(index int, total int, name string, provider map[string]any, prefix string) {
bytes, _ := json.Marshal(&Status{
Action: "FetchProviders",
Args: []string{name},
Progress: index,
MaxProgress: total,
})
reportStatus(string(bytes))
u, uok := provider["url"]
p, pok := provider["path"]
if !uok || !pok {
return
}
us, uok := u.(string)
ps, pok := p.(string)
if !uok || !pok {
return
}
if _, err := os.Stat(ps); err == nil {
return
}
url, err := U.Parse(us)
if err != nil {
return
}
_ = fetch(url, ps)
})
bytes, _ := json.Marshal(&Status{
Action: "Verifying",
Args: []string{},
Progress: 0xffff,
MaxProgress: 0xffff,
})
reportStatus(string(bytes))
cfg, err := Parse(rawCfg)
if err != nil {
return err
}
destroyProviders(cfg)
return nil
}
@@ -0,0 +1,96 @@
package config
import (
"os"
P "path"
"runtime"
"strings"
"gopkg.in/yaml.v3"
"cfa/native/app"
"github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/hub"
"github.com/metacubex/mihomo/log"
)
func logDns(cfg *config.RawConfig) {
bytes, err := yaml.Marshal(&cfg.DNS)
if err != nil {
log.Warnln("Marshal dns: %s", err.Error())
return
}
log.Infoln("dns:")
for _, line := range strings.Split(string(bytes), "\n") {
log.Infoln(" %s", line)
}
}
func UnmarshalAndPatch(profilePath string) (*config.RawConfig, error) {
configPath := P.Join(profilePath, "config.yaml")
configData, err := os.ReadFile(configPath)
if err != nil {
return nil, err
}
rawConfig, err := config.UnmarshalRawConfig(configData)
if err != nil {
return nil, err
}
if err := process(rawConfig, profilePath); err != nil {
return nil, err
}
return rawConfig, nil
}
func Parse(rawConfig *config.RawConfig) (*config.Config, error) {
cfg, err := config.ParseRawConfig(rawConfig)
if err != nil {
return nil, err
}
return cfg, nil
}
func Load(path string) error {
rawCfg, err := UnmarshalAndPatch(path)
if err != nil {
log.Errorln("Load %s: %s", path, err.Error())
return err
}
logDns(rawCfg)
cfg, err := Parse(rawCfg)
if err != nil {
log.Errorln("Load %s: %s", path, err.Error())
return err
}
// like hub.Parse()
hub.ApplyConfig(cfg)
app.ApplySubtitlePattern(rawCfg.ClashForAndroid.UiSubtitlePattern)
runtime.GC()
return nil
}
func LoadDefault() {
cfg, err := config.Parse([]byte{})
if err != nil {
panic(err.Error())
}
hub.ApplyConfig(cfg)
}
@@ -0,0 +1,68 @@
package config
import (
"io"
"os"
"github.com/metacubex/mihomo/constant"
)
type OverrideSlot int
const (
OverrideSlotPersist OverrideSlot = iota
OverrideSlotSession
)
const defaultPersistOverride = `{}`
const defaultSessionOverride = `{}`
var sessionOverride = defaultSessionOverride
func overridePersistPath() string {
return constant.Path.Resolve("override.json")
}
func ReadOverride(slot OverrideSlot) string {
switch slot {
case OverrideSlotPersist:
file, err := os.OpenFile(overridePersistPath(), os.O_RDONLY, 0600)
if err != nil {
return defaultPersistOverride
}
buf, err := io.ReadAll(file)
if err != nil {
return defaultPersistOverride
}
return string(buf)
case OverrideSlotSession:
return sessionOverride
}
return ""
}
func WriteOverride(slot OverrideSlot, content string) {
switch slot {
case OverrideSlotPersist:
file, err := os.OpenFile(overridePersistPath(), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
if err != nil {
return
}
_, err = file.Write([]byte(content))
case OverrideSlotSession:
sessionOverride = content
}
}
func ClearOverride(slot OverrideSlot) {
switch slot {
case OverrideSlotPersist:
_ = os.Remove(overridePersistPath())
case OverrideSlotSession:
sessionOverride = defaultSessionOverride
}
}
@@ -0,0 +1,148 @@
package config
import (
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/dlclark/regexp2"
"cfa/native/common"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/config"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
)
var processors = []processor{
patchExternalController, // must before patchOverride, so we only apply ExternalController in Override settings
patchOverride,
patchGeneral,
patchProfile,
patchDns,
patchTun,
patchListeners,
patchProviders,
validConfig,
}
type processor func(cfg *config.RawConfig, profileDir string) error
func patchOverride(cfg *config.RawConfig, _ string) error {
if err := json.NewDecoder(strings.NewReader(ReadOverride(OverrideSlotPersist))).Decode(cfg); err != nil {
log.Warnln("Apply persist override: %s", err.Error())
}
if err := json.NewDecoder(strings.NewReader(ReadOverride(OverrideSlotSession))).Decode(cfg); err != nil {
log.Warnln("Apply session override: %s", err.Error())
}
return nil
}
func patchExternalController(cfg *config.RawConfig, _ string) error {
cfg.ExternalController = ""
cfg.ExternalControllerTLS = ""
return nil
}
func patchGeneral(cfg *config.RawConfig, profileDir string) error {
cfg.Interface = ""
cfg.RoutingMark = 0
if cfg.ExternalController != "" || cfg.ExternalControllerTLS != "" {
cfg.ExternalUI = profileDir + "/ui"
}
return nil
}
func patchProfile(cfg *config.RawConfig, _ string) error {
cfg.Profile.StoreSelected = false
cfg.Profile.StoreFakeIP = true
return nil
}
func patchDns(cfg *config.RawConfig, _ string) error {
if !cfg.DNS.Enable {
cfg.DNS = config.RawDNS{
Enable: true,
UseHosts: true,
DefaultNameserver: defaultNameServers,
NameServer: defaultNameServers,
EnhancedMode: C.DNSFakeIP,
FakeIPRange: defaultFakeIPRange,
FakeIPFilter: defaultFakeIPFilter,
}
cfg.ClashForAndroid.AppendSystemDNS = true
}
if cfg.ClashForAndroid.AppendSystemDNS {
cfg.DNS.NameServer = append(cfg.DNS.NameServer, "system://")
}
return nil
}
func patchTun(cfg *config.RawConfig, _ string) error {
cfg.Tun.Enable = false
cfg.Tun.AutoRoute = false
cfg.Tun.AutoDetectInterface = false
return nil
}
func patchListeners(cfg *config.RawConfig, _ string) error {
newListeners := make([]map[string]any, 0, len(cfg.Listeners))
for _, mapping := range cfg.Listeners {
if proxyType, existType := mapping["type"].(string); existType {
switch proxyType {
case "tproxy", "redir", "tun":
continue // remove those listeners which is not supported
}
}
newListeners = append(newListeners, mapping)
}
cfg.Listeners = newListeners
return nil
}
func patchProviders(cfg *config.RawConfig, profileDir string) error {
forEachProviders(cfg, func(index int, total int, key string, provider map[string]any, prefix string) {
path, _ := provider["path"].(string)
if len(path) > 0 {
path = common.ResolveAsRoot(path)
} else if url, ok := provider["url"].(string); ok {
path = prefix + "/" + utils.MakeHash([]byte(url)).String() // same as C.GetPathByHash
} else {
return // both path and url is empty, WTF???
}
provider["path"] = profileDir + "/providers/" + path
})
return nil
}
func validConfig(cfg *config.RawConfig, _ string) error {
if len(cfg.Proxy) == 0 && len(cfg.ProxyProvider) == 0 {
return errors.New("profile does not contain `proxies` or `proxy-providers`")
}
if _, err := regexp2.Compile(cfg.ClashForAndroid.UiSubtitlePattern, 0); err != nil {
return fmt.Errorf("compile ui-subtitle-pattern: %s", err.Error())
}
return nil
}
func process(cfg *config.RawConfig, profileDir string) error {
for _, p := range processors {
if err := p(cfg, profileDir); err != nil {
return err
}
}
return nil
}
@@ -0,0 +1,43 @@
package config
import (
"io"
"github.com/metacubex/mihomo/config"
)
const (
PROXIES = "proxies"
RULES = "rules"
)
func forEachProviders(rawCfg *config.RawConfig, fun func(index int, total int, key string, provider map[string]any, prefix string)) {
total := len(rawCfg.ProxyProvider) + len(rawCfg.RuleProvider)
index := 0
for k, v := range rawCfg.ProxyProvider {
fun(index, total, k, v, PROXIES)
index++
}
for k, v := range rawCfg.RuleProvider {
fun(index, total, k, v, RULES)
index++
}
}
func destroyProviders(cfg *config.Config) {
for _, p := range cfg.Providers {
if p, ok := p.(io.Closer); ok {
_ = p.Close()
}
}
for _, p := range cfg.RuleProviders {
if p, ok := p.(io.Closer); ok {
_ = p.Close()
}
}
}
@@ -0,0 +1,18 @@
// +build debug
package main
import (
"net/http"
_ "net/http/pprof"
"github.com/metacubex/mihomo/log"
)
func init() {
go func() {
log.Debugln("pprof service listen at: 0.0.0.0:8888")
_ = http.ListenAndServe("0.0.0.0:8888", nil)
}()
}
@@ -0,0 +1,59 @@
package delegate
import (
"errors"
"fmt"
"strings"
"syscall"
"github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/log"
"cfa/native/app"
"cfa/native/platform"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/constant"
)
var errBlocked = errors.New("blocked")
func Init(home, versionName, gitVersion string, platformVersion int) {
log.Infoln("Init core, home: %s, versionName: %s, gitVersion: %s, platformVersion: %d", home, versionName, gitVersion, platformVersion)
constant.SetHomeDir(home)
// gitVersion = ${CURRENT_BRANCH}_${COMMIT_HASH}_${COMPILE_TIME}
if versions := strings.Split(gitVersion, "_"); len(versions) == 3 {
constant.Version = fmt.Sprintf("%s-%s-CMFA-%s", strings.ToLower(versions[0]), versions[1], strings.ToLower(versionName))
constant.BuildTime = versions[2]
} else {
constant.Version = gitVersion
}
constant.Version = strings.ToLower(constant.Version)
app.ApplyVersionName(versionName)
app.ApplyPlatformVersion(platformVersion)
process.DefaultPackageNameResolver = func(metadata *constant.Metadata) (string, error) {
src, dst := metadata.RawSrcAddr, metadata.RawDstAddr
if src == nil || dst == nil {
return "", process.ErrInvalidNetwork
}
uid := app.QuerySocketUid(metadata.RawSrcAddr, metadata.RawDstAddr)
pkg := app.QueryAppByUid(uid)
log.Debugln("[PKG] %s --> %s by %d[%s]", metadata.SourceAddress(), metadata.RemoteAddress(), uid, pkg)
return pkg, nil
}
dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error {
if platform.ShouldBlockConnection() {
return errBlocked
}
return conn.Control(func(fd uintptr) {
app.MarkSocket(int(fd))
})
}
}
@@ -0,0 +1,72 @@
package main
//#include "bridge.h"
import "C"
import (
"strings"
"time"
"unsafe"
"github.com/metacubex/mihomo/log"
)
type message struct {
Level string `json:"level"`
Message string `json:"message"`
Time int64 `json:"time"`
}
func init() {
go func() {
sub := log.Subscribe()
defer log.UnSubscribe(sub)
for msg := range sub {
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)
}
}
}()
}
//export subscribeLogcat
func subscribeLogcat(remote unsafe.Pointer) {
go func(remote unsafe.Pointer) {
sub := log.Subscribe()
defer log.UnSubscribe(sub)
for msg := range sub {
if msg.LogLevel < log.Level() && !strings.HasPrefix(msg.Payload, "[APP]") {
continue
}
rMsg := &message{
Level: msg.LogLevel.String(),
Message: msg.Payload,
Time: time.Now().UnixNano() / 1000 / 1000,
}
if C.logcat_received(remote, marshalJson(rMsg)) != 0 {
C.release_object(remote)
log.Debugln("Logcat subscriber closed")
break
}
}
}(remote)
log.Infoln("[APP] Logcat level: %s", log.Level().String())
}
@@ -0,0 +1,55 @@
package main
/*
#cgo LDFLAGS: -llog
#include "bridge.h"
*/
import "C"
import (
"runtime"
"runtime/debug"
"cfa/native/config"
"cfa/native/delegate"
"cfa/native/tunnel"
"github.com/metacubex/mihomo/log"
)
func main() {
panic("Stub!")
}
//export coreInit
func coreInit(home, versionName, gitVersion C.c_string, sdkVersion C.int) {
h := C.GoString(home)
v := C.GoString(versionName)
g := C.GoString(gitVersion)
s := int(sdkVersion)
delegate.Init(h, v, g, s)
reset()
}
//export reset
func reset() {
config.LoadDefault()
tunnel.ResetStatistic()
tunnel.CloseAllConnections()
runtime.GC()
debug.FreeOSMemory()
}
//export forceGc
func forceGc() {
go func() {
log.Infoln("[APP] request force GC")
runtime.GC()
debug.FreeOSMemory()
}()
}
@@ -0,0 +1,42 @@
// +build linux
package platform
import "syscall"
var nullFd int
var maxFdCount int
func init() {
fd, err := syscall.Open("/dev/null", syscall.O_WRONLY, 0644)
if err != nil {
panic(err.Error())
}
nullFd = fd
var limit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
maxFdCount = 1024
} else {
maxFdCount = int(limit.Cur)
}
maxFdCount = maxFdCount / 4 * 3
}
func ShouldBlockConnection() bool {
fd, err := syscall.Dup(nullFd)
if err != nil {
return true
}
_ = syscall.Close(fd)
if fd > maxFdCount {
return true
}
return false
}
@@ -0,0 +1,171 @@
// +build linux
package platform
import (
"bufio"
"encoding/binary"
"encoding/hex"
"fmt"
"net"
"os"
"strconv"
"strings"
"unsafe"
)
var netIndexOfLocal = -1
var netIndexOfUid = -1
var nativeEndian binary.ByteOrder
func QuerySocketUidFromProcFs(source, _ net.Addr) int {
if netIndexOfLocal < 0 || netIndexOfUid < 0 {
return -1
}
network := source.Network()
if strings.HasSuffix(network, "4") || strings.HasSuffix(network, "6") {
network = network[:len(network)-1]
}
path := "/proc/net/" + network
var sIP net.IP
var sPort int
switch s := source.(type) {
case *net.TCPAddr:
sIP = s.IP
sPort = s.Port
case *net.UDPAddr:
sIP = s.IP
sPort = s.Port
default:
return -1
}
sIP = sIP.To16()
if sIP == nil {
return -1
}
uid := doQuery(path+"6", sIP, sPort)
if uid == -1 {
sIP = sIP.To4()
if sIP == nil {
return -1
}
uid = doQuery(path, sIP, sPort)
}
return uid
}
func doQuery(path string, sIP net.IP, sPort int) int {
file, err := os.Open(path)
if err != nil {
return -1
}
defer file.Close()
reader := bufio.NewReader(file)
var bytes [2]byte
binary.BigEndian.PutUint16(bytes[:], uint16(sPort))
local := fmt.Sprintf("%s:%s", hex.EncodeToString(nativeEndianIP(sIP)), hex.EncodeToString(bytes[:]))
for {
row, _, err := reader.ReadLine()
if err != nil {
return -1
}
fields := strings.Fields(string(row))
if len(fields) <= netIndexOfLocal || len(fields) <= netIndexOfUid {
continue
}
if strings.EqualFold(local, fields[netIndexOfLocal]) {
uid, err := strconv.Atoi(fields[netIndexOfUid])
if err != nil {
return -1
}
return uid
}
}
}
func nativeEndianIP(ip net.IP) []byte {
result := make([]byte, len(ip))
for i := 0; i < len(ip); i += 4 {
value := binary.BigEndian.Uint32(ip[i:])
nativeEndian.PutUint32(result[i:], value)
}
return result
}
func init() {
file, err := os.Open("/proc/net/tcp")
if err != nil {
return
}
defer file.Close()
reader := bufio.NewReader(file)
header, _, err := reader.ReadLine()
if err != nil {
return
}
columns := strings.Fields(string(header))
var txQueue, rxQueue, tr, tmWhen bool
for idx, col := range columns {
offset := 0
if txQueue && rxQueue {
offset--
}
if tr && tmWhen {
offset--
}
switch col {
case "tx_queue":
txQueue = true
case "rx_queue":
rxQueue = true
case "tr":
tr = true
case "tm->when":
tmWhen = true
case "local_address":
netIndexOfLocal = idx + offset
case "uid":
netIndexOfUid = idx + offset
}
}
}
func init() {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
nativeEndian = binary.BigEndian
} else {
nativeEndian = binary.LittleEndian
}
}
@@ -0,0 +1,25 @@
package main
//#include "bridge.h"
import "C"
import (
"cfa/native/proxy"
)
//export startHttp
func startHttp(listenAt C.c_string) *C.char {
l := C.GoString(listenAt)
listen, err := proxy.Start(l)
if err != nil {
return nil
}
return C.CString(listen)
}
//export stopHttp
func stopHttp() {
proxy.Stop()
}
@@ -0,0 +1,40 @@
package proxy
import (
"sync"
"github.com/metacubex/mihomo/listener/http"
"github.com/metacubex/mihomo/tunnel"
)
var listener *http.Listener
var lock sync.Mutex
func Start(listen string) (listenAt string, err error) {
lock.Lock()
defer lock.Unlock()
stopLocked()
listener, err = http.NewWithAuthenticate(listen, tunnel.Tunnel, false)
if err == nil {
listenAt = listener.Address()
}
return
}
func Stop() {
lock.Lock()
defer lock.Unlock()
stopLocked()
}
func stopLocked() {
if listener != nil {
listener.Close()
}
listener = nil
}
@@ -0,0 +1,9 @@
#include "trace.h"
#if ENABLE_TRACE
void trace_method_exit(const char **name) {
__android_log_print(ANDROID_LOG_VERBOSE, TAG, "TRACE-OUT %s", *name);
}
#endif
@@ -0,0 +1,19 @@
#pragma once
#include "bridge.h"
#include <android/log.h>
#define ENABLE_TRACE 0
#if ENABLE_TRACE
extern void trace_method_exit(const char **name);
#define TRACE_METHOD() __attribute__((cleanup(trace_method_exit))) const char *__method_name = __FUNCTION__; __android_log_print(ANDROID_LOG_VERBOSE, TAG, "TRACE-IN %s", __method_name)
#else
#define TRACE_METHOD()
#endif
@@ -0,0 +1,109 @@
package main
//#include "bridge.h"
import "C"
import (
"context"
"io"
"sync"
"unsafe"
"golang.org/x/sync/semaphore"
"cfa/native/app"
"cfa/native/tun"
)
var rTunLock sync.Mutex
var rTun *remoteTun
type remoteTun struct {
closer io.Closer
callback unsafe.Pointer
closed bool
limit *semaphore.Weighted
}
func (t *remoteTun) markSocket(fd int) {
_ = t.limit.Acquire(context.Background(), 1)
defer t.limit.Release(1)
if t.closed {
return
}
C.mark_socket(t.callback, C.int(fd))
}
func (t *remoteTun) querySocketUid(protocol int, source, target string) int {
_ = t.limit.Acquire(context.Background(), 1)
defer t.limit.Release(1)
if t.closed {
return -1
}
return int(C.query_socket_uid(t.callback, C.int(protocol), C.CString(source), C.CString(target)))
}
func (t *remoteTun) close() {
_ = t.limit.Acquire(context.TODO(), 4)
defer t.limit.Release(4)
t.closed = true
if t.closer != nil {
_ = t.closer.Close()
}
app.ApplyTunContext(nil, nil)
C.release_object(t.callback)
}
//export startTun
func startTun(fd C.int, stack, gateway, portal, dns C.c_string, callback unsafe.Pointer) C.int {
rTunLock.Lock()
defer rTunLock.Unlock()
if rTun != nil {
rTun.close()
rTun = nil
}
f := int(fd)
s := C.GoString(stack)
g := C.GoString(gateway)
p := C.GoString(portal)
d := C.GoString(dns)
remote := &remoteTun{callback: callback, closed: false, limit: semaphore.NewWeighted(4)}
app.ApplyTunContext(remote.markSocket, remote.querySocketUid)
closer, err := tun.Start(f, s, g, p, d)
if err != nil {
remote.close()
return 1
}
remote.closer = closer
rTun = remote
return 0
}
//export stopTun
func stopTun() {
rTunLock.Lock()
defer rTunLock.Unlock()
if rTun != nil {
rTun.close()
rTun = nil
}
}
@@ -0,0 +1,77 @@
package tun
import (
"encoding/json"
"io"
"net"
"net/netip"
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing_tun"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
)
func Start(fd int, stack, gateway, portal, dns string) (io.Closer, error) {
log.Debugln("TUN: fd = %d, stack = %s, gateway = %s, portal = %s, dns = %s", fd, stack, gateway, portal, dns)
tunStack, ok := C.StackTypeMapping[strings.ToLower(stack)]
if !ok {
tunStack = C.TunSystem
}
var prefix4 []netip.Prefix
var prefix6 []netip.Prefix
for _, gatewayStr := range strings.Split(gateway, ",") { // "172.19.0.1/30" or "172.19.0.1/30,fdfe:dcba:9876::1/126"
gatewayStr = strings.TrimSpace(gatewayStr)
if len(gatewayStr) == 0 {
continue
}
prefix, err := netip.ParsePrefix(gatewayStr)
if err != nil {
log.Errorln("TUN:", err)
return nil, err
}
if prefix.Addr().Is4() {
prefix4 = append(prefix4, prefix)
} else {
prefix6 = append(prefix6, prefix)
}
}
var dnsHijack []string
for _, dnsStr := range strings.Split(dns, ",") { // "172.19.0.2" or "0.0.0.0"
dnsStr = strings.TrimSpace(dnsStr)
if len(dnsStr) == 0 {
continue
}
dnsHijack = append(dnsHijack, net.JoinHostPort(dnsStr, "53"))
}
options := LC.Tun{
Enable: true,
Device: sing_tun.InterfaceName,
Stack: tunStack,
DNSHijack: dnsHijack,
AutoRoute: false, // had set route in TunService.kt
AutoDetectInterface: false, // implements by VpnService::protect
Inet4Address: prefix4,
Inet6Address: prefix6,
MTU: 9000, // private const val TUN_MTU = 9000 in TunService.kt
FileDescriptor: fd,
}
tunOptions, _ := json.Marshal(options)
log.Debugln(string(tunOptions))
listener, err := sing_tun.New(options, tunnel.Tunnel)
if err != nil {
log.Errorln("TUN:", err)
return nil, err
}
return listener, nil
}
@@ -0,0 +1,111 @@
package main
//#include "bridge.h"
import "C"
import (
"unsafe"
"cfa/native/app"
"cfa/native/tunnel"
)
//export queryTunnelState
func queryTunnelState() *C.char {
mode := tunnel.QueryMode()
response := &struct {
Mode string `json:"mode"`
}{mode}
return marshalJson(response)
}
//export queryNow
func queryNow(upload, download *C.uint64_t) {
up, down := tunnel.Now()
*upload = C.uint64_t(up)
*download = C.uint64_t(down)
}
//export queryTotal
func queryTotal(upload, download *C.uint64_t) {
up, down := tunnel.Total()
*upload = C.uint64_t(up)
*download = C.uint64_t(down)
}
//export queryGroupNames
func queryGroupNames(excludeNotSelectable C.int) *C.char {
return marshalJson(tunnel.QueryProxyGroupNames(excludeNotSelectable != 0))
}
//export queryGroup
func queryGroup(name C.c_string, sortMode C.c_string) *C.char {
n := C.GoString(name)
s := C.GoString(sortMode)
mode := tunnel.Default
switch s {
case "Title":
mode = tunnel.Title
case "Delay":
mode = tunnel.Delay
}
response := tunnel.QueryProxyGroup(n, mode, app.SubtitlePattern())
if response == nil {
return nil
}
return marshalJson(response)
}
//export healthCheck
func healthCheck(completable unsafe.Pointer, name C.c_string) {
go func(name string) {
tunnel.HealthCheck(name)
C.complete(completable, nil)
}(C.GoString(name))
}
//export healthCheckAll
func healthCheckAll() {
tunnel.HealthCheckAll()
}
//export patchSelector
func patchSelector(selector, name C.c_string) C.int {
s := C.GoString(selector)
n := C.GoString(name)
if tunnel.PatchSelector(s, n) {
return 1
}
return 0
}
//export queryProviders
func queryProviders() *C.char {
return marshalJson(tunnel.QueryProviders())
}
//export updateProvider
func updateProvider(completable unsafe.Pointer, pType C.c_string, name C.c_string) {
go func(pType, name string) {
C.complete(completable, marshalString(tunnel.UpdateProvider(pType, name)))
C.release_object(completable)
}(C.GoString(pType), C.GoString(name))
}
//export suspend
func suspend(suspended C.int) {
tunnel.Suspend(suspended != 0)
}
@@ -0,0 +1,34 @@
package tunnel
import (
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/tunnel/statistic"
)
func CloseAllConnections() {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
_ = c.Close()
return true
})
}
func closeMatch(filter func(conn C.Connection) bool) {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
if filter(c) {
_ = c.Close()
}
return true
})
}
func closeConnByGroup(name string) {
closeMatch(func(conn C.Connection) bool {
for _, c := range conn.Chains() {
if c == name {
return true
}
}
return false
})
}
@@ -0,0 +1,49 @@
package tunnel
import (
"sync"
"github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
)
func HealthCheck(name string) {
p := tunnel.Proxies()[name]
if p == nil {
log.Warnln("Request health check for `%s`: not found", name)
return
}
g, ok := p.Adapter().(outboundgroup.ProxyGroup)
if !ok {
log.Warnln("Request health check for `%s`: invalid type %s", name, p.Type().String())
return
}
wg := &sync.WaitGroup{}
for _, pr := range g.Providers() {
wg.Add(1)
go func(provider provider.ProxyProvider) {
provider.HealthCheck()
wg.Done()
}(pr)
}
wg.Wait()
}
func HealthCheckAll() {
for _, g := range QueryProxyGroupNames(false) {
go func(group string) {
HealthCheck(group)
}(g)
}
}
@@ -0,0 +1,7 @@
package tunnel
import (
"net/netip"
)
var loopback = netip.MustParseAddr("127.0.0.1")
@@ -0,0 +1,93 @@
package tunnel
import (
"errors"
"fmt"
"time"
"github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
)
var ErrInvalidType = errors.New("invalid type")
type Provider struct {
Name string `json:"name"`
VehicleType string `json:"vehicleType"`
Type string `json:"type"`
UpdatedAt int64 `json:"updatedAt"`
}
type UpdatableProvider interface {
UpdatedAt() time.Time
}
func QueryProviders() []*Provider {
r := tunnel.RuleProviders()
p := tunnel.Providers()
providers := make([]provider.Provider, 0, len(r)+len(p))
for _, rule := range r {
if rule.VehicleType() == provider.Compatible {
continue
}
providers = append(providers, rule)
}
for _, proxy := range p {
if proxy.VehicleType() == provider.Compatible {
continue
}
providers = append(providers, proxy)
}
result := make([]*Provider, 0, len(providers))
for _, p := range providers {
updatedAt := time.Time{}
if s, ok := p.(UpdatableProvider); ok {
updatedAt = s.UpdatedAt()
}
result = append(result, &Provider{
Name: p.Name(),
VehicleType: p.VehicleType().String(),
Type: p.Type().String(),
UpdatedAt: updatedAt.UnixNano() / 1000 / 1000,
})
}
return result
}
func UpdateProvider(t string, name string) error {
err := ErrInvalidType
switch t {
case "Rule":
p := tunnel.RuleProviders()[name]
if p == nil {
return fmt.Errorf("%s not found", name)
}
err = p.Update()
case "Proxy":
p := tunnel.Providers()[name]
if p == nil {
return fmt.Errorf("%s not found", name)
}
err = p.Update()
}
if err != nil {
log.Warnln("Updating provider %s: %s", name, err.Error())
}
return err
}
@@ -0,0 +1,240 @@
package tunnel
import (
"sort"
"strings"
"github.com/dlclark/regexp2"
"github.com/metacubex/mihomo/adapter/outboundgroup"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
)
type SortMode int
const (
Default SortMode = iota
Title
Delay
)
type Proxy struct {
Name string `json:"name"`
Title string `json:"title"`
Subtitle string `json:"subtitle"`
Type string `json:"type"`
Delay int `json:"delay"`
}
type ProxyGroup struct {
Type string `json:"type"`
Now string `json:"now"`
Proxies []*Proxy `json:"proxies"`
}
type sortableProxyList struct {
list []*Proxy
less func(a, b *Proxy) bool
}
func (s *sortableProxyList) Len() int {
return len(s.list)
}
func (s *sortableProxyList) Less(i, j int) bool {
return s.less(s.list[i], s.list[j])
}
func (s *sortableProxyList) Swap(i, j int) {
s.list[i], s.list[j] = s.list[j], s.list[i]
}
func QueryProxyGroupNames(excludeNotSelectable bool) []string {
mode := tunnel.Mode()
if mode == tunnel.Direct {
return []string{}
}
global := tunnel.Proxies()["GLOBAL"].Adapter().(outboundgroup.ProxyGroup)
proxies := global.Providers()[0].Proxies()
result := make([]string, 0, len(proxies)+1)
if mode == tunnel.Global {
result = append(result, "GLOBAL")
}
for _, p := range proxies {
if _, ok := p.Adapter().(outboundgroup.ProxyGroup); ok {
if !excludeNotSelectable || p.Type() == C.Selector {
result = append(result, p.Name())
}
}
}
return result
}
func QueryProxyGroup(name string, sortMode SortMode, uiSubtitlePattern *regexp2.Regexp) *ProxyGroup {
p := tunnel.Proxies()[name]
if p == nil {
log.Warnln("Query group `%s`: not found", name)
return nil
}
g, ok := p.Adapter().(outboundgroup.ProxyGroup)
if !ok {
log.Warnln("Query group `%s`: invalid type %s", name, p.Type().String())
return nil
}
proxies := convertProxies(g.Proxies(), uiSubtitlePattern)
// proxies := collectProviders(g.Providers(), uiSubtitlePattern)
switch sortMode {
case Title:
wrapper := &sortableProxyList{
list: proxies,
less: func(a, b *Proxy) bool {
return strings.Compare(a.Title, b.Title) < 0
},
}
sort.Sort(wrapper)
case Delay:
wrapper := &sortableProxyList{
list: proxies,
less: func(a, b *Proxy) bool {
return a.Delay < b.Delay
},
}
sort.Sort(wrapper)
case Default:
default:
}
return &ProxyGroup{
Type: g.Type().String(),
Now: g.Now(),
Proxies: proxies,
}
}
func PatchSelector(selector, name string) bool {
p := tunnel.Proxies()[selector]
if p == nil {
log.Warnln("Patch selector `%s`: not found", selector)
return false
}
g, ok := p.Adapter().(outboundgroup.ProxyGroup)
if !ok {
log.Warnln("Patch selector `%s`: invalid type %s", selector, p.Type().String())
return false
}
s, ok := g.(outboundgroup.SelectAble)
if !ok {
log.Warnln("Patch selector `%s`: invalid type %s", selector, p.Type().String())
return false
}
if err := s.Set(name); err != nil {
log.Warnln("Patch selector `%s`: %s", selector, err.Error())
}
log.Infoln("Patch selector %s -> %s", selector, name)
closeConnByGroup(selector)
return true
}
func convertProxies(proxies []C.Proxy, uiSubtitlePattern *regexp2.Regexp) []*Proxy {
result := make([]*Proxy, 0, 128)
for _, p := range proxies {
name := p.Name()
title := name
subtitle := p.Type().String()
if uiSubtitlePattern != nil {
if _, ok := p.Adapter().(outboundgroup.ProxyGroup); !ok {
runes := []rune(name)
match, err := uiSubtitlePattern.FindRunesMatch(runes)
if err == nil && match != nil {
title = string(runes[:match.Index]) + string(runes[match.Index+match.Length:])
subtitle = string(runes[match.Index : match.Index+match.Length])
}
}
}
testURL := "https://www.gstatic.com/generate_204"
for k := range p.ExtraDelayHistories() {
if len(k) > 0 {
testURL = k
break
}
}
result = append(result, &Proxy{
Name: name,
Title: strings.TrimSpace(title),
Subtitle: strings.TrimSpace(subtitle),
Type: p.Type().String(),
Delay: int(p.LastDelayForTestUrl(testURL)),
})
}
return result
}
func collectProviders(providers []provider.ProxyProvider, uiSubtitlePattern *regexp2.Regexp) []*Proxy {
result := make([]*Proxy, 0, 128)
for _, p := range providers {
for _, px := range p.Proxies() {
name := px.Name()
title := name
subtitle := px.Type().String()
if uiSubtitlePattern != nil {
if _, ok := px.Adapter().(outboundgroup.ProxyGroup); !ok {
runes := []rune(name)
match, err := uiSubtitlePattern.FindRunesMatch(runes)
if err == nil && match != nil {
title = string(runes[:match.Index]) + string(runes[match.Index+match.Length:])
subtitle = string(runes[match.Index : match.Index+match.Length])
}
}
}
testURL := "https://www.gstatic.com/generate_204"
for k := range px.ExtraDelayHistories() {
if len(k) > 0 {
testURL = k
break
}
}
result = append(result, &Proxy{
Name: name,
Title: strings.TrimSpace(title),
Subtitle: strings.TrimSpace(subtitle),
Type: px.Type().String(),
Delay: int(px.LastDelayForTestUrl(testURL)),
})
}
}
return result
}
@@ -0,0 +1,9 @@
package tunnel
import (
"github.com/metacubex/mihomo/tunnel"
)
func QueryMode() string {
return tunnel.Mode().String()
}
@@ -0,0 +1,17 @@
package tunnel
import (
"github.com/metacubex/mihomo/tunnel/statistic"
)
func ResetStatistic() {
statistic.DefaultManager.ResetStatistic()
}
func Now() (up int64, down int64) {
return statistic.DefaultManager.Now()
}
func Total() (up int64, down int64) {
return statistic.DefaultManager.Total()
}
@@ -0,0 +1,9 @@
package tunnel
func Suspend(s bool) {
// cause by ACTION_SCREEN_OFF/ACTION_SCREEN_ON,
// but we don't know what should do so just ignored.
//
// WARNING: don't call core's Tunnel.OnSuspend/OnRunning at here,
// this will cause the core to stop processing new incoming connections when the screen is locked.
}
@@ -0,0 +1,32 @@
package main
import "C"
import (
"encoding/json"
"reflect"
)
func marshalJson(obj any) *C.char {
res, err := json.Marshal(obj)
if err != nil {
panic(err.Error())
}
return C.CString(string(res))
}
func marshalString(obj any) *C.char {
if obj == nil {
return nil
}
switch o := obj.(type) {
case error:
return C.CString(o.Error())
case string:
return C.CString(o)
}
panic("invalid marshal type " + reflect.TypeOf(obj).Name())
}
@@ -0,0 +1,228 @@
package com.github.kr328.clash.core
import com.github.kr328.clash.core.bridge.*
import com.github.kr328.clash.core.model.*
import com.github.kr328.clash.core.util.parseInetSocketAddress
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.jsonPrimitive
import java.io.File
import java.net.InetSocketAddress
object Clash {
enum class OverrideSlot {
Persist, Session
}
private val ConfigurationOverrideJson = Json {
ignoreUnknownKeys = true
encodeDefaults = false
}
fun reset() {
Bridge.nativeReset()
}
fun forceGc() {
Bridge.nativeForceGc()
}
fun suspendCore(suspended: Boolean) {
Bridge.nativeSuspend(suspended)
}
fun queryTunnelState(): TunnelState {
val json = Bridge.nativeQueryTunnelState()
return Json.decodeFromString(TunnelState.serializer(), json)
}
fun queryTrafficNow(): Traffic {
return Bridge.nativeQueryTrafficNow()
}
fun queryTrafficTotal(): Traffic {
return Bridge.nativeQueryTrafficTotal()
}
fun notifyDnsChanged(dns: List<String>) {
Bridge.nativeNotifyDnsChanged(dns.joinToString(separator = ","))
}
fun notifyTimeZoneChanged(name: String, offset: Int) {
Bridge.nativeNotifyTimeZoneChanged(name, offset)
}
fun notifyInstalledAppsChanged(uids: List<Pair<Int, String>>) {
val uidList = uids.joinToString(separator = ",") { "${it.first}:${it.second}" }
Bridge.nativeNotifyInstalledAppChanged(uidList)
}
fun startTun(
fd: Int,
stack: String,
gateway: String,
portal: String,
dns: String,
markSocket: (Int) -> Boolean,
querySocketUid: (protocol: Int, source: InetSocketAddress, target: InetSocketAddress) -> Int
) {
Bridge.nativeStartTun(fd, stack, gateway, portal, dns, object : TunInterface {
override fun markSocket(fd: Int) {
markSocket(fd)
}
override fun querySocketUid(protocol: Int, source: String, target: String): Int {
return querySocketUid(
protocol,
parseInetSocketAddress(source),
parseInetSocketAddress(target)
)
}
})
}
fun stopTun() {
Bridge.nativeStopTun()
}
fun startHttp(listenAt: String): String? {
return Bridge.nativeStartHttp(listenAt)
}
fun stopHttp() {
Bridge.nativeStopHttp()
}
fun queryGroupNames(excludeNotSelectable: Boolean): List<String> {
val names = Json.Default.decodeFromString(
JsonArray.serializer(),
Bridge.nativeQueryGroupNames(excludeNotSelectable)
)
return names.map {
require(it.jsonPrimitive.isString)
it.jsonPrimitive.content
}
}
fun queryGroup(name: String, sort: ProxySort): ProxyGroup {
return Bridge.nativeQueryGroup(name, sort.name)
?.let { Json.Default.decodeFromString(ProxyGroup.serializer(), it) }
?: ProxyGroup(Proxy.Type.Unknown, emptyList(), "")
}
fun healthCheck(name: String): CompletableDeferred<Unit> {
return CompletableDeferred<Unit>().apply {
Bridge.nativeHealthCheck(this, name)
}
}
fun healthCheckAll() {
Bridge.nativeHealthCheckAll()
}
fun patchSelector(selector: String, name: String): Boolean {
return Bridge.nativePatchSelector(selector, name)
}
fun fetchAndValid(
path: File,
url: String,
force: Boolean,
reportStatus: (FetchStatus) -> Unit
): CompletableDeferred<Unit> {
return CompletableDeferred<Unit>().apply {
Bridge.nativeFetchAndValid(
object : FetchCallback {
override fun report(statusJson: String) {
reportStatus(
Json.Default.decodeFromString(
FetchStatus.serializer(),
statusJson
)
)
}
override fun complete(error: String?) {
if (error != null)
completeExceptionally(ClashException(error))
else
complete(Unit)
}
},
path.absolutePath,
url,
force
)
}
}
fun load(path: File): CompletableDeferred<Unit> {
return CompletableDeferred<Unit>().apply {
Bridge.nativeLoad(this, path.absolutePath)
}
}
fun queryProviders(): List<Provider> {
val providers =
Json.Default.decodeFromString(JsonArray.serializer(), Bridge.nativeQueryProviders())
return List(providers.size) {
Json.Default.decodeFromJsonElement(Provider.serializer(), providers[it])
}
}
fun updateProvider(type: Provider.Type, name: String): CompletableDeferred<Unit> {
return CompletableDeferred<Unit>().apply {
Bridge.nativeUpdateProvider(this, type.toString(), name)
}
}
fun queryOverride(slot: OverrideSlot): ConfigurationOverride {
return try {
ConfigurationOverrideJson.decodeFromString(
ConfigurationOverride.serializer(),
Bridge.nativeReadOverride(slot.ordinal)
)
} catch (e: Exception) {
ConfigurationOverride()
}
}
fun patchOverride(slot: OverrideSlot, configuration: ConfigurationOverride) {
Bridge.nativeWriteOverride(
slot.ordinal,
ConfigurationOverrideJson.encodeToString(
ConfigurationOverride.serializer(),
configuration
)
)
}
fun clearOverride(slot: OverrideSlot) {
Bridge.nativeClearOverride(slot.ordinal)
}
fun queryConfiguration(): UiConfiguration {
return Json.Default.decodeFromString(
UiConfiguration.serializer(),
Bridge.nativeQueryConfiguration()
)
}
fun subscribeLogcat(): ReceiveChannel<LogMessage> {
return Channel<LogMessage>(32).apply {
Bridge.nativeSubscribeLogcat(object : LogcatInterface {
override fun received(jsonPayload: String) {
trySend(Json.decodeFromString(LogMessage.serializer(), jsonPayload))
}
})
}
}
}
@@ -0,0 +1,71 @@
package com.github.kr328.clash.core.bridge
import android.os.Build
import android.os.ParcelFileDescriptor
import androidx.annotation.Keep
import com.github.kr328.clash.common.Global
import com.github.kr328.clash.common.log.Log
import kotlinx.coroutines.CompletableDeferred
import java.io.File
@Keep
object Bridge {
external fun nativeReset()
external fun nativeForceGc()
external fun nativeSuspend(suspend: Boolean)
external fun nativeQueryTunnelState(): String
external fun nativeQueryTrafficNow(): Long
external fun nativeQueryTrafficTotal(): Long
external fun nativeNotifyDnsChanged(dnsList: String)
external fun nativeNotifyTimeZoneChanged(name: String, offset: Int)
external fun nativeNotifyInstalledAppChanged(uidList: String)
external fun nativeStartTun(fd: Int, stack: String, gateway: String, portal: String, dns: String, cb: TunInterface)
external fun nativeStopTun()
external fun nativeStartHttp(listenAt: String): String?
external fun nativeStopHttp()
external fun nativeQueryGroupNames(excludeNotSelectable: Boolean): String
external fun nativeQueryGroup(name: String, sort: String): String?
external fun nativeHealthCheck(completable: CompletableDeferred<Unit>, name: String)
external fun nativeHealthCheckAll()
external fun nativePatchSelector(selector: String, name: String): Boolean
external fun nativeFetchAndValid(
completable: FetchCallback,
path: String,
url: String,
force: Boolean
)
external fun nativeLoad(completable: CompletableDeferred<Unit>, path: String)
external fun nativeQueryProviders(): String
external fun nativeUpdateProvider(
completable: CompletableDeferred<Unit>,
type: String,
name: String
)
external fun nativeReadOverride(slot: Int): String
external fun nativeWriteOverride(slot: Int, content: String)
external fun nativeClearOverride(slot: Int)
external fun nativeQueryConfiguration(): String
external fun nativeSubscribeLogcat(callback: LogcatInterface)
external fun nativeCoreVersion(): String
private external fun nativeInit(home: String, versionName: String, sdkVersion: Int)
init {
System.loadLibrary("bridge")
val ctx = Global.application
ParcelFileDescriptor.open(File(ctx.packageCodePath), ParcelFileDescriptor.MODE_READ_ONLY)
.detachFd()
val home = ctx.filesDir.resolve("clash").apply { mkdirs() }.absolutePath
val versionName = ctx.packageManager.getPackageInfo(ctx.packageName, 0).versionName
val sdkVersion = Build.VERSION.SDK_INT
Log.d("Home = $home")
nativeInit(home, versionName, sdkVersion)
}
}
@@ -0,0 +1,6 @@
package com.github.kr328.clash.core.bridge
import androidx.annotation.Keep
@Keep
class ClashException(msg: String) : IllegalArgumentException(msg)
@@ -0,0 +1,21 @@
package com.github.kr328.clash.core.bridge
import android.net.Uri
import androidx.annotation.Keep
import com.github.kr328.clash.common.Global
import java.io.FileNotFoundException
@Keep
object Content {
@JvmStatic
fun open(url: String): Int {
val uri = Uri.parse(url)
if (uri.scheme != "content") {
throw UnsupportedOperationException("Unsupported scheme ${uri.scheme}")
}
return Global.application.contentResolver.openFileDescriptor(uri, "r")?.detachFd()
?: throw FileNotFoundException("$uri not found")
}
}
@@ -0,0 +1,9 @@
package com.github.kr328.clash.core.bridge
import androidx.annotation.Keep
@Keep
interface FetchCallback {
fun report(statusJson: String)
fun complete(error: String?)
}
@@ -0,0 +1,8 @@
package com.github.kr328.clash.core.bridge
import androidx.annotation.Keep
@Keep
interface LogcatInterface {
fun received(jsonPayload: String)
}
@@ -0,0 +1,9 @@
package com.github.kr328.clash.core.bridge
import androidx.annotation.Keep
@Keep
interface TunInterface {
fun markSocket(fd: Int)
fun querySocketUid(protocol: Int, source: String, target: String): Int
}
@@ -0,0 +1,242 @@
package com.github.kr328.clash.core.model
import android.os.Parcel
import android.os.Parcelable
import com.github.kr328.clash.core.util.Parcelizer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ConfigurationOverride(
@SerialName("port")
var httpPort: Int? = null,
@SerialName("socks-port")
var socksPort: Int? = null,
@SerialName("redir-port")
var redirectPort: Int? = null,
@SerialName("tproxy-port")
var tproxyPort: Int? = null,
@SerialName("mixed-port")
var mixedPort: Int? = null,
@SerialName("authentication")
var authentication: List<String>? = null,
@SerialName("allow-lan")
var allowLan: Boolean? = null,
@SerialName("bind-address")
var bindAddress: String? = null,
@SerialName("mode")
var mode: TunnelState.Mode? = null,
@SerialName("log-level")
var logLevel: LogMessage.Level? = null,
@SerialName("ipv6")
var ipv6: Boolean? = null,
@SerialName("external-controller")
var externalController: String? = null,
@SerialName("external-controller-tls")
var externalControllerTLS: String? = null,
@SerialName("external-controller-cors")
var externalControllerCors: ExternalControllerCors = ExternalControllerCors(),
@SerialName("secret")
var secret: String? = null,
@SerialName("hosts")
var hosts: Map<String, String>? = null,
@SerialName("unified-delay")
var unifiedDelay: Boolean? = null,
@SerialName("geodata-mode")
var geodataMode: Boolean? = null,
@SerialName("tcp-concurrent")
var tcpConcurrent: Boolean? = null,
@SerialName("find-process-mode")
var findProcessMode: FindProcessMode? = null,
@SerialName("dns")
val dns: Dns = Dns(),
@SerialName("clash-for-android")
val app: App = App(),
@SerialName("sniffer")
val sniffer: Sniffer = Sniffer(),
@SerialName("geox-url")
val geoxurl: GeoXUrl = GeoXUrl(),
) : Parcelable {
@Serializable
data class Dns(
@SerialName("enable")
var enable: Boolean? = null,
@SerialName("prefer-h3")
var preferH3: Boolean? = null,
@SerialName("listen")
var listen: String? = null,
@SerialName("ipv6")
var ipv6: Boolean? = null,
@SerialName("use-hosts")
var useHosts: Boolean? = null,
@SerialName("enhanced-mode")
var enhancedMode: DnsEnhancedMode? = null,
@SerialName("nameserver")
var nameServer: List<String>? = null,
@SerialName("fallback")
var fallback: List<String>? = null,
@SerialName("default-nameserver")
var defaultServer: List<String>? = null,
@SerialName("fake-ip-filter")
var fakeIpFilter: List<String>? = null,
@SerialName("fake-ip-filter-mode")
var fakeIPFilterMode: FilterMode? = null,
@SerialName("fallback-filter")
val fallbackFilter: DnsFallbackFilter = DnsFallbackFilter(),
@SerialName("nameserver-policy")
var nameserverPolicy: Map<String, String>? = null,
)
@Serializable
data class DnsFallbackFilter(
@SerialName("geoip")
var geoIp: Boolean? = null,
@SerialName("geoip-code")
var geoIpCode: String? = null,
@SerialName("ipcidr")
var ipcidr: List<String>? = null,
@SerialName("domain")
var domain: List<String>? = null,
)
@Serializable
data class App(
@SerialName("append-system-dns")
var appendSystemDns: Boolean? = null
)
@Serializable
enum class FindProcessMode {
@SerialName("off")
Off,
@SerialName("strict")
Strict,
@SerialName("always")
Always,
}
@Serializable
enum class DnsEnhancedMode {
@SerialName("normal")
None,
@SerialName("redir-host")
Mapping,
@SerialName("fake-ip")
FakeIp,
}
@Serializable
enum class FilterMode {
@SerialName("blacklist")
BlackList,
@SerialName("whitelist")
WhiteList,
}
@Serializable
data class Sniffer(
@SerialName("enable")
var enable: Boolean? = null,
@SerialName("sniffing")
var sniffing: List<String>? = null,
@SerialName("force-dns-mapping")
var forceDnsMapping: Boolean? = null,
@SerialName("parse-pure-ip")
var parsePureIp: Boolean? = null,
@SerialName("override-destination")
var overrideDestination: Boolean? = null,
@SerialName("force-domain")
var forceDomain: List<String>? = null,
@SerialName("skip-domain")
var skipDomain: List<String>? = null,
@SerialName("port-whitelist")
var portWhitelist: List<String>? = null,
)
@Serializable
data class GeoXUrl(
@SerialName("geoip")
var geoip: String? = null,
@SerialName("mmdb")
var mmdb: String? = null,
@SerialName("geosite")
var geosite: String? = null,
)
@Serializable
data class ExternalControllerCors(
@SerialName("allow-origins")
var allowOrigins: List<String>? = null,
@SerialName("allow-private-network")
var allowPrivateNetwork: Boolean? = null,
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
Parcelizer.encodeToParcel(serializer(), parcel, this)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<ConfigurationOverride> {
override fun createFromParcel(parcel: Parcel): ConfigurationOverride {
return Parcelizer.decodeFromParcel(serializer(), parcel)
}
override fun newArray(size: Int): Array<ConfigurationOverride?> {
return arrayOfNulls(size)
}
}
}
@@ -0,0 +1,38 @@
package com.github.kr328.clash.core.model
import android.os.Parcel
import android.os.Parcelable
import com.github.kr328.clash.core.util.Parcelizer
import kotlinx.serialization.Serializable
@Serializable
data class FetchStatus(
val action: Action,
val args: List<String>,
val progress: Int,
val max: Int
) : Parcelable {
enum class Action {
FetchConfiguration,
FetchProviders,
Verifying,
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
Parcelizer.encodeToParcel(serializer(), dest, this)
}
companion object CREATOR : Parcelable.Creator<FetchStatus> {
override fun createFromParcel(parcel: Parcel): FetchStatus {
return Parcelizer.decodeFromParcel(serializer(), parcel)
}
override fun newArray(size: Int): Array<FetchStatus?> {
return arrayOfNulls(size)
}
}
}
@@ -0,0 +1,56 @@
@file:UseSerializers(DateSerializer::class)
package com.github.kr328.clash.core.model
import android.os.Parcel
import android.os.Parcelable
import com.github.kr328.clash.core.util.DateSerializer
import com.github.kr328.clash.core.util.Parcelizer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import java.util.*
@Serializable
data class LogMessage(
val level: Level,
val message: String,
val time: Date,
) : Parcelable {
@Serializable
enum class Level {
@SerialName("debug")
Debug,
@SerialName("info")
Info,
@SerialName("warning")
Warning,
@SerialName("error")
Error,
@SerialName("silent")
Silent,
@SerialName("unknown")
Unknown,
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
Parcelizer.encodeToParcel(serializer(), parcel, this)
}
override fun describeContents(): Int {
return 0
}
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<LogMessage> {
override fun createFromParcel(parcel: Parcel): LogMessage {
return Parcelizer.decodeFromParcel(serializer(), parcel)
}
override fun newArray(size: Int): Array<LogMessage?> {
return arrayOfNulls(size)
}
}
}
}
@@ -0,0 +1,44 @@
package com.github.kr328.clash.core.model
import android.os.Parcel
import android.os.Parcelable
import com.github.kr328.clash.core.util.Parcelizer
import kotlinx.serialization.Serializable
@Serializable
data class Provider(
val name: String,
val type: Type,
val vehicleType: VehicleType,
val updatedAt: Long
) : Parcelable, Comparable<Provider> {
enum class Type {
Proxy, Rule
}
enum class VehicleType {
HTTP, File, Compatible
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
Parcelizer.encodeToParcel(serializer(), parcel, this)
}
override fun describeContents(): Int {
return 0
}
override fun compareTo(other: Provider): Int {
return compareValuesBy(this, other, Provider::type, Provider::name)
}
companion object CREATOR : Parcelable.Creator<Provider> {
override fun createFromParcel(parcel: Parcel): Provider {
return Parcelizer.decodeFromParcel(serializer(), parcel)
}
override fun newArray(size: Int): Array<Provider?> {
return arrayOfNulls(size)
}
}
}
@@ -0,0 +1,28 @@
package com.github.kr328.clash.core.model
import android.os.Parcel
import android.os.Parcelable
import com.github.kr328.clash.common.util.createListFromParcelSlice
import com.github.kr328.clash.common.util.writeToParcelSlice
class ProviderList(data: List<Provider>) : List<Provider> by data, Parcelable {
constructor(parcel: Parcel) : this(Provider.createListFromParcelSlice(parcel, 0, 20))
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
return writeToParcelSlice(parcel, flags)
}
companion object CREATOR : Parcelable.Creator<ProviderList> {
override fun createFromParcel(parcel: Parcel): ProviderList {
return ProviderList(parcel)
}
override fun newArray(size: Int): Array<ProviderList?> {
return arrayOfNulls(size)
}
}
}
@@ -0,0 +1,66 @@
package com.github.kr328.clash.core.model
import android.os.Parcel
import android.os.Parcelable
import com.github.kr328.clash.core.util.Parcelizer
import kotlinx.serialization.Serializable
@Serializable
data class Proxy(
val name: String,
val title: String,
val subtitle: String,
val type: Type,
val delay: Int,
) : Parcelable {
@Suppress("unused")
enum class Type(val group: Boolean) {
Direct(false),
Reject(false),
RejectDrop(false),
Compatible(false),
Pass(false),
Shadowsocks(false),
ShadowsocksR(false),
Snell(false),
Socks5(false),
Http(false),
Vmess(false),
Vless(false),
Trojan(false),
Hysteria(false),
Hysteria2(false),
Tuic(false),
WireGuard(false),
Dns(false),
Ssh(false),
Relay(true),
Selector(true),
Fallback(true),
URLTest(true),
LoadBalance(true),
Unknown(false);
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
Parcelizer.encodeToParcel(serializer(), parcel, this)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Proxy> {
override fun createFromParcel(parcel: Parcel): Proxy {
return Parcelizer.decodeFromParcel(serializer(), parcel)
}
override fun newArray(size: Int): Array<Proxy?> {
return arrayOfNulls(size)
}
}
}
@@ -0,0 +1,62 @@
package com.github.kr328.clash.core.model
import android.os.Parcel
import android.os.Parcelable
import com.github.kr328.clash.common.util.createListFromParcelSlice
import com.github.kr328.clash.common.util.writeToParcelSlice
import kotlinx.serialization.Serializable
@Serializable
data class ProxyGroup(
val type: Proxy.Type,
val proxies: List<Proxy>,
val now: String,
) : Parcelable {
class SliceProxyList(data: List<Proxy>) : List<Proxy> by data, Parcelable {
constructor(parcel: Parcel) : this(Proxy.createListFromParcelSlice(parcel, 0, 50))
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
writeToParcelSlice(dest, flags)
}
companion object CREATOR : Parcelable.Creator<SliceProxyList> {
override fun createFromParcel(parcel: Parcel): SliceProxyList {
return SliceProxyList(parcel)
}
override fun newArray(size: Int): Array<SliceProxyList?> {
return arrayOfNulls(size)
}
}
}
constructor(parcel: Parcel) : this(
Proxy.Type.values()[parcel.readInt()],
SliceProxyList(parcel),
parcel.readString()!!,
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(type.ordinal)
SliceProxyList(proxies).writeToParcel(parcel, 0)
parcel.writeString(now)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<ProxyGroup> {
override fun createFromParcel(parcel: Parcel): ProxyGroup {
return ProxyGroup(parcel)
}
override fun newArray(size: Int): Array<ProxyGroup?> {
return arrayOfNulls(size)
}
}
}
@@ -0,0 +1,5 @@
package com.github.kr328.clash.core.model
enum class ProxySort {
Default, Title, Delay
}
@@ -0,0 +1,3 @@
package com.github.kr328.clash.core.model
typealias Traffic = Long
@@ -0,0 +1,45 @@
package com.github.kr328.clash.core.model
import android.os.Parcel
import android.os.Parcelable
import com.github.kr328.clash.core.util.Parcelizer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class TunnelState(
val mode: Mode,
) : Parcelable {
@Serializable
enum class Mode {
@SerialName("direct")
Direct,
@SerialName("global")
Global,
@SerialName("rule")
Rule,
@SerialName("script")
Script,
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
Parcelizer.encodeToParcel(serializer(), parcel, this)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<TunnelState> {
override fun createFromParcel(parcel: Parcel): TunnelState {
return Parcelizer.decodeFromParcel(serializer(), parcel)
}
override fun newArray(size: Int): Array<TunnelState?> {
return arrayOfNulls(size)
}
}
}
@@ -0,0 +1,27 @@
package com.github.kr328.clash.core.model
import android.os.Parcel
import android.os.Parcelable
import com.github.kr328.clash.core.util.Parcelizer
import kotlinx.serialization.Serializable
@Serializable
class UiConfiguration : Parcelable {
override fun writeToParcel(parcel: Parcel, flags: Int) {
Parcelizer.encodeToParcel(serializer(), parcel, this)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<UiConfiguration> {
override fun createFromParcel(parcel: Parcel): UiConfiguration {
return Parcelizer.decodeFromParcel(serializer(), parcel)
}
override fun newArray(size: Int): Array<UiConfiguration?> {
return arrayOfNulls(size)
}
}
}
@@ -0,0 +1,11 @@
package com.github.kr328.clash.core.util
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.URL
fun parseInetSocketAddress(address: String): InetSocketAddress {
val url = URL("https://$address")
return InetSocketAddress(InetAddress.getByName(url.host), url.port)
}
@@ -0,0 +1,256 @@
package com.github.kr328.clash.core.util
import android.os.Parcel
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.CompositeEncoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule
object Parcelizer {
private class ParcelDecoder(private val parcel: Parcel) : Decoder, CompositeDecoder {
override val serializersModule: SerializersModule = SerializersModule {}
@ExperimentalSerializationApi
override fun decodeSequentially(): Boolean = true
override fun decodeByteElement(descriptor: SerialDescriptor, index: Int) = decodeByte()
override fun decodeCharElement(descriptor: SerialDescriptor, index: Int) = decodeChar()
override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int) = decodeDouble()
override fun decodeElementIndex(descriptor: SerialDescriptor) = decodeInt()
override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int) = decodeFloat()
override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int) =
decodeBoolean()
@ExperimentalSerializationApi
override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder {
return this
}
override fun decodeIntElement(descriptor: SerialDescriptor, index: Int) = decodeInt()
override fun decodeLongElement(descriptor: SerialDescriptor, index: Int) = decodeLong()
override fun decodeShortElement(descriptor: SerialDescriptor, index: Int) = decodeShort()
override fun decodeStringElement(descriptor: SerialDescriptor, index: Int) = decodeString()
@ExperimentalSerializationApi
override fun <T : Any> decodeNullableSerializableElement(
descriptor: SerialDescriptor,
index: Int,
deserializer: DeserializationStrategy<T?>,
previousValue: T?
): T? = decodeNullableSerializableValue(deserializer)
override fun <T> decodeSerializableElement(
descriptor: SerialDescriptor,
index: Int,
deserializer: DeserializationStrategy<T>,
previousValue: T?
): T = decodeSerializableValue(deserializer)
override fun endStructure(descriptor: SerialDescriptor) {
}
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
return this
}
override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {
return decodeInt()
}
override fun decodeBoolean(): Boolean {
return decodeByte() != (0.toByte())
}
override fun decodeByte(): Byte {
return parcel.readByte()
}
override fun decodeChar(): Char {
return decodeInt().toChar()
}
override fun decodeDouble(): Double {
return parcel.readDouble()
}
override fun decodeEnum(enumDescriptor: SerialDescriptor): Int {
return decodeInt()
}
override fun decodeFloat(): Float {
return parcel.readFloat()
}
@ExperimentalSerializationApi
override fun decodeInline(inlineDescriptor: SerialDescriptor): Decoder {
return this
}
override fun decodeInt(): Int {
return parcel.readInt()
}
override fun decodeLong(): Long {
return parcel.readLong()
}
@ExperimentalSerializationApi
override fun decodeNotNullMark(): Boolean {
return decodeBoolean()
}
@ExperimentalSerializationApi
override fun decodeNull(): Nothing? {
return null
}
override fun decodeShort(): Short {
return decodeInt().toShort()
}
override fun decodeString(): String {
return parcel.readString()!!
}
}
private class ParcelEncoder(private val parcel: Parcel) : Encoder, CompositeEncoder {
override val serializersModule: SerializersModule = SerializersModule {}
override fun encodeBooleanElement(
descriptor: SerialDescriptor,
index: Int,
value: Boolean
) = encodeBoolean(value)
override fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte) =
encodeByte(value)
override fun encodeCharElement(descriptor: SerialDescriptor, index: Int, value: Char) =
encodeChar(value)
override fun encodeDoubleElement(descriptor: SerialDescriptor, index: Int, value: Double) =
encodeDouble(value)
override fun encodeFloatElement(descriptor: SerialDescriptor, index: Int, value: Float) =
encodeFloat(value)
@ExperimentalSerializationApi
override fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder {
return this
}
override fun encodeIntElement(descriptor: SerialDescriptor, index: Int, value: Int) =
encodeInt(value)
override fun encodeLongElement(descriptor: SerialDescriptor, index: Int, value: Long) =
encodeLong(value)
override fun encodeShortElement(descriptor: SerialDescriptor, index: Int, value: Short) =
encodeShort(value)
override fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String) =
encodeString(value)
@ExperimentalSerializationApi
override fun <T : Any> encodeNullableSerializableElement(
descriptor: SerialDescriptor,
index: Int,
serializer: SerializationStrategy<T>,
value: T?
) = encodeNullableSerializableValue(serializer, value)
override fun <T> encodeSerializableElement(
descriptor: SerialDescriptor,
index: Int,
serializer: SerializationStrategy<T>,
value: T
) = encodeSerializableValue(serializer, value)
override fun endStructure(descriptor: SerialDescriptor) {
}
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
return this
}
override fun beginCollection(
descriptor: SerialDescriptor,
collectionSize: Int
): CompositeEncoder {
encodeInt(collectionSize)
return super.beginCollection(descriptor, collectionSize)
}
override fun encodeBoolean(value: Boolean) {
encodeByte(if (value) 1 else 0)
}
override fun encodeByte(value: Byte) {
parcel.writeByte(value)
}
override fun encodeChar(value: Char) {
encodeInt(value.code)
}
override fun encodeDouble(value: Double) {
parcel.writeDouble(value)
}
override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) {
encodeInt(index)
}
override fun encodeFloat(value: Float) {
parcel.writeFloat(value)
}
@ExperimentalSerializationApi
override fun encodeInline(inlineDescriptor: SerialDescriptor): Encoder {
return this
}
override fun encodeInt(value: Int) {
parcel.writeInt(value)
}
override fun encodeLong(value: Long) {
parcel.writeLong(value)
}
@ExperimentalSerializationApi
override fun encodeNull() {
encodeBoolean(false)
}
override fun encodeShort(value: Short) {
encodeInt(value.toInt())
}
override fun encodeString(value: String) {
parcel.writeString(value)
}
@ExperimentalSerializationApi
override fun encodeNotNullMark() {
encodeBoolean(true)
}
}
fun <T> decodeFromParcel(deserializer: DeserializationStrategy<T>, parcel: Parcel): T {
return deserializer.deserialize(ParcelDecoder(parcel))
}
fun <T> encodeToParcel(serializer: SerializationStrategy<T>, parcel: Parcel, value: T) {
serializer.serialize(ParcelEncoder(parcel), value)
}
}
@@ -0,0 +1,22 @@
package com.github.kr328.clash.core.util
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.util.*
object DateSerializer : KSerializer<Date> {
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG)
override fun deserialize(decoder: Decoder): Date {
return Date(decoder.decodeLong())
}
override fun serialize(encoder: Encoder, value: Date) {
encoder.encodeLong(value.time)
}
}
@@ -0,0 +1,54 @@
package com.github.kr328.clash.core.util
import com.github.kr328.clash.core.model.Traffic
fun Traffic.trafficUpload(): String {
return trafficString(scaleTraffic(this ushr 32))
}
fun Traffic.trafficDownload(): String {
return trafficString(scaleTraffic(this and 0xFFFFFFFF))
}
fun Traffic.trafficTotal(): String {
val upload = scaleTraffic(this ushr 32)
val download = scaleTraffic(this and 0xFFFFFFFF)
return trafficString(upload + download)
}
private fun trafficString(scaled: Long): String {
return when {
scaled > 1024 * 1024 * 1024 * 100L -> {
val data = scaled / 1024 / 1024 / 1024
String.format("%.2f GiB", data.toFloat() / 100)
}
scaled > 1024 * 1024 * 100L -> {
val data = scaled / 1024 / 1024
String.format("%.2f MiB", data.toFloat() / 100)
}
scaled > 1024 * 100L -> {
val data = scaled / 1024
String.format("%.2f KiB", data.toFloat() / 100)
}
else -> {
"$scaled Bytes"
}
}
}
private fun scaleTraffic(value: Long): Long {
val type = (value ushr 30) and 0x3
val data = value and 0x3FFFFFFF
return when (type) {
0L -> data
1L -> data * 1024
2L -> data * 1024 * 1024
3L -> data * 1024 * 1024 * 1024
else -> throw IllegalArgumentException("invalid value type")
}
}