big refactor. make compatible v2 and v1 interface

This commit is contained in:
Hiddify
2024-03-16 01:39:33 +01:00
parent c51f73faed
commit 361419b95e
27 changed files with 1455 additions and 1085 deletions

View File

@@ -1,21 +0,0 @@
// Copyright 2015 Daniel Theophanes.
// Use of this source code is governed by a zlib-style
// license that can be found in the LICENSE file.
// simple does nothing except block while running the service.
package main
import "C"
import (
"fmt"
"github.com/hiddify/libcore/admin_service"
)
//export AdminServiceStart
func AdminServiceStart(arg *C.char) *C.char {
goArg := C.GoString(arg)
exitCode, outMessage := admin_service.StartService(goArg)
return C.CString(fmt.Sprintf("%d %s", exitCode, outMessage))
}

View File

@@ -1,102 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"github.com/hiddify/libcore/bridge"
"github.com/sagernet/sing-box/experimental/libbox"
"github.com/sagernet/sing-box/log"
)
type CommandClientHandler struct {
port int64
logger log.Logger
}
func (cch *CommandClientHandler) Connected() {
cch.logger.Debug("CONNECTED")
}
func (cch *CommandClientHandler) Disconnected(message string) {
cch.logger.Debug("DISCONNECTED: ", message)
}
func (cch *CommandClientHandler) ClearLog() {
cch.logger.Debug("clear log")
}
func (cch *CommandClientHandler) WriteLog(message string) {
cch.logger.Debug("log: ", message)
}
func (cch *CommandClientHandler) WriteStatus(message *libbox.StatusMessage) {
msg, err := json.Marshal(
map[string]int64{
"connections-in": int64(message.ConnectionsIn),
"connections-out": int64(message.ConnectionsOut),
"uplink": message.Uplink,
"downlink": message.Downlink,
"uplink-total": message.UplinkTotal,
"downlink-total": message.DownlinkTotal,
},
)
cch.logger.Debug("Memory: ", libbox.FormatBytes(message.Memory), ", Goroutines: ", message.Goroutines)
if err != nil {
bridge.SendStringToPort(cch.port, fmt.Sprintf("error: %e", err))
} else {
bridge.SendStringToPort(cch.port, string(msg))
}
}
func (cch *CommandClientHandler) WriteGroups(message libbox.OutboundGroupIterator) {
if message == nil {
return
}
groups := []*OutboundGroup{}
for message.HasNext() {
group := message.Next()
items := group.GetItems()
groupItems := []*OutboundGroupItem{}
for items.HasNext() {
item := items.Next()
groupItems = append(groupItems,
&OutboundGroupItem{
Tag: item.Tag,
Type: item.Type,
URLTestTime: item.URLTestTime,
URLTestDelay: item.URLTestDelay,
},
)
}
groups = append(groups, &OutboundGroup{Tag: group.Tag, Type: group.Type, Selected: group.Selected, Items: groupItems})
}
response, err := json.Marshal(groups)
if err != nil {
bridge.SendStringToPort(cch.port, fmt.Sprintf("error: %e", err))
} else {
bridge.SendStringToPort(cch.port, string(response))
}
}
func (cch *CommandClientHandler) InitializeClashMode(modeList libbox.StringIterator, currentMode string) {
cch.logger.Debug("initial clash mode: ", currentMode)
}
func (cch *CommandClientHandler) UpdateClashMode(newMode string) {
cch.logger.Debug("update clash mode: ", newMode)
}
type OutboundGroup struct {
Tag string `json:"tag"`
Type string `json:"type"`
Selected string `json:"selected"`
Items []*OutboundGroupItem `json:"items"`
}
type OutboundGroupItem struct {
Tag string `json:"tag"`
Type string `json:"type"`
URLTestTime int64 `json:"url-test-time"`
URLTestDelay int32 `json:"url-test-delay"`
}

View File

@@ -1,46 +0,0 @@
package main
import (
pb "github.com/hiddify/libcore/hiddifyrpc"
v2 "github.com/hiddify/libcore/v2"
"github.com/sagernet/sing-box/experimental/libbox"
"github.com/sagernet/sing-box/log"
)
var commandServer *libbox.CommandServer
type CommandServerHandler struct {
logger log.Logger
}
func (csh *CommandServerHandler) ServiceReload() error {
csh.logger.Trace("Reloading service")
propagateStatus(pb.CoreState_STARTING)
if commandServer != nil {
commandServer.SetService(nil)
commandServer = nil
}
if v2.Box != nil {
v2.Box.Close()
v2.Box = nil
}
return startService(true)
}
func (csh *CommandServerHandler) GetSystemProxyStatus() *libbox.SystemProxyStatus {
csh.logger.Trace("Getting system proxy status")
return &libbox.SystemProxyStatus{Available: true, Enabled: false}
}
func (csh *CommandServerHandler) SetSystemProxyEnabled(isEnabled bool) error {
csh.logger.Trace("Setting system proxy status, enabled? ", isEnabled)
return csh.ServiceReload()
}
func startCommandServer(logFactory log.Factory) error {
logger := logFactory.NewLogger("[Command Server Handler]")
logger.Trace("Starting command server")
commandServer = libbox.NewCommandServer(&CommandServerHandler{logger: logger}, 300)
return commandServer.Start()
}

