refactor clash bridge

This commit is contained in:
Kr328 committed 2020-06-10 21:24:38 +08:00
1 parent 432fa115e2
commit 8581f53b20
17 files changed
+432 -266

No files matched your search

+1 -1
View File
@@ -145,7 +145,7 @@ task("compileClashCore") {
mkdirs()
}.resolve("libclash.so")
"go build --buildmode=c-shared -o \"$out\"".exec(pwd = golangSource, env = env)
"go build --buildmode=c-shared -trimpath -o \"$out\"".exec(pwd = golangSource, env = env)
}
}
}
+1 -1
View File
@@ -8,4 +8,4 @@ include_directories(${CLASH_OUTPUT}/${ANDROID_ABI} ${CLASH_SOURCE})
link_directories(${CLASH_OUTPUT}/${CMAKE_ANDROID_ARCH_ABI})
link_libraries(log clash)
add_library(bridge SHARED main.cpp main.h init.cpp query.cpp patch.cpp tun.cpp defer.cpp)
add_library(bridge SHARED main.cpp main.h init.cpp query.cpp patch.cpp tun.cpp defer.cpp event_queue.cpp)
+84 -48
View File
@@ -56,25 +56,34 @@ Java_com_github_kr328_clash_core_bridge_Bridge_downloadProfile__ILjava_lang_Stri
const char *b = context->getString(base);
const char *o = context->getString(output);
auto *ctx = new deferContext();
jobject completableFuture = context->newGlobalReference(context->newCompletableFuture());
ctx->completableFuture = context->newGlobalReference(context->newCompletableFuture());
uint64_t token = EventQueue::getInstance()->obtainToken();
ccall_t onSuccess = {
.context = ctx,
.function = &downloadContextSuccess
};
ccall_t onFailure = {
.context = ctx,
.function = &downloadContextFailure
};
EventQueue::getInstance()->registerHandler(COMPLETE, token, [completableFuture](event_type_t, uint64_t token, std::string const &payload) {
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
Master::runWithContext<void>(env, [&](Master::Context *context) {
if ( payload.empty() ) {
context->completeCompletableFuture(completableFuture, nullptr);
} else {
context->completeExceptionallyCompletableFuture(completableFuture, context->newClashException(payload));
}
downloadProfileFromFd(fd, b, o, onSuccess, onFailure);
context->removeGlobalReference(completableFuture);
});
return 0;
});
EventQueue::getInstance()->unregisterHandler(COMPLETE, token);
});
downloadProfileFromFd(fd, b, o, token);
context->releaseString(base, b);
context->releaseString(output, o);
return ctx->completableFuture;
return completableFuture;
});
}
@@ -89,26 +98,35 @@ Java_com_github_kr328_clash_core_bridge_Bridge_downloadProfile__Ljava_lang_Strin
const char *b = context->getString(base);
const char *o = context->getString(output);
auto *ctx = new deferContext();
jobject completableFuture = context->newGlobalReference(context->newCompletableFuture());
ctx->completableFuture = context->newGlobalReference(context->newCompletableFuture());
uint64_t token = EventQueue::getInstance()->obtainToken();
ccall_t onSuccess = {
.context = ctx,
.function = &downloadContextSuccess
};
ccall_t onFailure = {
.context = ctx,
.function = &downloadContextFailure
};
EventQueue::getInstance()->registerHandler(COMPLETE, token, [completableFuture](event_type_t, uint64_t token, std::string const &payload) {
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
Master::runWithContext<void>(env, [&](Master::Context *context) {
if ( payload.empty() ) {
context->completeCompletableFuture(completableFuture, nullptr);
} else {
context->completeExceptionallyCompletableFuture(completableFuture, context->newClashException(payload));
}
downloadProfileFromUrl(u, b, o, onSuccess, onFailure);
context->removeGlobalReference(completableFuture);
});
return 0;
});
EventQueue::getInstance()->unregisterHandler(COMPLETE, token);
});
downloadProfileFromUrl(u, b, o, token);
context->releaseString(url, u);
context->releaseString(base, b);
context->releaseString(output, o);
return ctx->completableFuture;
return completableFuture;
});
}
@@ -122,25 +140,34 @@ Java_com_github_kr328_clash_core_bridge_Bridge_loadProfile(JNIEnv *env, jclass c
const char *p = context->getString(path);
const char *b = context->getString(base);
auto *ctx = new deferContext();
jobject completableFuture = context->newGlobalReference(context->newCompletableFuture());
ctx->completableFuture = context->newGlobalReference(context->newCompletableFuture());
uint64_t token = EventQueue::getInstance()->obtainToken();
ccall_t onSuccess = {
.context = ctx,
.function = &downloadContextSuccess
};
ccall_t onFailure = {
.context = ctx,
.function = &downloadContextFailure
};
EventQueue::getInstance()->registerHandler(COMPLETE, token, [completableFuture](event_type_t, uint64_t token, std::string const &payload) {
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
Master::runWithContext<void>(env, [&](Master::Context *context) {
if ( payload.empty() ) {
context->completeCompletableFuture(completableFuture, nullptr);
} else {
context->completeExceptionallyCompletableFuture(completableFuture, context->newClashException(payload));
}
loadProfile(p, b, onSuccess, onFailure);
context->removeGlobalReference(completableFuture);
});
return 0;
});
EventQueue::getInstance()->unregisterHandler(COMPLETE, token);
});
loadProfile(p, b, token);
context->releaseString(path, p);
context->releaseString(base, b);
return ctx->completableFuture;
return completableFuture;
});
}
@@ -153,23 +180,32 @@ Java_com_github_kr328_clash_core_bridge_Bridge_performHealthCheck(JNIEnv *env, j
return Master::runWithContext<jobject>(env, [&](Master::Context *context) -> jobject {
const char *g = context->getString(group);
auto *ctx = new deferContext();
jobject completableFuture = context->newGlobalReference(context->newCompletableFuture());
ctx->completableFuture = context->newGlobalReference(context->newCompletableFuture());
uint64_t token = EventQueue::getInstance()->obtainToken();
ccall_t onSuccess = {
.context = ctx,
.function = &downloadContextSuccess
};
ccall_t onFailure = {
.context = ctx,
.function = &downloadContextFailure
};
EventQueue::getInstance()->registerHandler(COMPLETE, token, [completableFuture](event_type_t, uint64_t token, std::string const &payload) {
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
Master::runWithContext<void>(env, [&](Master::Context *context) {
if ( payload.empty() ) {
context->completeCompletableFuture(completableFuture, nullptr);
} else {
context->completeExceptionallyCompletableFuture(completableFuture, context->newClashException(payload));
}
performHealthCheck(g, onSuccess, onFailure);
context->removeGlobalReference(completableFuture);
});
return 0;
});
EventQueue::getInstance()->unregisterHandler(COMPLETE, token);
});
performHealthCheck(g, token);
context->releaseString(group, g);
return ctx->completableFuture;
return completableFuture;
});
}
+103
View File
@@ -0,0 +1,103 @@
#include "event_queue.h"
static void *handleEventQueue(void *context) {
auto *queue = reinterpret_cast<EventQueue*>(context);
while ( !queue->isClosed() ) {
event_t *e = queue->dequeueEvent();
EventQueue::Handler h = queue->findHandler(e->type, e->token);
h(e->type, e->token, e->payload);
answer_event(e->id);
free(e);
}
return nullptr;
}
EventQueue::EventQueue(): lock(), condition(), closed(false), token(0) {
instance = this;
pthread_mutex_init(&lock, nullptr);
pthread_cond_init(&condition, nullptr);
for ( int i = 0 ; i < DEFAULT_EVENT_QUEUE_PROCESSES ; i++ ) {
pthread_t tid = 0;
if ( pthread_create(&tid, nullptr, &handleEventQueue, this) < 0 )
abort();
}
}
void EventQueue::enqueueEvent(event_t *event) {
pthread_mutex_lock(&lock);
queue.push_back(event);
pthread_cond_signal(&condition);
pthread_mutex_unlock(&lock);
}
event_t *EventQueue::dequeueEvent() {
pthread_mutex_lock(&lock);
while ( queue.empty() )
pthread_cond_wait(&condition, &lock);
auto *result = queue.back();
queue.pop_back();
pthread_mutex_unlock(&lock);
return result;
}
void EventQueue::registerHandler(event_type_t type, uint64_t token, const EventQueue::Handler& handler) {
pthread_mutex_lock(&lock);
handlers[type][token] = handler;
pthread_mutex_unlock(&lock);
}
void EventQueue::unregisterHandler(event_type_t type, uint64_t token) {
pthread_mutex_lock(&lock);
handlers[type].erase(token);
pthread_mutex_unlock(&lock);
}
EventQueue::Handler EventQueue::findHandler(event_type_t type, uint64_t token) {
pthread_mutex_lock(&lock);
Handler result = handlers[type][token];
pthread_mutex_unlock(&lock);
if (result == nullptr)
return [](event_type_t type, uint64_t token, const std::string& payload){};
return result;
}
EventQueue *EventQueue::getInstance() {
return instance;
}
uint64_t EventQueue::obtainToken() {
pthread_mutex_lock(&lock);
uint64_t r = token++;
pthread_mutex_unlock(&lock);
return r;
}
EventQueue *EventQueue::instance;
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include <pthread.h>
#include <vector>
#include <map>
#include <functional>
#include <cstdint>
#include <string>
#include "event.h"
#define DEFAULT_EVENT_QUEUE_PROCESSES 8
class EventQueue {
public:
typedef std::function<void (event_type_t type, uint64_t token, std::string payload)> Handler;
public:
EventQueue();
public:
void enqueueEvent(event_t *event);
event_t *dequeueEvent();
public:
void registerHandler(event_type_t type, uint64_t token, const Handler& handler);
void unregisterHandler(event_type_t type, uint64_t token);
Handler findHandler(event_type_t type, uint64_t token);
public:
uint64_t obtainToken();
public:
static EventQueue *getInstance();
public:
inline bool isClosed() {
return closed;
}
private:
bool closed;
std::vector<event_t*> queue;
std::map<event_type_t, std::map<uint64_t, Handler>> handlers;
pthread_mutex_t lock;
pthread_cond_t condition;
uint64_t token;
public:
static EventQueue *instance;
};
+7
View File
@@ -224,6 +224,10 @@ jobject Master::Context::createProxyGroup(char const *name, proxy_type_t type,
return env->NewObject(master->cProxyGroup, master->cProxyGroupConstructor, env->NewStringUTF(name), ts, env->NewStringUTF(current), proxies);
}
static void enqueue_event(event_t *e) {
EventQueue::getInstance()->enqueueEvent(e);
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *unused) {
UNUSED(unused);
@@ -233,6 +237,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *unused) {
return -1;
new Master(vm, env);
new EventQueue();
set_event_handler(&enqueue_event);
return JNI_VERSION_1_6;
}
+1
View File
@@ -3,6 +3,7 @@
#include <functional>
#include "libclash.h"
#include "event_queue.h"
#define UNUSED(v) ((void)v)
+27 -46
View File
@@ -1,38 +1,6 @@
#include "main.h"
struct tunContext {
jobject callback;
};
static void tunContextOnNewSocket(void *context, void *argument) {
auto *ctx = reinterpret_cast<tunContext*>(context);
auto fd = reinterpret_cast<long>(argument);
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
Master::runWithContext<void>(env, [&](Master::Context *context) {
context->tunCallbackNewSocket(ctx->callback, fd);
});
return 0;
});
}
static void tunContextOnStop(void *context, void *argument) {
UNUSED(argument);
auto *ctx = reinterpret_cast<tunContext*>(context);
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
Master::runWithContext<void>(env, [&](Master::Context *context) {
context->tunCallbackStop(ctx->callback);
context->removeGlobalReference(ctx->callback);
});
return 0;
});
delete ctx;
}
#include "event_queue.h"
extern "C"
JNIEXPORT void JNICALL
@@ -48,20 +16,34 @@ Java_com_github_kr328_clash_core_bridge_Bridge_startTunDevice(JNIEnv *env, jclas
const char *dnsString = context->getString(dns);
jobject callbackGlobal = context->newGlobalReference(callback);
auto *callbackContext = new tunContext();
callbackContext->callback = callbackGlobal;
EventQueue::getInstance()->registerHandler(NEW_SOCKET, 0, [callbackGlobal](event_type_t, u_int64_t, const std::string& payload) {
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
Master::runWithContext<void>(env, [&](Master::Context *context) {
context->tunCallbackNewSocket(callbackGlobal, std::stoi(payload));
});
ccall_t onNewSocket = {
.function = &tunContextOnNewSocket,
.context = callbackContext
};
ccall_t onStop = {
.function = &tunContextOnStop,
.context = callbackContext
};
return 0;
});
});
char *exception = startTunDevice(fd, mtu, gatewayString, mirrorString, dnsString, onNewSocket, onStop);
EventQueue::getInstance()->registerHandler(TUN_STOP, 0, [callbackGlobal](event_type_t, u_int64_t, const std::string&) {
Master::runWithAttached<int>([&](JNIEnv *env) -> int {
Master::runWithContext<void>(env, [&](Master::Context *context) {
context->tunCallbackStop(callbackGlobal);
context->removeGlobalReference(callbackGlobal);
});
return 0;
});
auto queue = EventQueue::getInstance();
queue->unregisterHandler(NEW_SOCKET, 0);
queue->unregisterHandler(TUN_STOP, 0);
});
char *exception = startTunDevice(fd, mtu, gatewayString, mirrorString, dnsString);
context->releaseString(gateway, gatewayString);
context->releaseString(mirror, mirrorString);
@@ -69,9 +51,8 @@ Java_com_github_kr328_clash_core_bridge_Bridge_startTunDevice(JNIEnv *env, jclas
if ( exception != nullptr ) {
context->throwThrowable(context->newClashException(exception));
context->removeGlobalReference(callbackContext->callback);
context->removeGlobalReference(callbackGlobal);
free(exception);
delete callbackContext;
}
});
}
-35
View File
@@ -1,35 +0,0 @@
#include "ccall.h"
#include <pthread.h>
#include <stdlib.h>
#include <stdint.h>
extern void nextCcall(ccall_t *call, void **argument, uint64_t *index);
extern void finishCcall();
static void *run(void *arg) {
(void) arg;
while ( 1 ) {
ccall_t call;
void *argument;
uint64_t index;
nextCcall(&call, &argument, &index);
call.function(call.context, argument);
finishCcall(index);
}
return NULL;
}
void initialize_ccall(int pool_size) {
for ( int i = 0 ; i < pool_size ; i++ ) {
pthread_t tid = 0;
if ( pthread_create(&tid, NULL, &run, NULL) < 0 )
abort();
}
}
-66
View File
@@ -1,66 +0,0 @@
package main
/*
#include "ccall.h"
extern void initialize_ccall(int pool_size);
*/
import "C"
import (
"runtime"
"sync"
"unsafe"
)
type nativeCcall C.ccall_t
type ccallContext struct {
call nativeCcall
result chan struct{}
argument unsafe.Pointer
}
var ccallQueue = make(chan *ccallContext)
var ccallMap sync.Map
var currentIndex uint64
var indexLock sync.Mutex
func init() {
C.initialize_ccall(C.int(runtime.NumCPU()))
}
func callCcall(call nativeCcall, argument unsafe.Pointer) {
context := &ccallContext{
call: call,
result: make(chan struct{}),
argument: argument,
}
ccallQueue <- context
<- context.result
}
//export nextCcall
func nextCcall(ccall *nativeCcall, argument *unsafe.Pointer, index *uint64) {
ctx := <- ccallQueue
indexLock.Lock()
currentIndex++
*index = currentIndex
*ccall = ctx.call
*argument = ctx.argument
indexLock.Unlock()
ccallMap.Store(*index, ctx)
}
//export finishCcall
func finishCcall(index uint64) {
ctx, ok := ccallMap.Load(index)
if !ok {
return
}
ctx.(*ccallContext).result <- struct{}{}
}
-18
View File
@@ -1,18 +0,0 @@
#pragma once
#include <stdint.h>
#if __cplusplus
extern "C" {
#endif
typedef void (*ccall_function_t)(void *context, void *argument);
typedef struct ccall_t {
ccall_function_t function;
void *context;
} ccall_t;
#if __cplusplus
};
#endif
+15 -15
View File
@@ -8,29 +8,29 @@ import (
"github.com/Dreamacro/clash/tunnel"
"github.com/kr328/cfa/config"
"sync"
"unsafe"
)
//#include "buffer.h"
//#include "event.h"
import "C"
//export downloadProfileFromFd
func downloadProfileFromFd(fd int, base C.const_string_t, output C.const_string_t, onSuccess nativeCcall, onFailure nativeCcall) {
func downloadProfileFromFd(fd int, base C.const_string_t, output C.const_string_t, callbackId uint64) {
b := C.GoString(base)
o := C.GoString(output)
go func() {
err := config.DownloadFd(fd, o, b)
if err != nil {
callCcall(onFailure, unsafe.Pointer(C.CString(err.Error())))
sendEvent(C.COMPLETE, callbackId, err.Error())
} else {
callCcall(onSuccess, nil)
sendEvent(C.COMPLETE, callbackId, "")
}
}()
}
//export downloadProfileFromUrl
func downloadProfileFromUrl(url C.const_string_t, base C.const_string_t, output C.const_string_t, onSuccess nativeCcall, onFailure nativeCcall) {
func downloadProfileFromUrl(url C.const_string_t, base C.const_string_t, output C.const_string_t, callbackId uint64) {
u := C.GoString(url)
b := C.GoString(base)
o := C.GoString(output)
@@ -38,36 +38,36 @@ func downloadProfileFromUrl(url C.const_string_t, base C.const_string_t, output
go func() {
err := config.DownloadUrl(u, o, b)
if err != nil {
callCcall(onFailure, unsafe.Pointer(C.CString(err.Error())))
sendEvent(C.COMPLETE, callbackId, err.Error())
} else {
callCcall(onSuccess, nil)
sendEvent(C.COMPLETE, callbackId, "")
}
}()
}
//export loadProfile
func loadProfile(path C.const_string_t, base C.const_string_t, onSuccess nativeCcall, onFailure nativeCcall) {
func loadProfile(path C.const_string_t, base C.const_string_t, callbackId uint64) {
p := C.GoString(path)
b := C.GoString(base)
go func() {
err := config.LoadFromFile(p, b)
if err != nil {
callCcall(onFailure, unsafe.Pointer(C.CString(err.Error())))
sendEvent(C.COMPLETE, callbackId, err.Error())
} else {
callCcall(onSuccess, nil)
sendEvent(C.COMPLETE, callbackId, "")
}
}()
}
//export performHealthCheck
func performHealthCheck(group C.const_string_t, onSuccess nativeCcall, onFailure nativeCcall) {
func performHealthCheck(group C.const_string_t, callbackId uint64) {
g := C.GoString(group)
go func() {
p := tunnel.Proxies()[g]
if p == nil {
callCcall(onFailure, unsafe.Pointer(C.CString("No such proxy group")))
sendEvent(C.COMPLETE, callbackId, "No such proxy group")
log.Warnln("Perform health check failure: %s not found", g)
@@ -76,7 +76,7 @@ func performHealthCheck(group C.const_string_t, onSuccess nativeCcall, onFailure
pw, ok := p.(*outbound.Proxy)
if !ok {
callCcall(onFailure, unsafe.Pointer(C.CString("Invalid group")))
sendEvent(C.COMPLETE, callbackId, "Invalid group")
log.Warnln("Perform health check failure: %s not valid group", g)
@@ -85,7 +85,7 @@ func performHealthCheck(group C.const_string_t, onSuccess nativeCcall, onFailure
adapter, ok := pw.ProxyAdapter.(outboundgroup.ProxyGroup)
if !ok {
callCcall(onFailure, unsafe.Pointer(C.CString("Invalid group")))
sendEvent(C.COMPLETE, callbackId, "Invalid group")
log.Warnln("Perform health check failure: %s not valid group", g)
@@ -107,6 +107,6 @@ func performHealthCheck(group C.const_string_t, onSuccess nativeCcall, onFailure
wg.Wait()
callCcall(onSuccess, nil)
sendEvent(C.COMPLETE, callbackId, "")
}()
}
+28
View File
@@ -0,0 +1,28 @@
#include "event.h"
#include <malloc.h>
#include <string.h>
extern void answerEvent(uint64_t id);
static event_handler_t event_handler;
void set_event_handler(event_handler_t handler) {
event_handler = handler;
}
void send_event(event_t *event, const void *payload, size_t payload_length) {
event_handler_t h = event_handler;
if ( h != NULL ) {
memcpy(event->payload, payload, payload_length);
h(event);
}
else {
answer_event(event->id);
free(event);
}
}
void answer_event(uint64_t id) {
answerEvent(id);
}
+55
View File
@@ -0,0 +1,55 @@
package main
//#include "event.h"
import "C"
import (
"sync"
"unsafe"
)
type EventWaiter struct {
result chan struct{}
}
var idLock = sync.Mutex{}
var currentId = uint64(0)
var ids = map[uint64]*EventWaiter{}
//export answerEvent
func answerEvent(id uint64) {
idLock.Lock()
defer idLock.Unlock()
waiter, ok := ids[id]
if ok {
close(waiter.result)
delete(ids, id)
}
}
func sendEvent(t C.event_type_t, token uint64, payload string) *EventWaiter {
idLock.Lock()
defer idLock.Unlock()
currentId++
id := currentId
r := &EventWaiter{make(chan struct{})}
ids[id] = r
p := append([]byte(payload), 0)
e := allocCEvent(len(p))
e.id = C.uint64_t(id)
e._type = t
e.token = C.uint64_t(token)
C.send_event(e, unsafe.Pointer(&p[0]), C.size_t(len(p)))
return r
}
func allocCEvent(payloadLength int) *C.event_t {
return (*C.event_t)(C.malloc(C.sizeof_event_t + C.size_t(payloadLength)))
}
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#if __cplusplus
extern "C" {
#endif
typedef enum {
NEW_SOCKET, TUN_STOP, COMPLETE
} event_type_t;
typedef struct event_t {
uint64_t id;
uint64_t token;
event_type_t type;
char payload[];
} event_t;
typedef void (*event_handler_t)(event_t *event);
void set_event_handler(event_handler_t handler);
void send_event(event_t *event, const void *payload, size_t payload_length);
void answer_event(uint64_t id);
#if __cplusplus
};
#endif
+29 -36
View File
@@ -1,57 +1,50 @@
package main
//#include "buffer.h"
import "C"
import (
"github.com/Dreamacro/clash/component/dialer"
"github.com/kr328/cfa/tun"
"net"
"strconv"
"sync"
"syscall"
"unsafe"
)
var lock sync.Mutex
//#include "buffer.h"
//#include "event.h"
import "C"
var tunLock sync.Mutex
func init() {
c := func(_, _ string, conn syscall.RawConn) error {
return conn.Control(func(fd uintptr) {
<- sendEvent(C.NEW_SOCKET, 0, strconv.Itoa(int(fd))).result
})
}
dialer.DialerHook = func(d *net.Dialer) error {
d.Control = c
return nil
}
dialer.ListenConfigHook = func(l *net.ListenConfig) error {
l.Control = c
return nil
}
}
//export startTunDevice
func startTunDevice(fd, mtu int, gateway, mirror, dns C.const_string_t, onNewSocket nativeCcall, onStop nativeCcall) *C.char {
func startTunDevice(fd, mtu int, gateway, mirror, dns C.const_string_t) *C.char {
stopTunDevice()
lock.Lock()
defer lock.Unlock()
tunLock.Lock()
defer tunLock.Unlock()
g := C.GoString(gateway)
m := C.GoString(mirror)
d := C.GoString(dns)
l := &sync.Mutex{}
c := false
closed := &c
dialer.DialerHook = func(d *net.Dialer) error {
d.Control = func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
l.Lock()
defer l.Unlock()
if *closed {
return
}
//noinspection GoVetUnsafePointer
callCcall(onNewSocket, unsafe.Pointer(fd))
})
}
return nil
}
err := tun.StartTunDevice(fd, mtu, g, m, d, func() {
l.Lock()
defer l.Unlock()
*closed = true
callCcall(onStop, nil)
sendEvent(C.TUN_STOP, 0, "")
})
if err != nil {
return C.CString(err.Error())
@@ -62,8 +55,8 @@ func startTunDevice(fd, mtu int, gateway, mirror, dns C.const_string_t, onNewSoc
//export stopTunDevice
func stopTunDevice() {
lock.Lock()
defer lock.Unlock()
tunLock.Lock()
defer tunLock.Unlock()
tun.StopTunDevice()
}
+1
View File
@@ -5,3 +5,4 @@ import "io"
func CloseSilent(closer io.Closer) {
_ = closer.Close()
}