This commit is contained in:
Omid The Great
2024-01-28 17:38:37 +03:30
parent b7e0bc0f4a
commit a5435e6101
20 changed files with 1145 additions and 0 deletions

102
global/command_client.go Normal file
View File

@@ -0,0 +1,102 @@
package global
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"`
}

43
global/command_server.go Normal file
View File

@@ -0,0 +1,43 @@
package global
import (
"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(Starting)
if commandServer != nil {
commandServer.SetService(nil)
commandServer = nil
}
if box != nil {
box.Close()
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()
}

55
global/commands.go Normal file
View File

@@ -0,0 +1,55 @@
package global
import (
"github.com/sagernet/sing-box/experimental/libbox"
"github.com/sagernet/sing-box/log"
)
var (
statusClient *libbox.CommandClient
groupClient *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,
},
)
return statusClient.Connect()
case libbox.CommandGroup:
groupClient = libbox.NewCommandClient(
&CommandClientHandler{
port: port,
logger: logFactory.NewLogger("[Group Command Client]"),
},
&libbox.CommandClientOptions{
Command: libbox.CommandGroup,
StatusInterval: 1000000000,
},
)
return groupClient.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
}
return nil
}

15
global/constant.go Normal file
View File

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

365
global/global.go Normal file
View File

@@ -0,0 +1,365 @@
package global
import (
"encoding/json"
"errors"
"fmt"
"github.com/hiddify/libcore/config"
"github.com/sagernet/sing-box/experimental/libbox"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
var box *libbox.BoxService
var configOptions *config.ConfigOptions
var activeConfigPath *string
var logFactory *log.Factory
func setup(baseDir string, workingDir string, tempDir string, statusPort int64, debug bool) error {
Setup(baseDir, workingDir, tempDir)
statusPropagationPort = 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 err
}
logFactory = &factory
return nil
}
func parse(path string, tempPath string, debug bool) error {
config, err := config.ParseConfig(tempPath, debug)
if err != nil {
return err
}
err = os.WriteFile(path, config, 0777)
if err != nil {
return err
}
return nil
}
func changeConfigOptions(configOptionsJson string) error {
configOptions = &config.ConfigOptions{}
err := json.Unmarshal([]byte(configOptionsJson), configOptions)
if err != nil {
return err
}
return nil
}
func generateConfig(path string) (string, error) {
config, err := generateConfigFromFile(path, *configOptions)
if err != nil {
return "", err
}
return config, nil
}
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
}
func start(configPath string, disableMemoryLimit bool) error {
if status != Stopped {
return nil
}
propagateStatus(Starting)
activeConfigPath = &configPath
libbox.SetMemoryLimit(!disableMemoryLimit)
err := startService(false)
if err != nil {
return err
}
return nil
}
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 fmt.Errorf("error building config: %w", err)
}
config.SaveCurrentConfig(sWorkingPath, *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)
}
box = instance
commandServer.SetService(box)
propagateStatus(Started)
return nil
}
func stop() error {
if status != Started {
return nil
}
if box == nil {
return errors.New("instance not found")
}
propagateStatus(Stopping)
commandServer.SetService(nil)
err := box.Close()
if err != nil {
return err
}
box = nil
err = commandServer.Close()
if err != nil {
return err
}
commandServer = nil
propagateStatus(Stopped)
return nil
}
func restart(configPath string, disableMemoryLimit bool) error {
log.Debug("[Service] Restarting")
if status != Started {
return nil
}
if box == nil {
return errors.New("instance not found")
}
err := stop()
if err != nil {
return err
}
propagateStatus(Starting)
time.Sleep(250 * time.Millisecond)
activeConfigPath = &configPath
libbox.SetMemoryLimit(!disableMemoryLimit)
gErr := startService(false)
if gErr != nil {
return gErr
}
return nil
}
func startCommandClient(command int, port int64) error {
err := StartCommand(int32(command), port, *logFactory)
if err != nil {
return err
}
return nil
}
func stopCommandClient(command int) error {
err := StopCommand(int32(command))
if err != nil {
return err
}
return nil
}
func selectOutbound(groupTag string, outboundTag string) error {
err := libbox.NewStandaloneCommandClient().SelectOutbound(groupTag, outboundTag)
if err != nil {
return err
}
return nil
}
func urlTest(groupTag string) error {
err := libbox.NewStandaloneCommandClient().URLTest(groupTag)
if err != nil {
return err
}
return nil
}
func StartServiceC(delayStart bool, content string) error {
options, err := parseConfig(content)
if err != nil {
return stopAndAlert(EmptyConfiguration, err)
}
configOptions = &config.ConfigOptions{}
patchedOptions, err := config.BuildConfig(*configOptions, options)
options = *patchedOptions
err = config.SaveCurrentConfig(sWorkingPath, options)
if err != nil {
return err
}
err = startCommandServer(*logFactory)
if err != nil {
return stopAndAlert(StartCommandServer, err)
}
instance, err := NewService(options)
if err != nil {
return stopAndAlert(CreateService, err)
}
if delayStart {
time.Sleep(250 * time.Millisecond)
}
err = instance.Start()
if err != nil {
return stopAndAlert(StartService, err)
}
box = instance
commandServer.SetService(box)
propagateStatus(Started)
return nil
}
func StopService() error {
if status != Started {
return nil
}
if box == nil {
return errors.New("instance not found")
}
propagateStatus(Stopping)
commandServer.SetService(nil)
err := box.Close()
if err != nil {
return err
}
box = nil
err = commandServer.Close()
if err != nil {
return err
}
commandServer = nil
propagateStatus(Stopped)
return nil
}
func SetupC(baseDir string, workDir string, tempDir string, debug bool) error {
err := os.MkdirAll("./bin", 600)
if err != nil {
return err
}
err = os.MkdirAll("./work", 600)
if err != nil {
return err
}
err = os.MkdirAll("./temp", 600)
if err != nil {
return err
}
Setup(baseDir, workDir, tempDir)
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 err
}
logFactory = &factory
return nil
}
func MakeConfig(Ipv6 bool, ServerPort int, StrictRoute bool, EndpointIndependentNat bool, Stack string) string {
var ipv6 string
if Ipv6 {
ipv6 = " \"inet6_address\": \"fdfe:dcba:9876::1/126\",\n"
} else {
ipv6 = ""
}
base := "{\n \"inbounds\": [\n {\n \"type\": \"tun\",\n \"tag\": \"tun-in\",\n \"interface_name\": \"tun0\",\n \"inet4_address\": \"172.19.0.1/30\",\n" + ipv6 + " \"mtu\": 9000,\n \"auto_route\": true,\n \"strict_route\": " + fmt.Sprintf("%t", StrictRoute) + ",\n \"endpoint_independent_nat\": " + fmt.Sprintf("%t", EndpointIndependentNat) + ",\n \"stack\": \"" + Stack + "\"\n }],\n \"outbounds\": [\n {\n \"type\": \"socks\",\n \"tag\": \"socks-out\",\n \"server\": \"127.0.0.1\",\n \"server_port\": " + fmt.Sprintf("%d", ServerPort) + ",\n \"version\": \"5\"\n }\n ]\n}\n"
return base
}
func WriteParameters(Ipv6 bool, ServerPort int, StrictRoute bool, EndpointIndependentNat bool, Stack string) error {
parameters := fmt.Sprintf("%t,%d,%t,%t,%s", Ipv6, ServerPort, StrictRoute, EndpointIndependentNat, Stack)
err := os.WriteFile("bin/parameters.config", []byte(parameters), 600)
if err != nil {
return err
}
return nil
}
func ReadParameters() (bool, int, bool, bool, string, error) {
Data, err := os.ReadFile("bin/parameters.config")
if err != nil {
return false, 0, false, false, "", err
}
DataSlice := strings.Split(string(Data), ",")
Ipv6, _ := strconv.ParseBool(DataSlice[0])
ServerPort, _ := strconv.Atoi(DataSlice[1])
StrictRoute, _ := strconv.ParseBool(DataSlice[2])
EndpointIndependentNat, _ := strconv.ParseBool(DataSlice[3])
stack := DataSlice[4]
return Ipv6, ServerPort, StrictRoute, EndpointIndependentNat, stack, nil
}