View File

@@ -1,72 +0,0 @@
package main
import (
"github.com/sagernet/sing-box/experimental/libbox"
"github.com/sagernet/sing-box/log"
)
var (
statusClient *libbox.CommandClient
groupClient *libbox.CommandClient
groupInfoOnlyClient *libbox.CommandClient
)
func StartCommand(command int32, port int64, logFactory log.Factory) error {
switch command {
case libbox.CommandStatus:
statusClient = libbox.NewCommandClient(
&CommandClientHandler{
port: port,
logger: logFactory.NewLogger("[Status Command Client]"),
},
&libbox.CommandClientOptions{
Command: libbox.CommandStatus,
StatusInterval: 1000000000, //1000ms debounce
},
)
return statusClient.Connect()
case libbox.CommandGroup:
groupClient = libbox.NewCommandClient(
&CommandClientHandler{
port: port,
logger: logFactory.NewLogger("[Group Command Client]"),
},
&libbox.CommandClientOptions{
Command: libbox.CommandGroup,
StatusInterval: 300000000, //300ms debounce
},
)
return groupClient.Connect()
case libbox.CommandGroupInfoOnly:
groupInfoOnlyClient = libbox.NewCommandClient(
&CommandClientHandler{
port: port,
logger: logFactory.NewLogger("[GroupInfoOnly Command Client]"),
},
&libbox.CommandClientOptions{
Command: libbox.CommandGroupInfoOnly,
StatusInterval: 300000000, //300ms debounce
},
)
return groupInfoOnlyClient.Connect()
}
return nil
}
func StopCommand(command int32) error {
switch command {
case libbox.CommandStatus:
err := statusClient.Disconnect()
statusClient = nil
return err
case libbox.CommandGroup:
err := groupClient.Disconnect()
groupClient = nil
return err
case libbox.CommandGroupInfoOnly:
err := groupInfoOnlyClient.Disconnect()
groupInfoOnlyClient = nil
return err
}
return nil
}

View File

@@ -1,15 +0,0 @@
package main
const (
Stopped = "Stopped"
Starting = "Starting"
Started = "Started"
Stopping = "Stopping"
)
const (
EmptyConfiguration = "EmptyConfiguration"
StartCommandServer = "StartCommandServer"
CreateService = "CreateService"
StartService = "StartService"
)

View File

