Add Darwin FFI for macOS in-process embedding

- ffi_darwin.go: C-exported functions (minising_start/stop/is_running/
  get_peers/get_build_info/free) for c-archive buildmode
- main.go: exclude darwin+cgo from CLI build
- go.mod: fix sing-tun replace path after directory reorganization
This commit is contained in:
NeoMody
2026-04-05 01:49:36 +08:00
parent f53883dcc4
commit 160d010350
3 changed files with 162 additions and 2 deletions

160
ffi_darwin.go Normal file
View File

@@ -0,0 +1,160 @@
//go:build darwin && cgo
package main
/*
#include <stdlib.h>
*/
import "C"
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"sync"
"unsafe"
"github.com/netkits-dev/mini-sing/option"
)
var (
ffiMu sync.Mutex
ffiBox *Box
ffiCtx context.Context
ffiCancel context.CancelFunc
// Set via -ldflags at build time
buildTime = "unknown"
buildHash = "unknown"
)
//export minising_start
func minising_start(cconfig *C.char) *C.char {
config := C.GoString(cconfig)
// Setup logging from config
if logPath := extractLogPath(config); logPath != "" {
setupLog(logPath)
}
log.Printf("[ffi] mini-sing build=%s hash=%s", buildTime, buildHash)
log.Println("[ffi] start called, config bytes:", len(config))
var opts option.Options
if err := json.Unmarshal([]byte(config), &opts); err != nil {
return C.CString("parse: " + err.Error())
}
ffiMu.Lock()
defer ffiMu.Unlock()
if ffiBox != nil {
log.Println("[ffi] already running, rejecting start")
return C.CString("already running")
}
log.Println("[ffi] creating box")
ffiCtx, ffiCancel = context.WithCancel(context.Background())
box, err := NewBox(ffiCtx, opts)
if err != nil {
log.Println("[ffi] create box failed:", err)
ffiCancel()
return C.CString("create: " + err.Error())
}
log.Println("[ffi] box created, starting")
if err := box.Start(); err != nil {
log.Println("[ffi] start failed:", err)
box.Close()
ffiCancel()
return C.CString("start: " + err.Error())
}
ffiBox = box
log.Println("[ffi] started successfully")
return nil // nil = success
}
//export minising_stop
func minising_stop() {
log.Println("[ffi] stop called")
ffiMu.Lock()
defer ffiMu.Unlock()
if ffiBox != nil {
ffiCancel()
ffiBox.Close()
ffiBox = nil
log.Println("[ffi] stopped")
}
}
//export minising_is_running
func minising_is_running() C.int {
ffiMu.Lock()
defer ffiMu.Unlock()
if ffiBox != nil {
return 1
}
return 0
}
//export minising_get_peers
func minising_get_peers() *C.char {
ffiMu.Lock()
box := ffiBox
ffiMu.Unlock()
if box == nil {
return C.CString(`{"error":"not running"}`)
}
ts := box.findTailscale()
if ts == nil {
return C.CString(`{"error":"tailscale not enabled"}`)
}
self, peers, err := ts.Peers()
if err != nil {
return C.CString(fmt.Sprintf(`{"error":"%s"}`, err.Error()))
}
data, _ := json.Marshal(map[string]any{"self": self, "peers": peers})
return C.CString(string(data))
}
//export minising_get_build_info
func minising_get_build_info() *C.char {
info := fmt.Sprintf(`{"build_time":"%s","build_hash":"%s"}`, buildTime, buildHash)
return C.CString(info)
}
//export minising_free
func minising_free(p *C.char) {
C.free(unsafe.Pointer(p))
}
func extractLogPath(config string) string {
var raw map[string]json.RawMessage
if err := json.Unmarshal([]byte(config), &raw); err != nil {
return ""
}
logRaw, ok := raw["log"]
if !ok {
return ""
}
var logObj struct {
Output string `json:"output"`
}
if err := json.Unmarshal(logRaw, &logObj); err != nil {
return ""
}
return logObj.Output
}
func setupLog(logPath string) {
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return
}
log.SetOutput(f)
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
}
func main() {} // required for c-archive buildmode

2
go.mod
View File

@@ -23,7 +23,7 @@ require (
replace github.com/netkits-dev/mini-dns => ../mini-dns
replace github.com/sagernet/sing-tun => ../sing-tun
replace github.com/sagernet/sing-tun => ../reference/sing-tun
replace github.com/netkits-dev/tailnet => ../tailnet

View File

@@ -1,4 +1,4 @@
//go:build !android || !cgo
//go:build !cgo || (!android && !darwin)
package main