18
global/parameters.go Normal file
View File

@@ -0,0 +1,18 @@
package global
type Stack string
const (
System Stack = "system"
GVisor Stack = "gVisor"
Mixed Stack = "mixed"
LWIP Stack = "LWIP"
)
type Parameters struct {
Ipv6 bool
ServerPort int
StrictRoute bool
EndpointIndependentNat bool
Stack Stack
}

68
global/service.go Normal file
View File

@@ -0,0 +1,68 @@
package global
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
}

33
global/status.go Normal file
View File

@@ -0,0 +1,33 @@
package global
import "C"
import (
"encoding/json"
"github.com/hiddify/libcore/bridge"
)
var statusPropagationPort int64
var status = Stopped
type StatusMessage struct {
Status string `json:"status"`
Alert *string `json:"alert"`
Message *string `json:"message"`
}
func propagateStatus(newStatus string) {
status = newStatus
msg, _ := json.Marshal(StatusMessage{Status: status})
bridge.SendStringToPort(statusPropagationPort, string(msg))
}
func stopAndAlert(alert string, err error) error {
status = Stopped
message := err.Error()
msg, _ := json.Marshal(StatusMessage{Status: status, Alert: &alert, Message: &message})
bridge.SendStringToPort(statusPropagationPort, string(msg))
return nil
}