@@ -7,10 +7,7 @@ import "C"
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"time"
"unsafe"
"github.com/hiddify/libcore/bridge"
@@ -18,16 +15,9 @@ import (
pb "github.com/hiddify/libcore/hiddifyrpc"
v2 "github.com/hiddify/libcore/v2"
"github.com/sagernet/sing-box/experimental/libbox"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
// var v2.Box *libbox.BoxService
var configOptions *config.ConfigOptions
var activeConfigPath *string
var logFactory *log.Factory
//export setupOnce
func setupOnce(api unsafe.Pointer) {
bridge.InitializeDartApi(api)
@@ -35,287 +25,146 @@ func setupOnce(api unsafe.Pointer) {
//export setup
func setup(baseDir *C.char, workingDir *C.char, tempDir *C.char, statusPort C.longlong, debug bool) (CErr *C.char) {
defer config.DeferPanicToError("setup", func(err error) {
CErr = C.CString(err.Error())
fmt.Printf("Error: %+v\n", err)
})
err := v2.Setup(C.GoString(baseDir), C.GoString(workingDir), C.GoString(tempDir), int64(statusPort), debug)
Setup(C.GoString(baseDir), C.GoString(workingDir), C.GoString(tempDir))
statusPropagationPort = int64(statusPort)
var defaultWriter io.Writer
if !debug {
defaultWriter = io.Discard
}
factory, err := log.New(
log.Options{
DefaultWriter: defaultWriter,
BaseTime: time.Now(),
Observable: false,
})
if err != nil {
return C.CString(err.Error())
}
logFactory = &factory
return C.CString("")
return emptyOrErrorC(err)
}
//export parse
func parse(path *C.char, tempPath *C.char, debug bool) (CErr *C.char) {
defer config.DeferPanicToError("parse", func(err error) {
CErr = C.CString(err.Error())
res, err := v2.Parse(&pb.ParseRequest{
ConfigPath: C.GoString(path),
TempPath: C.GoString(tempPath),
})
if err != nil {
log.Error(err.Error())
return C.CString(err.Error())
}
config, err := config.ParseConfig(C.GoString(tempPath), debug)
if err != nil {
return C.CString(err.Error())
}
err = os.WriteFile(C.GoString(path), config, 0644)
if err != nil {
return C.CString(err.Error())
}
return C.CString("")
err = os.WriteFile(C.GoString(path), []byte(res.Content), 0644)
return emptyOrErrorC(err)
}
//export changeConfigOptions
func changeConfigOptions(configOptionsJson *C.char) (CErr *C.char) {
defer config.DeferPanicToError("changeConfigOptions", func(err error) {
CErr = C.CString(err.Error())
_, err := v2.ChangeConfigOptions(&pb.ChangeConfigOptionsRequest{
ConfigOptionsJson: C.GoString(configOptionsJson),
})
configOptions = &config.ConfigOptions{}
err := json.Unmarshal([]byte(C.GoString(configOptionsJson)), configOptions)
if err != nil {
return C.CString(err.Error())
}
if configOptions.Warp.WireguardConfigStr != "" {
err := json.Unmarshal([]byte(configOptions.Warp.WireguardConfigStr), &configOptions.Warp.WireguardConfig)
if err != nil {
return C.CString(err.Error())
}
}
return C.CString("")
return emptyOrErrorC(err)
}
//export generateConfig
func generateConfig(path *C.char) (res *C.char) {
defer config.DeferPanicToError("generateConfig", func(err error) {
res = C.CString("error" + err.Error())
_, err := v2.GenerateConfig(&pb.GenerateConfigRequest{
Path: C.GoString(path),
})
config, err := generateConfigFromFile(C.GoString(path), *configOptions)
if err != nil {
return C.CString("error" + err.Error())
}
return C.CString(config)
}
func generateConfigFromFile(path string, configOpt config.ConfigOptions) (string, error) {
os.Chdir(filepath.Dir(path))
content, err := os.ReadFile(path)
if err != nil {
return "", err
}
options, err := parseConfig(string(content))
if err != nil {
return "", err
}
config, err := config.BuildConfigJson(configOpt, options)
if err != nil {
return "", err
}
return config, nil
return emptyOrErrorC(err)
}
//export start
func start(configPath *C.char, disableMemoryLimit bool) (CErr *C.char) {
defer config.DeferPanicToError("start", func(err error) {
stopAndAlert("Unexpected Error!", err)
CErr = C.CString(err.Error())
_, err := v2.Start(&pb.StartRequest{
ConfigPath: C.GoString(configPath),
EnableOldCommandServer: true,
DisableMemoryLimit: disableMemoryLimit,
})
if v2.CoreState != pb.CoreState_STOPPED {
return C.CString("")
}
propagateStatus(pb.CoreState_STARTING)
path := C.GoString(configPath)
activeConfigPath = &path
libbox.SetMemoryLimit(!disableMemoryLimit)
err := startService(false)
if err != nil {
return C.CString(err.Error())
}
return C.CString("")
}
func startService(delayStart bool) error {
content, err := os.ReadFile(*activeConfigPath)
if err != nil {
return stopAndAlert(EmptyConfiguration, err)
}
options, err := parseConfig(string(content))
if err != nil {
return stopAndAlert(EmptyConfiguration, err)
}
os.Chdir(filepath.Dir(*activeConfigPath))
var patchedOptions *option.Options
patchedOptions, err = config.BuildConfig(*configOptions, options)
if err != nil {
return stopAndAlert("Error Building Config", err)
}
config.SaveCurrentConfig(filepath.Join(sWorkingPath, "current-config.json"), *patchedOptions)
err = startCommandServer(*logFactory)
if err != nil {
return stopAndAlert(StartCommandServer, err)
}
instance, err := NewService(*patchedOptions)
if err != nil {
return stopAndAlert(CreateService, err)
}
if delayStart {
time.Sleep(250 * time.Millisecond)
}
err = instance.Start()
if err != nil {
return stopAndAlert(StartService, err)
}
v2.Box = instance
commandServer.SetService(v2.Box)
propagateStatus(pb.CoreState_STARTED)
return nil
return emptyOrErrorC(err)
}
//export stop
func stop() (CErr *C.char) {
defer config.DeferPanicToError("stop", func(err error) {
stopAndAlert("Unexpected Error in Stop!", err)
CErr = C.CString(err.Error())
})
if v2.CoreState != pb.CoreState_STARTED {
stopAndAlert("Already Stopped", nil)
return C.CString("")
}
if v2.Box == nil {
return C.CString("instance not found")
}
propagateStatus(pb.CoreState_STOPPING)
config.DeactivateTunnelService()
commandServer.SetService(nil)
err := v2.Box.Close()
if err != nil {
stopAndAlert("Unexpected Error in Close!", err)
return C.CString(err.Error())
}
v2.Box = nil
err = commandServer.Close()
if err != nil {
stopAndAlert("Unexpected Error in Stop CommandServer/!", err)
return C.CString(err.Error())
}
commandServer = nil
propagateStatus(pb.CoreState_STOPPED)
return C.CString("")
_, err := v2.Stop()
return emptyOrErrorC(err)
}
//export restart
func restart(configPath *C.char, disableMemoryLimit bool) (CErr *C.char) {
defer config.DeferPanicToError("restart", func(err error) {
stopAndAlert("Unexpected Error!", err)
CErr = C.CString(err.Error())
_, err := v2.Restart(&pb.StartRequest{
ConfigPath: C.GoString(configPath),
EnableOldCommandServer: true,
DisableMemoryLimit: disableMemoryLimit,
})
log.Debug("[Service] Restarting")
if v2.CoreState != pb.CoreState_STARTED {
return C.CString("")
}
if v2.Box == nil {
return C.CString("instance not found")
}
err := stop()
if C.GoString(err) != "" {
return err
}
propagateStatus(pb.CoreState_STARTING)
time.Sleep(250 * time.Millisecond)
path := C.GoString(configPath)
activeConfigPath = &path
libbox.SetMemoryLimit(!disableMemoryLimit)
gErr := startService(false)
if gErr != nil {
return C.CString(gErr.Error())
}
return C.CString("")
return emptyOrErrorC(err)
}
//export startCommandClient
func startCommandClient(command C.int, port C.longlong) *C.char {
err := StartCommand(int32(command), int64(port), *logFactory)
if err != nil {
return C.CString(err.Error())
}
return C.CString("")
err := v2.StartCommand(int32(command), int64(port))
return emptyOrErrorC(err)
}
//export stopCommandClient
func stopCommandClient(command C.int) *C.char {
err := StopCommand(int32(command))
if err != nil {
return C.CString(err.Error())
}
return C.CString("")
err := v2.StopCommand(int32(command))
return emptyOrErrorC(err)
}
//export selectOutbound
func selectOutbound(groupTag *C.char, outboundTag *C.char) (CErr *C.char) {
defer config.DeferPanicToError("selectOutbound", func(err error) {
CErr = C.CString(err.Error())
_, err := v2.SelectOutbound(&pb.SelectOutboundRequest{
GroupTag: C.GoString(groupTag),
OutboundTag: C.GoString(outboundTag),
})
err := libbox.NewStandaloneCommandClient().SelectOutbound(C.GoString(groupTag), C.GoString(outboundTag))
if err != nil {
return C.CString(err.Error())
}
return C.CString("")
return emptyOrErrorC(err)
}
//export urlTest
func urlTest(groupTag *C.char) (CErr *C.char) {
defer config.DeferPanicToError("urlTest", func(err error) {
CErr = C.CString(err.Error())
_, err := v2.UrlTest(&pb.UrlTestRequest{
GroupTag: C.GoString(groupTag),
})
err := libbox.NewStandaloneCommandClient().URLTest(C.GoString(groupTag))
if err != nil {
return C.CString(err.Error())
return emptyOrErrorC(err)
}
func emptyOrErrorC(err error) *C.char {
if err == nil {
return C.CString("")
}
return C.CString("")
log.Error(err.Error())
return C.CString(err.Error())
}
//export generateWarpConfig
func generateWarpConfig(licenseKey *C.char, accountId *C.char, accessToken *C.char) (CResp *C.char) {
defer config.DeferPanicToError("generateWarpConfig", func(err error) {
CResp = C.CString(fmt.Sprint("error: ", err.Error()))
res, err := v2.GenerateWarpConfig(&pb.GenerateWarpConfigRequest{
LicenseKey: C.GoString(licenseKey),
AccountId: C.GoString(accountId),
AccessToken: C.GoString(accessToken),
})
account, err := config.GenerateWarpAccount(C.GoString(licenseKey), C.GoString(accountId), C.GoString(accessToken))
if err != nil {
return C.CString(fmt.Sprint("error: ", err.Error()))
}
return C.CString(account)
warpAccount := config.WarpAccount{
AccountID: res.Account.AccountId,
AccessToken: res.Account.AccessToken,
}
warpConfig := config.WarpWireguardConfig{
PrivateKey: res.Config.PrivateKey,
LocalAddressIPv4: res.Config.LocalAddressIpv4,
LocalAddressIPv6: res.Config.LocalAddressIpv6,
PeerPublicKey: res.Config.PeerPublicKey,
}
log := res.Log
response := &config.WarpGenerationResponse{
WarpAccount: warpAccount,
Log: log,
Config: warpConfig,
}
responseJson, err := json.Marshal(response)
if err != nil {
return C.CString("")
}
return C.CString(string(responseJson))
}
func main() {}

10
custom/grpc_interface.go Normal file
View File

@@ -0,0 +1,10 @@
package main
import "C"
import v2 "github.com/hiddify/libcore/v2"
//export StartCoreGrpcServer
func StartCoreGrpcServer(listenAddress *C.char) (CErr *C.char) {
err := v2.StartCoreGrpcServer(C.GoString(listenAddress))
return emptyOrErrorC(err)
}

View File

@@ -1,68 +0,0 @@
package main
import (
"context"
"os"
"runtime"
runtimeDebug "runtime/debug"
B "github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-box/experimental/libbox"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/filemanager"
"github.com/sagernet/sing/service/pause"
)
var (
sWorkingPath string
sTempPath string
sUserID int
sGroupID int
)
func Setup(basePath string, workingPath string, tempPath string) {
tcpConn := runtime.GOOS == "windows" //TODO add TVOS
libbox.Setup(basePath, workingPath, tempPath, tcpConn)
sWorkingPath = workingPath
os.Chdir(sWorkingPath)
sTempPath = tempPath
sUserID = os.Getuid()
sGroupID = os.Getgid()
}
func NewService(options option.Options) (*libbox.BoxService, error) {
runtimeDebug.FreeOSMemory()
ctx, cancel := context.WithCancel(context.Background())
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
urlTestHistoryStorage := urltest.NewHistoryStorage()
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
instance, err := B.New(B.Options{
Context: ctx,
Options: options,
})
if err != nil {
cancel()
return nil, E.Cause(err, "create service")
}
runtimeDebug.FreeOSMemory()
service := libbox.NewBoxService(
ctx,
cancel,
instance,
service.FromContext[pause.Manager](ctx),
urlTestHistoryStorage,
)
return &service, nil
}
func parseConfig(configContent string) (option.Options, error) {
var options option.Options
err := options.UnmarshalJSON([]byte(configContent))
if err != nil {
return option.Options{}, E.Cause(err, "decode config")
}
return options, nil
}

View File

@@ -1,69 +0,0 @@
package main
import "C"
import (
"encoding/json"
"fmt"
"github.com/hiddify/libcore/bridge"
"github.com/hiddify/libcore/config"
pb "github.com/hiddify/libcore/hiddifyrpc"
v2 "github.com/hiddify/libcore/v2"
)
var statusPropagationPort int64
// var status = Stopped
type StatusMessage struct {
Status string `json:"status"`
Alert *string `json:"alert"`
Message *string `json:"message"`
}
func propagateStatus(newStatus pb.CoreState) {
v2.CoreState = newStatus
msg, _ := json.Marshal(StatusMessage{Status: convert2OldState(v2.CoreState)})
bridge.SendStringToPort(statusPropagationPort, string(msg))
}
func convert2OldState(newStatus pb.CoreState) string {
if newStatus == pb.CoreState_STOPPED {
return Stopped
}
if newStatus == pb.CoreState_STARTED {
return Started
}
if newStatus == pb.CoreState_STARTING {
return Starting
}
if newStatus == pb.CoreState_STOPPING {
return Stopping
}
return "Invalid"
}
func stopAndAlert(alert string, err error) (resultErr error) {
defer config.DeferPanicToError("stopAndAlert", func(err error) {
resultErr = err
})
v2.CoreState = pb.CoreState_STOPPED
message := err.Error()
fmt.Printf("Error: %s: %s\n", alert, message)
msg, _ := json.Marshal(StatusMessage{Status: convert2OldState(v2.CoreState), Alert: &alert, Message: &message})
bridge.SendStringToPort(statusPropagationPort, string(msg))
go config.DeactivateTunnelService()
if commandServer != nil {
commandServer.SetService(nil)
}
if v2.Box != nil {
v2.Box.Close()
v2.Box = nil
}
if commandServer != nil {
commandServer.Close()
}
return nil
}