Initial: initial commit
This commit is contained in:
5
core/src/main/AndroidManifest.xml
Normal file
5
core/src/main/AndroidManifest.xml
Normal file
@@ -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>
|
||||
23
core/src/main/cpp/CMakeLists.txt
Normal file
23
core/src/main/cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
project(clash-bridge C)
|
||||
|
||||
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)
|
||||
12
core/src/main/cpp/bridge_helper.c
Normal file
12
core/src/main/cpp/bridge_helper.c
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
5
core/src/main/cpp/bridge_helper.h
Normal file
5
core/src/main/cpp/bridge_helper.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint64_t down_scale_traffic(uint64_t value);
|
||||
75
core/src/main/cpp/jni_helper.c
Normal file
75
core/src/main/cpp/jni_helper.c
Normal file
@@ -0,0 +1,75 @@
|
||||
#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(JNIEnv **penv) {
|
||||
JavaVM *vm = global_java_vm();
|
||||
|
||||
if ((*vm)->AttachCurrentThread(vm, penv, NULL) != JNI_OK) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void jni_detach_thread(JNIEnv **env) {
|
||||
(void) env;
|
||||
|
||||
JavaVM *vm = global_java_vm();
|
||||
|
||||
(*vm)->DetachCurrentThread(vm);
|
||||
}
|
||||
|
||||
void release_string(char **str) {
|
||||
free(*str);
|
||||
}
|
||||
32
core/src/main/cpp/jni_helper.h
Normal file
32
core/src/main/cpp/jni_helper.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <malloc.h>
|
||||
#include <android/log.h>
|
||||
|
||||
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(JNIEnv **penv);
|
||||
|
||||
extern void jni_detach_thread(JNIEnv **env);
|
||||
|
||||
extern void release_string(char **str);
|
||||
|
||||
#define ATTACH_JNI() __attribute__((unused, cleanup(jni_detach_thread))) JNIEnv *env = NULL; jni_attach_thread(&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)
|
||||
528
core/src/main/cpp/main.c
Normal file
528
core/src/main/cpp/main.c
Normal file
@@ -0,0 +1,528 @@
|
||||
#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"
|
||||
|
||||
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);
|
||||
|
||||
coreInit(_home, _version_name, 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_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,
|
||||
jint mtu, jstring gateway,
|
||||
jstring mirror, jstring dns,
|
||||
jobject cb) {
|
||||
TRACE_METHOD();
|
||||
|
||||
scoped_string _gateway = get_string(gateway);
|
||||
scoped_string _mirror = get_string(mirror);
|
||||
scoped_string _dns = get_string(dns);
|
||||
jobject _interface = new_global(cb);
|
||||
|
||||
startTun(fd, mtu, _gateway, _mirror, _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 void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_nativeInstallSideloadGeoip(JNIEnv *env, jobject thiz,
|
||||
jbyteArray data) {
|
||||
TRACE_METHOD();
|
||||
|
||||
if (data == NULL) {
|
||||
installSideloadGeoip(NULL, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
jbyte *bytes = (*env)->GetByteArrayElements(env, data, NULL);
|
||||
int size = (*env)->GetArrayLength(env, data);
|
||||
|
||||
scoped_string err = installSideloadGeoip(bytes, size);
|
||||
|
||||
(*env)->ReleaseByteArrayElements(env, data, bytes, JNI_ABORT);
|
||||
|
||||
if (err != NULL) {
|
||||
(*env)->ThrowNew(
|
||||
env,
|
||||
find_class("com/github/kr328/clash/core/bridge/ClashException"),
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
11
core/src/main/golang/.idea/codeStyles/Project.xml
generated
Normal file
11
core/src/main/golang/.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<GoCodeStyleSettings>
|
||||
<option name="IMPORT_SORTING" value="GOIMPORTS" />
|
||||
<option name="MOVE_ALL_IMPORTS_IN_ONE_DECLARATION" value="true" />
|
||||
<option name="MOVE_ALL_STDLIB_IMPORTS_IN_ONE_GROUP" value="true" />
|
||||
<option name="GROUP_STDLIB_IMPORTS" value="true" />
|
||||
<option name="GROUP_CURRENT_PROJECT_IMPORTS" value="true" />
|
||||
</GoCodeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
core/src/main/golang/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
core/src/main/golang/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
55
core/src/main/golang/app.go
Normal file
55
core/src/main/golang/app.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
//#include "bridge.h"
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"cfa/app"
|
||||
|
||||
"github.com/Dreamacro/clash/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 queryConfiguration
|
||||
func queryConfiguration() *C.char {
|
||||
response := &struct{}{}
|
||||
|
||||
return marshalJson(&response)
|
||||
}
|
||||
|
||||
func init() {
|
||||
app.ApplyContentContext(openRemoteContent)
|
||||
}
|
||||
48
core/src/main/golang/app/app.go
Normal file
48
core/src/main/golang/app/app.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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]
|
||||
}
|
||||
27
core/src/main/golang/app/content.go
Normal file
27
core/src/main/golang/app/content.go
Normal file
@@ -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
|
||||
}
|
||||
15
core/src/main/golang/app/dns.go
Normal file
15
core/src/main/golang/app/dns.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package app
|
||||
|
||||
import "strings"
|
||||
|
||||
var systemDns []string
|
||||
|
||||
func NotifyDnsChanged(dnsList string) {
|
||||
dns := strings.Split(dnsList, ",")
|
||||
|
||||
systemDns = dns
|
||||
}
|
||||
|
||||
func SystemDns() []string {
|
||||
return systemDns
|
||||
}
|
||||
46
core/src/main/golang/app/tun.go
Normal file
46
core/src/main/golang/app/tun.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"cfa/platform"
|
||||
"net"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
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 {
|
||||
protocol := syscall.IPPROTO_TCP
|
||||
|
||||
if strings.HasPrefix(source.String(), "udp") {
|
||||
protocol = syscall.IPPROTO_UDP
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
34
core/src/main/golang/app/ui.go
Normal file
34
core/src/main/golang/app/ui.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/dlclark/regexp2"
|
||||
|
||||
"github.com/Dreamacro/clash/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
|
||||
}
|
||||
115
core/src/main/golang/bridge.c
Normal file
115
core/src/main/golang/bridge.c
Normal file
@@ -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);
|
||||
}
|
||||
53
core/src/main/golang/bridge.h
Normal file
53
core/src/main/golang/bridge.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <malloc.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#define TAG "ClashForAndroid"
|
||||
|
||||
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);
|
||||
1
core/src/main/golang/clash
Submodule
1
core/src/main/golang/clash
Submodule
Submodule core/src/main/golang/clash added at ab13a0d119
23
core/src/main/golang/common/path.go
Normal file
23
core/src/main/golang/common/path.go
Normal file
@@ -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, "/")
|
||||
}
|
||||
62
core/src/main/golang/config.go
Normal file
62
core/src/main/golang/config.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
//#include "bridge.h"
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"cfa/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))
|
||||
}
|
||||
27
core/src/main/golang/config/defaults.go
Normal file
27
core/src/main/golang/config/defaults.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package config
|
||||
|
||||
var (
|
||||
defaultNameServers = []string{
|
||||
"223.5.5.5",
|
||||
"119.29.29.29",
|
||||
"8.8.8.8",
|
||||
"1.1.1.1",
|
||||
}
|
||||
defaultFakeIPFilter = []string{
|
||||
// stun services
|
||||
"+.stun.*.*",
|
||||
"+.stun.*.*.*",
|
||||
"+.stun.*.*.*.*",
|
||||
|
||||
// Google Voices
|
||||
"lens.l.google.com",
|
||||
"stun.l.google.com",
|
||||
|
||||
// Nintendo Switch
|
||||
"*.n.n.srv.nintendo.net",
|
||||
}
|
||||
localNetwork = []string{
|
||||
"0.0.0.0/32",
|
||||
"127.0.0.0/8",
|
||||
}
|
||||
)
|
||||
182
core/src/main/golang/config/fetch.go
Normal file
182
core/src/main/golang/config/fetch.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
U "net/url"
|
||||
"os"
|
||||
P "path"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"cfa/app"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
Action string `json:"action"`
|
||||
Args []string `json:"args"`
|
||||
Progress int `json:"progress"`
|
||||
MaxProgress int `json:"max"`
|
||||
}
|
||||
|
||||
var client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
// from http.DefaultTransport
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DialContext: dialer.DefaultTunnelDialer,
|
||||
},
|
||||
}
|
||||
|
||||
func openUrl(url string) (io.ReadCloser, error) {
|
||||
request, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Set("User-Agent", "ClashForAndroid/"+app.VersionName())
|
||||
|
||||
response, err := client.Do(request)
|
||||
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 {
|
||||
var reader io.ReadCloser
|
||||
var err error
|
||||
|
||||
switch url.Scheme {
|
||||
case "http", "https":
|
||||
reader, err = openUrl(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]interface{}) {
|
||||
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
|
||||
}
|
||||
102
core/src/main/golang/config/load.go
Normal file
102
core/src/main/golang/config/load.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
P "path"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"cfa/app"
|
||||
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
)
|
||||
|
||||
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 := ioutil.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
|
||||
}
|
||||
|
||||
executor.ApplyConfig(cfg, true)
|
||||
|
||||
app.ApplySubtitlePattern(rawCfg.ClashForAndroid.UiSubtitlePattern)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadDefault() {
|
||||
rawConfig, _ := config.UnmarshalRawConfig([]byte{})
|
||||
|
||||
_ = patchDns(rawConfig, constant.Path.HomeDir())
|
||||
|
||||
cfg, err := config.ParseRawConfig(rawConfig)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
executor.ApplyConfig(cfg, true)
|
||||
}
|
||||
|
||||
func init() {
|
||||
LoadDefault()
|
||||
}
|
||||
68
core/src/main/golang/config/override.go
Normal file
68
core/src/main/golang/config/override.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type OverrideSlot int
|
||||
|
||||
const (
|
||||
OverrideSlotPersist OverrideSlot = iota
|
||||
OverrideSlotSession
|
||||
)
|
||||
|
||||
const defaultPersistOverride = `{"dns":{"enable": false}}`
|
||||
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 := ioutil.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
|
||||
}
|
||||
}
|
||||
108
core/src/main/golang/config/process.go
Normal file
108
core/src/main/golang/config/process.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/dlclark/regexp2"
|
||||
|
||||
"cfa/app"
|
||||
"cfa/common"
|
||||
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
)
|
||||
|
||||
var processors = []processor{
|
||||
patchOverride,
|
||||
patchGeneral,
|
||||
patchProfile,
|
||||
patchDns,
|
||||
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 patchGeneral(cfg *config.RawConfig, _ string) error {
|
||||
cfg.Interface = ""
|
||||
cfg.ExternalUI = ""
|
||||
cfg.ExternalController = ""
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func patchProfile(cfg *config.RawConfig, _ string) error {
|
||||
cfg.Profile.StoreSelected = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func patchDns(cfg *config.RawConfig, _ string) error {
|
||||
if !cfg.DNS.Enable {
|
||||
cfg.DNS.Enable = true
|
||||
cfg.DNS.IPv6 = false
|
||||
cfg.DNS.NameServer = defaultNameServers
|
||||
cfg.DNS.Fallback = []string{}
|
||||
cfg.DNS.FallbackFilter.GeoIP = false
|
||||
cfg.DNS.FallbackFilter.IPCIDR = localNetwork
|
||||
cfg.DNS.EnhancedMode = dns.FAKEIP
|
||||
cfg.DNS.FakeIPRange = "198.18.0.0/16"
|
||||
cfg.DNS.DefaultNameserver = defaultNameServers
|
||||
cfg.DNS.FakeIPFilter = defaultFakeIPFilter
|
||||
|
||||
cfg.ClashForAndroid.AppendSystemDNS = true
|
||||
}
|
||||
|
||||
if cfg.ClashForAndroid.AppendSystemDNS {
|
||||
cfg.DNS.NameServer = append(cfg.DNS.NameServer, app.SystemDns()...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func patchProviders(cfg *config.RawConfig, profileDir string) error {
|
||||
forEachProviders(cfg, func(index int, total int, key string, provider map[string]interface{}) {
|
||||
if path, ok := provider["path"].(string); ok {
|
||||
provider["path"] = profileDir + "/providers/" + common.ResolveAsRoot(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
|
||||
}
|
||||
22
core/src/main/golang/config/provider_open.go
Normal file
22
core/src/main/golang/config/provider_open.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// +build !premium
|
||||
|
||||
package config
|
||||
|
||||
import "github.com/Dreamacro/clash/config"
|
||||
|
||||
func forEachProviders(rawCfg *config.RawConfig, fun func(index int, total int, key string, provider map[string]interface{})) {
|
||||
total := len(rawCfg.ProxyProvider)
|
||||
index := 0
|
||||
|
||||
for k, v := range rawCfg.ProxyProvider {
|
||||
fun(index, total, k, v)
|
||||
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
func destroyProviders(cfg *config.Config) {
|
||||
for _, p := range cfg.Providers {
|
||||
_ = p.Destroy()
|
||||
}
|
||||
}
|
||||
32
core/src/main/golang/config/provider_premium.go
Normal file
32
core/src/main/golang/config/provider_premium.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// +build premium
|
||||
|
||||
package config
|
||||
|
||||
import "github.com/Dreamacro/clash/config"
|
||||
|
||||
func forEachProviders(rawCfg *config.RawConfig, fun func(index int, total int, key string, provider map[string]interface{})) {
|
||||
total := len(rawCfg.ProxyProvider) + len(rawCfg.RuleProvider)
|
||||
index := 0
|
||||
|
||||
for k, v := range rawCfg.ProxyProvider {
|
||||
fun(index, total, k, v)
|
||||
|
||||
index++
|
||||
}
|
||||
|
||||
for k, v := range rawCfg.RuleProvider {
|
||||
fun(index, total, k, v)
|
||||
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
func destroyProviders(cfg *config.Config) {
|
||||
for _, p := range cfg.ProxyProviders {
|
||||
_ = p.Destroy()
|
||||
}
|
||||
|
||||
for _, p := range cfg.RuleProviders {
|
||||
_ = p.Destroy()
|
||||
}
|
||||
}
|
||||
74
core/src/main/golang/core/init.go
Normal file
74
core/src/main/golang/core/init.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"cfa/blob"
|
||||
|
||||
"cfa/app"
|
||||
"cfa/platform"
|
||||
|
||||
"github.com/Dreamacro/clash/component/process"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
var errBlocked = errors.New("blocked")
|
||||
|
||||
func Init(home, versionName string, platformVersion int) {
|
||||
mmdb.LoadFromBytes(blob.GeoipDatabase)
|
||||
constant.SetHomeDir(home)
|
||||
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.DialerHook = func(dialer *net.Dialer) error {
|
||||
dialer.Control = func(network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func(fd uintptr) {
|
||||
app.MarkSocket(int(fd))
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
dialer.ListenPacketHook = func(lc *net.ListenConfig, address string) (string, error) {
|
||||
lc.Control = func(network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func(fd uintptr) {
|
||||
app.MarkSocket(int(fd))
|
||||
})
|
||||
}
|
||||
|
||||
if platform.ShouldBlockConnection() {
|
||||
return "", errBlocked
|
||||
}
|
||||
|
||||
return address, nil
|
||||
}
|
||||
|
||||
dialer.DialHook = func(dialer *net.Dialer, network string, ip net.IP) error {
|
||||
if platform.ShouldBlockConnection() {
|
||||
return errBlocked
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
18
core/src/main/golang/debug.go
Normal file
18
core/src/main/golang/debug.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// +build debug
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
log.Debugln("pprof service listen at: 0.0.0.0:8888")
|
||||
|
||||
_ = http.ListenAndServe("0.0.0.0:8888", nil)
|
||||
}()
|
||||
}
|
||||
18
core/src/main/golang/go.mod
Normal file
18
core/src/main/golang/go.mod
Normal file
@@ -0,0 +1,18 @@
|
||||
module cfa
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
cfa/blob v0.0.0 // local generated
|
||||
github.com/Dreamacro/clash v0.0.0 // local
|
||||
github.com/dlclark/regexp2 v1.4.0
|
||||
github.com/kr328/tun2socket v0.0.0-20210412191540-3d56c47e2d99
|
||||
github.com/miekg/dns v1.1.42
|
||||
github.com/oschwald/geoip2-golang v1.5.0
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
replace github.com/Dreamacro/clash => ./clash
|
||||
|
||||
replace cfa/blob => ../../../build/intermediates/golang_blob
|
||||
60
core/src/main/golang/go.sum
Normal file
60
core/src/main/golang/go.sum
Normal file
@@ -0,0 +1,60 @@
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc=
|
||||
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.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/kr328/tun2socket v0.0.0-20210412191540-3d56c47e2d99 h1:dkEFEnGUg2z/FAPywWr4yfR/sWDQK76qn3J4Y5H2hJs=
|
||||
github.com/kr328/tun2socket v0.0.0-20210412191540-3d56c47e2d99/go.mod h1:FWfSixjrLgtK+dHkDoN6lHMNhvER24gnjUZd/wt8Z9o=
|
||||
github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY=
|
||||
github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/oschwald/geoip2-golang v1.5.0 h1:igg2yQIrrcRccB1ytFXqBfOHCjXWIoMv85lVJ1ONZzw=
|
||||
github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s=
|
||||
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
|
||||
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
|
||||
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/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o=
|
||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210508051633-16afe75a6701 h1:lQVgcB3+FoAXOb20Dp6zTzAIrpj1k/yOOBN7s+Zv1rA=
|
||||
golang.org/x/net v0.0.0-20210508051633-16afe75a6701/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 h1:5PbJGn5Sp3GEUjJ61aYbUP6RIo3Z3r2E4Tv9y2z8UHo=
|
||||
golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
79
core/src/main/golang/log.go
Normal file
79
core/src/main/golang/log.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package main
|
||||
|
||||
//#include "bridge.h"
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/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 item := range sub {
|
||||
msg := item.(*log.Event)
|
||||
|
||||
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 i := range sub {
|
||||
msg, ok := i.(*log.Event)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
50
core/src/main/golang/main.go
Normal file
50
core/src/main/golang/main.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -llog
|
||||
|
||||
#include "bridge.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"cfa/config"
|
||||
"cfa/core"
|
||||
"cfa/tunnel"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
panic("Stub!")
|
||||
}
|
||||
|
||||
//export coreInit
|
||||
func coreInit(home, versionName C.c_string, sdkVersion C.int) {
|
||||
h := C.GoString(home)
|
||||
v := C.GoString(versionName)
|
||||
s := int(sdkVersion)
|
||||
|
||||
core.Init(h, v, s)
|
||||
|
||||
reset()
|
||||
}
|
||||
|
||||
//export reset
|
||||
func reset() {
|
||||
config.LoadDefault()
|
||||
tunnel.ResetStatistic()
|
||||
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
//export forceGc
|
||||
func forceGc() {
|
||||
go func() {
|
||||
log.Infoln("[APP] request force GC")
|
||||
|
||||
runtime.GC()
|
||||
}()
|
||||
}
|
||||
42
core/src/main/golang/platform/limit.go
Normal file
42
core/src/main/golang/platform/limit.go
Normal file
@@ -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
|
||||
}
|
||||
163
core/src/main/golang/platform/procfs.go
Normal file
163
core/src/main/golang/platform/procfs.go
Normal file
@@ -0,0 +1,163 @@
|
||||
// +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") {
|
||||
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
|
||||
}
|
||||
|
||||
if strings.HasSuffix(source.Network(), "6") {
|
||||
sIP = sIP.To16()
|
||||
} else {
|
||||
sIP = sIP.To4()
|
||||
}
|
||||
|
||||
if sIP == nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
23
core/src/main/golang/proxy.go
Normal file
23
core/src/main/golang/proxy.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
//#include "bridge.h"
|
||||
import "C"
|
||||
|
||||
import "cfa/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()
|
||||
}
|
||||
108
core/src/main/golang/proxy/http.go
Normal file
108
core/src/main/golang/proxy/http.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
const (
|
||||
LocalHttpTimeout = time.Millisecond * 100
|
||||
)
|
||||
|
||||
var listener *httpListener
|
||||
var lock sync.Mutex
|
||||
|
||||
type httpListener struct {
|
||||
net.Listener
|
||||
|
||||
closed bool
|
||||
}
|
||||
|
||||
func Start(listen string) (listenAt string, err error) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
stopLocked()
|
||||
|
||||
l, err := net.Listen("tcp", listen)
|
||||
if err != nil {
|
||||
log.Errorln("Listen HTTP proxy at: %s: %s", listen, err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
h := &httpListener{
|
||||
Listener: l,
|
||||
closed: false,
|
||||
}
|
||||
|
||||
listener = h
|
||||
|
||||
go func() {
|
||||
for !h.closed {
|
||||
conn, err := h.Accept()
|
||||
if err != nil {
|
||||
log.Warnln("Accept connection: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
_ = conn.(*net.TCPConn).SetKeepAlive(false)
|
||||
|
||||
h.handleConn(conn)
|
||||
}
|
||||
}()
|
||||
|
||||
return h.Addr().String(), nil
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
stopLocked()
|
||||
}
|
||||
|
||||
func stopLocked() {
|
||||
if listener != nil {
|
||||
listener.closed = true
|
||||
_ = listener.Close()
|
||||
}
|
||||
|
||||
listener = nil
|
||||
}
|
||||
|
||||
func (l *httpListener) handleConn(conn net.Conn) {
|
||||
_ = conn.SetReadDeadline(time.Now().Add(LocalHttpTimeout))
|
||||
|
||||
br := bufio.NewReader(conn)
|
||||
request, err := http.ReadRequest(br)
|
||||
|
||||
_ = conn.SetReadDeadline(time.Time{})
|
||||
|
||||
if err != nil || request.URL.Host == "" {
|
||||
if err != nil {
|
||||
log.Warnln("HTTP Connection closed: %s", err.Error())
|
||||
}
|
||||
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if request.Method == http.MethodConnect {
|
||||
_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tunnel.Add(adapters.NewHTTPS(request, conn))
|
||||
return
|
||||
}
|
||||
|
||||
tunnel.Add(adapters.NewHTTP(request, conn))
|
||||
}
|
||||
9
core/src/main/golang/trace.c
Normal file
9
core/src/main/golang/trace.c
Normal file
@@ -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
|
||||
19
core/src/main/golang/trace.h
Normal file
19
core/src/main/golang/trace.h
Normal file
@@ -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
|
||||
83
core/src/main/golang/tun.go
Normal file
83
core/src/main/golang/tun.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
//#include "bridge.h"
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"unsafe"
|
||||
|
||||
"cfa/app"
|
||||
"cfa/tun"
|
||||
|
||||
"golang.org/x/sync/semaphore"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
type remoteTun struct {
|
||||
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) stop() {
|
||||
_ = t.limit.Acquire(context.Background(), 4)
|
||||
defer t.limit.Release(4)
|
||||
|
||||
t.closed = true
|
||||
|
||||
C.release_object(t.callback)
|
||||
|
||||
log.Infoln("Android tun device destroyed")
|
||||
}
|
||||
|
||||
//export startTun
|
||||
func startTun(fd, mtu C.int, gateway, mirror, dns C.c_string, callback unsafe.Pointer) C.int {
|
||||
f := int(fd)
|
||||
m := int(mtu)
|
||||
|
||||
g := C.GoString(gateway)
|
||||
mr := C.GoString(mirror)
|
||||
d := C.GoString(dns)
|
||||
|
||||
remote := &remoteTun{callback: callback, closed: false, limit: semaphore.NewWeighted(4)}
|
||||
|
||||
if tun.Start(f, m, g, mr, d, remote.stop) != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
app.ApplyTunContext(remote.markSocket, remote.querySocketUid)
|
||||
|
||||
log.Infoln("Android tun device created")
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
//export stopTun
|
||||
func stopTun() {
|
||||
tun.Stop()
|
||||
}
|
||||
101
core/src/main/golang/tun/dns.go
Normal file
101
core/src/main/golang/tun/dns.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
|
||||
"github.com/kr328/tun2socket/binding"
|
||||
"github.com/kr328/tun2socket/redirect"
|
||||
)
|
||||
|
||||
const defaultDnsReadTimeout = time.Second * 30
|
||||
|
||||
func shouldHijackDns(dnsAddr binding.Address, targetAddr binding.Address) bool {
|
||||
if targetAddr.Port != 53 {
|
||||
return false
|
||||
}
|
||||
|
||||
return dnsAddr.IP.Equal(net.IPv4zero) || dnsAddr.IP.Equal(targetAddr.IP)
|
||||
}
|
||||
|
||||
func hijackUDPDns(pkt []byte, ep *binding.Endpoint, sender redirect.UDPSender) {
|
||||
go func() {
|
||||
answer, err := relayDnsPacket(pkt)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = sender(answer, &binding.Endpoint{
|
||||
Source: ep.Target,
|
||||
Target: ep.Source,
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
func hijackTCPDns(conn net.Conn) {
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
|
||||
for {
|
||||
if err := conn.SetReadDeadline(time.Now().Add(defaultDnsReadTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var length uint16
|
||||
if binary.Read(conn, binary.BigEndian, &length) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data := make([]byte, length)
|
||||
|
||||
_, err := io.ReadFull(conn, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rb, err := relayDnsPacket(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if binary.Write(conn, binary.BigEndian, uint16(len(rb))) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := conn.Write(rb); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func relayDnsPacket(payload []byte) ([]byte, error) {
|
||||
msg := &D.Msg{}
|
||||
if err := msg.Unpack(payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := resolver.ResolveMsg(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ans := range r.Answer {
|
||||
header := ans.Header()
|
||||
|
||||
if header.Class == D.ClassINET && (header.Rrtype == D.TypeA || header.Rrtype == D.TypeAAAA) {
|
||||
header.Ttl = 1
|
||||
}
|
||||
}
|
||||
|
||||
r.SetRcode(msg, r.Rcode)
|
||||
|
||||
return r.Pack()
|
||||
}
|
||||
21
core/src/main/golang/tun/log.go
Normal file
21
core/src/main/golang/tun/log.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package tun
|
||||
|
||||
import "github.com/Dreamacro/clash/log"
|
||||
|
||||
type logger struct{}
|
||||
|
||||
func (l *logger) D(format string, args ...interface{}) {
|
||||
log.Debugln(format, args...)
|
||||
}
|
||||
|
||||
func (l *logger) I(format string, args ...interface{}) {
|
||||
log.Infoln(format, args...)
|
||||
}
|
||||
|
||||
func (l *logger) W(format string, args ...interface{}) {
|
||||
log.Warnln(format, args...)
|
||||
}
|
||||
|
||||
func (l *logger) E(format string, args ...interface{}) {
|
||||
log.Errorln(format, args...)
|
||||
}
|
||||
40
core/src/main/golang/tun/tcp.go
Normal file
40
core/src/main/golang/tun/tcp.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/kr328/tun2socket/binding"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
func handleTCP(conn net.Conn, endpoint *binding.Endpoint) {
|
||||
src := &net.TCPAddr{
|
||||
IP: endpoint.Source.IP,
|
||||
Port: int(endpoint.Source.Port),
|
||||
Zone: "",
|
||||
}
|
||||
dst := &net.TCPAddr{
|
||||
IP: endpoint.Target.IP,
|
||||
Port: int(endpoint.Target.Port),
|
||||
Zone: "",
|
||||
}
|
||||
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Type: C.SOCKS,
|
||||
SrcIP: src.IP,
|
||||
DstIP: dst.IP,
|
||||
SrcPort: strconv.Itoa(src.Port),
|
||||
DstPort: strconv.Itoa(dst.Port),
|
||||
AddrType: C.AtypIPv4,
|
||||
Host: "",
|
||||
RawSrcAddr: src,
|
||||
RawDstAddr: dst,
|
||||
}
|
||||
|
||||
tunnel.Add(context.NewConnContext(conn, metadata))
|
||||
}
|
||||
84
core/src/main/golang/tun/tun.go
Normal file
84
core/src/main/golang/tun/tun.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/kr328/tun2socket/binding"
|
||||
"github.com/kr328/tun2socket/redirect"
|
||||
|
||||
"github.com/kr328/tun2socket"
|
||||
)
|
||||
|
||||
var lock sync.Mutex
|
||||
var tun *tun2socket.Tun2Socket
|
||||
|
||||
func Start(fd, mtu int, gateway, mirror, dns string, onStop func()) error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
stopLocked()
|
||||
|
||||
dnsHost, dnsPort, err := net.SplitHostPort(dns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dnsP, err := strconv.Atoi(dnsPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dnsAddr := binding.Address{
|
||||
IP: net.ParseIP(dnsHost),
|
||||
Port: uint16(dnsP),
|
||||
}
|
||||
|
||||
t := tun2socket.NewTun2Socket(os.NewFile(uintptr(fd), "/dev/tun"), mtu, net.ParseIP(gateway), net.ParseIP(mirror))
|
||||
|
||||
t.SetAllocator(allocUDP)
|
||||
t.SetClosedHandler(onStop)
|
||||
t.SetLogger(&logger{})
|
||||
|
||||
t.SetTCPHandler(func(conn net.Conn, endpoint *binding.Endpoint) {
|
||||
if shouldHijackDns(dnsAddr, endpoint.Target) {
|
||||
hijackTCPDns(conn)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
handleTCP(conn, endpoint)
|
||||
})
|
||||
t.SetUDPHandler(func(payload []byte, endpoint *binding.Endpoint, sender redirect.UDPSender) {
|
||||
if shouldHijackDns(dnsAddr, endpoint.Target) {
|
||||
hijackUDPDns(payload, endpoint, sender)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
handleUDP(payload, endpoint, sender)
|
||||
})
|
||||
|
||||
t.Start()
|
||||
|
||||
tun = t
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
stopLocked()
|
||||
}
|
||||
|
||||
func stopLocked() {
|
||||
if tun != nil {
|
||||
tun.Close()
|
||||
}
|
||||
|
||||
tun = nil
|
||||
}
|
||||
76
core/src/main/golang/tun/udp.go
Normal file
76
core/src/main/golang/tun/udp.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
"github.com/kr328/tun2socket/binding"
|
||||
"github.com/kr328/tun2socket/redirect"
|
||||
|
||||
adapters "github.com/Dreamacro/clash/adapters/inbound"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
type udpPacket struct {
|
||||
metadata *C.Metadata
|
||||
source binding.Address
|
||||
data []byte
|
||||
send redirect.UDPSender
|
||||
}
|
||||
|
||||
func (u *udpPacket) Data() []byte {
|
||||
return u.data
|
||||
}
|
||||
|
||||
func (u *udpPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
uAddr, ok := addr.(*net.UDPAddr)
|
||||
if !ok {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
return len(b), u.send(b, &binding.Endpoint{
|
||||
Source: binding.Address{IP: uAddr.IP, Port: uint16(uAddr.Port)},
|
||||
Target: u.source,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *udpPacket) Drop() {
|
||||
recycleUDP(u.data)
|
||||
}
|
||||
|
||||
func (u *udpPacket) LocalAddr() net.Addr {
|
||||
return &net.UDPAddr{
|
||||
IP: u.source.IP,
|
||||
Port: int(u.source.Port),
|
||||
Zone: "",
|
||||
}
|
||||
}
|
||||
|
||||
func handleUDP(payload []byte, endpoint *binding.Endpoint, sender redirect.UDPSender) {
|
||||
pkt := &udpPacket{
|
||||
source: endpoint.Source,
|
||||
data: payload,
|
||||
send: sender,
|
||||
}
|
||||
|
||||
rAddr := &net.UDPAddr{
|
||||
IP: endpoint.Target.IP,
|
||||
Port: int(endpoint.Target.Port),
|
||||
Zone: "",
|
||||
}
|
||||
|
||||
adapter := adapters.NewPacket(socks5.ParseAddrToSocksAddr(rAddr), pkt, C.SOCKS)
|
||||
|
||||
tunnel.AddPacket(adapter)
|
||||
}
|
||||
|
||||
func allocUDP(size int) []byte {
|
||||
return pool.Get(size)
|
||||
}
|
||||
|
||||
func recycleUDP(payload []byte) {
|
||||
_ = pool.Put(payload)
|
||||
}
|
||||
124
core/src/main/golang/tunnel.go
Normal file
124
core/src/main/golang/tunnel.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package main
|
||||
|
||||
//#include "bridge.h"
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"cfa/app"
|
||||
"cfa/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)
|
||||
}
|
||||
|
||||
//export installSideloadGeoip
|
||||
func installSideloadGeoip(block unsafe.Pointer, blockSize C.int) *C.char {
|
||||
if block == nil {
|
||||
_ = tunnel.InstallSideloadGeoip(nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
bytes := C.GoBytes(block, blockSize)
|
||||
|
||||
return marshalString(tunnel.InstallSideloadGeoip(bytes))
|
||||
}
|
||||
28
core/src/main/golang/tunnel/conn.go
Normal file
28
core/src/main/golang/tunnel/conn.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
||||
)
|
||||
|
||||
func closeMatch(filter func(conn C.Conn) bool) {
|
||||
for _, c := range statistic.DefaultManager.Snapshot().Connections {
|
||||
if cc, ok := c.(C.Conn); ok {
|
||||
if filter(cc) {
|
||||
_ = cc.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func closeConnByGroup(name string) {
|
||||
closeMatch(func(conn C.Conn) bool {
|
||||
for _, c := range conn.Chains() {
|
||||
if c == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
50
core/src/main/golang/tunnel/connectivity.go
Normal file
50
core/src/main/golang/tunnel/connectivity.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
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.(*outbound.Proxy).ProxyAdapter.(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)
|
||||
}
|
||||
}
|
||||
26
core/src/main/golang/tunnel/geoip.go
Normal file
26
core/src/main/golang/tunnel/geoip.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
)
|
||||
|
||||
func InstallSideloadGeoip(block []byte) error {
|
||||
if block == nil {
|
||||
mmdb.InstallOverride(nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
db, err := geoip2.FromBytes(block)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load sideload geoip mmdb: %s", err.Error())
|
||||
}
|
||||
|
||||
mmdb.InstallOverride(db)
|
||||
|
||||
return nil
|
||||
}
|
||||
63
core/src/main/golang/tunnel/providers.go
Normal file
63
core/src/main/golang/tunnel/providers.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// +build !premium
|
||||
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/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"`
|
||||
}
|
||||
|
||||
func QueryProviders() []*Provider {
|
||||
p := tunnel.Providers()
|
||||
|
||||
providers := make([]provider.Provider, 0, len(p))
|
||||
|
||||
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.(provider.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(_ string, name string) error {
|
||||
p, ok := tunnel.Providers()[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("%s not found", name)
|
||||
}
|
||||
|
||||
return p.Update()
|
||||
}
|
||||
91
core/src/main/golang/tunnel/providers_premium.go
Normal file
91
core/src/main/golang/tunnel/providers_premium.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// +build premium
|
||||
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/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"`
|
||||
}
|
||||
|
||||
func QueryProviders() []*Provider {
|
||||
r := tunnel.RuleProviders()
|
||||
p := tunnel.ProxyProviders()
|
||||
|
||||
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.(provider.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.ProxyProviders()[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
|
||||
}
|
||||
195
core/src/main/golang/tunnel/proxies.go
Normal file
195
core/src/main/golang/tunnel/proxies.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/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"].(*outbound.Proxy).ProxyAdapter.(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.(*outbound.Proxy).ProxyAdapter.(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.(*outbound.Proxy).ProxyAdapter.(outboundgroup.ProxyGroup)
|
||||
if !ok {
|
||||
log.Warnln("Query group `%s`: invalid type %s", name, p.Type().String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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.(*outbound.Proxy).ProxyAdapter.(outboundgroup.ProxyGroup)
|
||||
if !ok {
|
||||
log.Warnln("Patch selector `%s`: invalid type %s", selector, p.Type().String())
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
s, ok := g.(*outboundgroup.Selector)
|
||||
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 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.(*outbound.Proxy).ProxyAdapter.(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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, &Proxy{
|
||||
Name: name,
|
||||
Title: strings.TrimSpace(title),
|
||||
Subtitle: strings.TrimSpace(subtitle),
|
||||
Type: px.Type().String(),
|
||||
Delay: int(px.LastDelay()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
9
core/src/main/golang/tunnel/state.go
Normal file
9
core/src/main/golang/tunnel/state.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
func QueryMode() string {
|
||||
return tunnel.Mode().String()
|
||||
}
|
||||
17
core/src/main/golang/tunnel/statistic.go
Normal file
17
core/src/main/golang/tunnel/statistic.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/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()
|
||||
}
|
||||
7
core/src/main/golang/tunnel/suspend.go
Normal file
7
core/src/main/golang/tunnel/suspend.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package tunnel
|
||||
|
||||
import "github.com/Dreamacro/clash/adapters/provider"
|
||||
|
||||
func Suspend(s bool) {
|
||||
provider.Suspend(s)
|
||||
}
|
||||
32
core/src/main/golang/utils.go
Normal file
32
core/src/main/golang/utils.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func marshalJson(obj interface{}) *C.char {
|
||||
res, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
return C.CString(string(res))
|
||||
}
|
||||
|
||||
func marshalString(obj interface{}) *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())
|
||||
}
|
||||
228
core/src/main/java/com/github/kr328/clash/core/Clash.kt
Normal file
228
core/src/main/java/com/github/kr328/clash/core/Clash.kt
Normal file
@@ -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 notifyInstalledAppsChanged(uids: List<Pair<Int, String>>) {
|
||||
val uidList = uids.joinToString(separator = ",") { "${it.first}:${it.second}" }
|
||||
|
||||
Bridge.nativeNotifyInstalledAppChanged(uidList)
|
||||
}
|
||||
|
||||
fun startTun(
|
||||
fd: Int,
|
||||
mtu: Int,
|
||||
gateway: String,
|
||||
mirror: String,
|
||||
dns: String,
|
||||
markSocket: (Int) -> Boolean,
|
||||
querySocketUid: (protocol: Int, source: InetSocketAddress, target: InetSocketAddress) -> Int
|
||||
) {
|
||||
Bridge.nativeStartTun(fd, mtu, gateway, mirror, "$dns:53", 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 installSideloadGeoip(data: ByteArray?) {
|
||||
Bridge.nativeInstallSideloadGeoip(data)
|
||||
}
|
||||
|
||||
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) {
|
||||
offer(Json.decodeFromString(LogMessage.serializer(), jsonPayload))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
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 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 nativeNotifyInstalledAppChanged(uidList: String)
|
||||
external fun nativeStartTun(
|
||||
fd: Int,
|
||||
mtu: Int,
|
||||
gateway: String,
|
||||
mirror: 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 nativeInstallSideloadGeoip(data: ByteArray?)
|
||||
external fun nativeQueryConfiguration(): String
|
||||
external fun nativeSubscribeLogcat(callback: LogcatInterface)
|
||||
|
||||
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
|
||||
|
||||
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,133 @@
|
||||
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("hosts")
|
||||
var hosts: Map<String, String>? = null,
|
||||
|
||||
@SerialName("dns")
|
||||
val dns: Dns = Dns(),
|
||||
|
||||
@SerialName("clash-for-android")
|
||||
val app: App = App(),
|
||||
) : Parcelable {
|
||||
@Serializable
|
||||
data class Dns(
|
||||
@SerialName("enable")
|
||||
var enable: 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("fallback-filter")
|
||||
val fallbackFilter: DnsFallbackFilter = DnsFallbackFilter()
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DnsFallbackFilter(
|
||||
@SerialName("geoip")
|
||||
var geoIp: Boolean? = 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 DnsEnhancedMode {
|
||||
@SerialName("normal")
|
||||
None,
|
||||
|
||||
@SerialName("redir-host")
|
||||
Mapping,
|
||||
|
||||
@SerialName("fake-ip")
|
||||
FakeIp,
|
||||
}
|
||||
|
||||
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,55 @@
|
||||
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),
|
||||
|
||||
Shadowsocks(false),
|
||||
ShadowsocksR(false),
|
||||
Snell(false),
|
||||
Socks5(false),
|
||||
Http(false),
|
||||
Vmess(false),
|
||||
Trojan(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
11
core/src/main/java/com/github/kr328/clash/core/util/Net.kt
Normal file
11
core/src/main/java/com/github/kr328/clash/core/util/Net.kt
Normal file
@@ -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,258 @@
|
||||
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 {
|
||||
@ExperimentalSerializationApi
|
||||
override val serializersModule: SerializersModule = EmptySerializersModule
|
||||
|
||||
@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 {
|
||||
@ExperimentalSerializationApi
|
||||
override val serializersModule: SerializersModule = EmptySerializersModule
|
||||
|
||||
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
|
||||
|
||||
"${data / 100}.${data % 100} GiB"
|
||||
}
|
||||
scaled > 1024 * 1024 * 100L -> {
|
||||
val data = scaled / 1024 / 1024
|
||||
|
||||
"${data / 100}.${data % 100} MiB"
|
||||
}
|
||||
scaled > 1024 * 100L -> {
|
||||
val data = scaled / 1024
|
||||
|
||||
"${data / 100}.${data % 100} KiB"
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user