From a5435e61010c4fc87132a827d88492cd9994e403 Mon Sep 17 00:00:00 2001 From: Omid The Great Date: Sun, 28 Jan 2024 17:38:37 +0330 Subject: [PATCH] . --- .gitignore | 1 + cmd/cmd_service.go | 10 + cmd/cmd_service_install.go | 12 ++ cmd/cmd_service_start.go | 12 ++ cmd/cmd_service_stop.go | 12 ++ cmd/main.go | 9 + global/command_client.go | 102 +++++++++++ global/command_server.go | 43 +++++ global/commands.go | 55 ++++++ global/constant.go | 15 ++ global/global.go | 365 +++++++++++++++++++++++++++++++++++++ global/parameters.go | 18 ++ global/service.go | 68 +++++++ global/status.go | 33 ++++ go.mod | 2 + go.sum | 6 + service/service.go | 76 ++++++++ utils/certificate_li.go | 91 +++++++++ utils/certificate_wi.go | 92 ++++++++++ web/web.go | 123 +++++++++++++ 20 files changed, 1145 insertions(+) create mode 100644 cmd/cmd_service.go create mode 100644 cmd/cmd_service_install.go create mode 100644 cmd/cmd_service_start.go create mode 100644 cmd/cmd_service_stop.go create mode 100644 global/command_client.go create mode 100644 global/command_server.go create mode 100644 global/commands.go create mode 100644 global/constant.go create mode 100644 global/global.go create mode 100644 global/parameters.go create mode 100644 global/service.go create mode 100644 global/status.go create mode 100644 service/service.go create mode 100644 utils/certificate_li.go create mode 100644 utils/certificate_wi.go create mode 100644 web/web.go diff --git a/.gitignore b/.gitignore index f756616..6daaa60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /bin/* !/bin/.gitkeep .build +.idea **/*.log .DS_Store diff --git a/cmd/cmd_service.go b/cmd/cmd_service.go new file mode 100644 index 0000000..5d77639 --- /dev/null +++ b/cmd/cmd_service.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/spf13/cobra" +) + +var commandService = &cobra.Command{ + Use: "service", + Short: "Sign box service", +} diff --git a/cmd/cmd_service_install.go b/cmd/cmd_service_install.go new file mode 100644 index 0000000..6cdd34f --- /dev/null +++ b/cmd/cmd_service_install.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hiddify/libcore/service" + "github.com/spf13/cobra" +) + +var commandServiceInstall = &cobra.Command{ + Use: "install", + Short: "install the service", + Run: service.InstallService, +} diff --git a/cmd/cmd_service_start.go b/cmd/cmd_service_start.go new file mode 100644 index 0000000..e757206 --- /dev/null +++ b/cmd/cmd_service_start.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hiddify/libcore/service" + "github.com/spf13/cobra" +) + +var commandServiceStart = &cobra.Command{ + Use: "start", + Short: "Start a sign box instance", + Run: service.StartService, +} diff --git a/cmd/cmd_service_stop.go b/cmd/cmd_service_stop.go new file mode 100644 index 0000000..b0f1a33 --- /dev/null +++ b/cmd/cmd_service_stop.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hiddify/libcore/service" + "github.com/spf13/cobra" +) + +var commandServiceStop = &cobra.Command{ + Use: "stop", + Short: "stop sign box", + Run: service.StopService, +} diff --git a/cmd/main.go b/cmd/main.go index 097116e..b067212 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -22,8 +22,17 @@ var mainCommand = &cobra.Command{ } func init() { + mainCommand.AddCommand(commandService) + + commandService.AddCommand(commandServiceStart) + commandService.AddCommand(commandServiceStop) + commandService.AddCommand(commandServiceInstall) + + commandServiceStart.Flags().Int("port", 8080, "Webserver port number") + mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory") mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output") + } func main() { diff --git a/global/command_client.go b/global/command_client.go new file mode 100644 index 0000000..8470dea --- /dev/null +++ b/global/command_client.go @@ -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"` +} diff --git a/global/command_server.go b/global/command_server.go new file mode 100644 index 0000000..cff03b6 --- /dev/null +++ b/global/command_server.go @@ -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() +} diff --git a/global/commands.go b/global/commands.go new file mode 100644 index 0000000..d8916d8 --- /dev/null +++ b/global/commands.go @@ -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 +} diff --git a/global/constant.go b/global/constant.go new file mode 100644 index 0000000..30e03b1 --- /dev/null +++ b/global/constant.go @@ -0,0 +1,15 @@ +package global + +const ( + Stopped = "Stopped" + Starting = "Starting" + Started = "Started" + Stopping = "Stopping" +) + +const ( + EmptyConfiguration = "EmptyConfiguration" + StartCommandServer = "StartCommandServer" + CreateService = "CreateService" + StartService = "StartService" +) diff --git a/global/global.go b/global/global.go new file mode 100644 index 0000000..fd575b6 --- /dev/null +++ b/global/global.go @@ -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 +} diff --git a/global/parameters.go b/global/parameters.go new file mode 100644 index 0000000..9a3bc92 --- /dev/null +++ b/global/parameters.go @@ -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 +} diff --git a/global/service.go b/global/service.go new file mode 100644 index 0000000..5c85754 --- /dev/null +++ b/global/service.go @@ -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 +} diff --git a/global/status.go b/global/status.go new file mode 100644 index 0000000..abf08d4 --- /dev/null +++ b/global/status.go @@ -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 +} diff --git a/go.mod b/go.mod index 24fe2ee..de4008e 100644 --- a/go.mod +++ b/go.mod @@ -35,9 +35,11 @@ require ( github.com/google/btree v1.1.2 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/hashicorp/yamux v0.1.1 // indirect + github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect github.com/josharian/native v1.1.0 // indirect + github.com/kardianos/service v1.2.2 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/libdns/alidns v1.0.3 // indirect diff --git a/go.sum b/go.sum index 75d8333..66765ac 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5X github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb h1:PGufWXXDq9yaev6xX1YQauaO1MV90e6Mpoq1I7Lz/VM= +github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E= github.com/hiddify/hiddify-sing-box v1.7.9-0.20240126132136-307f29d4ab05 h1:Lu1VgoEDqQRMsEENwDVs+SMK16hLTie6DI+P8txZLMM= github.com/hiddify/hiddify-sing-box v1.7.9-0.20240126132136-307f29d4ab05/go.mod h1:B74zKdMcH3ZEmCi2OUqJTvEXCNtNQjivUEQ20y/5XQM= github.com/hiddify/ray2sing v0.0.0-20240126124612-8e00e77ec754 h1:OS1xPGAR34zQ2btXGa2ZVpI0nXQnkB6meI1hXkQvgJM= @@ -62,6 +64,8 @@ github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1 github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60= +github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -233,7 +237,9 @@ golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190529164535-6a60838ec259/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/service/service.go b/service/service.go new file mode 100644 index 0000000..03cee6a --- /dev/null +++ b/service/service.go @@ -0,0 +1,76 @@ +package service + +import ( + "github.com/hiddify/libcore/global" + "github.com/hiddify/libcore/web" + "github.com/kardianos/service" + "github.com/spf13/cobra" +) + +type hiddifyNext struct{} + +var port int + +func (m *hiddifyNext) Start(s service.Service) error { + go m.run() + return nil +} +func (m *hiddifyNext) Stop(s service.Service) error { + err := global.StopService() + if err != nil { + return err + } + return nil +} +func (m *hiddifyNext) run() { + web.StartWebServer(port) +} +func StartService(cmd *cobra.Command, args []string) { + port, _ = cmd.Flags().GetInt("port") + svcConfig := &service.Config{ + Name: "hiddify_next_core", + DisplayName: "hiddify next core", + Description: "@hiddify_com set this", + } + prg := &hiddifyNext{} + svc, err := service.New(prg, svcConfig) + if err != nil { + panic("Error: " + err.Error()) + } + err = svc.Run() + if err != nil { + panic("Error: " + err.Error()) + } +} +func StopService(cmd *cobra.Command, args []string) { + svcConfig := &service.Config{ + Name: "hiddify_next_core", + DisplayName: "hiddify next core", + Description: "@hiddify_com set this", + } + prg := &hiddifyNext{} + svc, err := service.New(prg, svcConfig) + if err != nil { + panic("Error: " + err.Error()) + } + err = svc.Stop() + if err != nil { + panic("Error: " + err.Error()) + } +} +func InstallService(cmd *cobra.Command, args []string) { + svcConfig := &service.Config{ + Name: "hiddify_next_core", + DisplayName: "hiddify next core", + Description: "@hiddify_com set this", + } + prg := &hiddifyNext{} + svc, err := service.New(prg, svcConfig) + if err != nil { + panic("Error: " + err.Error()) + } + err = svc.Install() + if err != nil { + panic("Error: " + err.Error()) + } +} diff --git a/utils/certificate_li.go b/utils/certificate_li.go new file mode 100644 index 0000000..f52552d --- /dev/null +++ b/utils/certificate_li.go @@ -0,0 +1,91 @@ +//go:build !windows + +package utils + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "io/ioutil" + "math/big" + "os" + "time" +) + +func GenerateCertificate(certPath, keyPath string, isServer bool) { + priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + panic(err) + } + + notBefore := time.Now() + notAfter := notBefore.Add(365 * 24 * time.Hour) + + var keyUsage x509.KeyUsage + var extKeyUsage []x509.ExtKeyUsage + + if isServer { + keyUsage = x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature + extKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} + } else { + keyUsage = x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature + extKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} + } + + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{Organization: []string{"Secure data transfer"}}, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: keyUsage, + ExtKeyUsage: extKeyUsage, + BasicConstraintsValid: true, + } + + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + panic(err) + } + + certFile, err := os.Create(certPath) + if err != nil { + panic(err) + } + defer certFile.Close() + certFile.Chmod(600) + pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + + keyFile, err := os.Create(keyPath) + if err != nil { + panic(err) + } + defer keyFile.Close() + privBytes, err := x509.MarshalECPrivateKey(priv) + if err != nil { + panic(err) + } + keyFile.Chmod(600) + pem.Encode(keyFile, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes}) +} + +func LoadCertificate(certPath, keyPath string) tls.Certificate { + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + panic(err) + } + return cert +} + +func LoadClientCA(certPath string) *x509.CertPool { + pool := x509.NewCertPool() + certPEM, err := ioutil.ReadFile(certPath) + if err != nil { + panic(err) + } + pool.AppendCertsFromPEM(certPEM) + return pool +} diff --git a/utils/certificate_wi.go b/utils/certificate_wi.go new file mode 100644 index 0000000..6a51447 --- /dev/null +++ b/utils/certificate_wi.go @@ -0,0 +1,92 @@ +//go:build windows + +package utils + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "github.com/hectane/go-acl" + "io/ioutil" + "math/big" + "os" + "time" +) + +func GenerateCertificate(certPath, keyPath string, isServer bool) { + priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + panic(err) + } + + notBefore := time.Now() + notAfter := notBefore.Add(365 * 24 * time.Hour) + + var keyUsage x509.KeyUsage + var extKeyUsage []x509.ExtKeyUsage + + if isServer { + keyUsage = x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature + extKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} + } else { + keyUsage = x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature + extKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} + } + + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{Organization: []string{"Secure data transfer"}}, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: keyUsage, + ExtKeyUsage: extKeyUsage, + BasicConstraintsValid: true, + } + + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + panic(err) + } + + certFile, err := os.Create(certPath) + if err != nil { + panic(err) + } + defer certFile.Close() + acl.Chmod(certFile.Name(), 600) + pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + + keyFile, err := os.Create(keyPath) + if err != nil { + panic(err) + } + defer keyFile.Close() + privBytes, err := x509.MarshalECPrivateKey(priv) + if err != nil { + panic(err) + } + acl.Chmod(keyFile.Name(), 600) + pem.Encode(keyFile, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes}) +} + +func LoadCertificate(certPath, keyPath string) tls.Certificate { + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + panic(err) + } + return cert +} + +func LoadClientCA(certPath string) *x509.CertPool { + pool := x509.NewCertPool() + certPEM, err := ioutil.ReadFile(certPath) + if err != nil { + panic(err) + } + pool.AppendCertsFromPEM(certPEM) + return pool +} diff --git a/web/web.go b/web/web.go new file mode 100644 index 0000000..f71dfd6 --- /dev/null +++ b/web/web.go @@ -0,0 +1,123 @@ +package web + +import ( + "crypto/tls" + "fmt" + "github.com/hiddify/libcore/global" + "github.com/hiddify/libcore/utils" + "net/http" + "strconv" +) + +const ( + serverCertPath = "cert/server-cert.pem" + serverKeyPath = "cert/server-key.pem" + clientCertPath = "cert/client-cert.pem" + clientKeyPath = "cert/client-key.pem" +) + +func StartWebServer(Port int) { + http.HandleFunc("/start", startHandler) + http.HandleFunc("/stop", StopHandler) + server := &http.Server{ + Addr: "127.0.0.1:" + fmt.Sprintf("%d", Port), + TLSConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + Certificates: []tls.Certificate{utils.LoadCertificate(serverCertPath, serverKeyPath)}, + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: utils.LoadClientCA(clientCertPath), + }, + } + err := server.ListenAndServeTLS(serverCertPath, serverKeyPath) + if err != nil { + panic("Error: " + err.Error()) + } +} +func startHandler(w http.ResponseWriter, r *http.Request) { + queryParams := r.URL.Query() + Ipv6 := queryParams.Get("Ipv6") + ServerPort := queryParams.Get("ServerPort") + StrictRoute := queryParams.Get("StrictRoute") + EndpointIndependentNat := queryParams.Get("EndpointIndependentNat") + TheStack := queryParams.Get("Stack") + + ipv6, err := strconv.ParseBool(Ipv6) + if err != nil { + http.Error(w, fmt.Sprintf("Error: %v", err), http.StatusBadRequest) + return + } + serverPort, err := strconv.Atoi(ServerPort) + if err != nil { + http.Error(w, fmt.Sprintf("Error: %v", err), http.StatusBadRequest) + return + } + strictRoute, err := strconv.ParseBool(StrictRoute) + if err != nil { + http.Error(w, fmt.Sprintf("Error: %v", err), http.StatusBadRequest) + return + } + endpointIndependentNat, err := strconv.ParseBool(EndpointIndependentNat) + if err != nil { + http.Error(w, fmt.Sprintf("Error: %v", err), http.StatusBadRequest) + return + } + theStack := GetStack(TheStack) + if theStack == "UNKNOWN" { + http.Error(w, fmt.Sprintf("Error: %s", "Stack is not valid"), http.StatusBadRequest) + return + } + + parameters := global.Parameters{Ipv6: ipv6, ServerPort: serverPort, StrictRoute: strictRoute, EndpointIndependentNat: endpointIndependentNat, Stack: GetStack(TheStack)} + + err = global.WriteParameters(parameters.Ipv6, parameters.ServerPort, parameters.StrictRoute, parameters.EndpointIndependentNat, GetStringFromStack(parameters.Stack)) + if err != nil { + http.Error(w, fmt.Sprintf("Error: %v", err), http.StatusBadRequest) + return + } + err = global.SetupC("./", "./work", "./tmp", false) + if err != nil { + http.Error(w, fmt.Sprintf("Error: %v", err), http.StatusBadRequest) + return + } + err = global.StartServiceC(true, global.MakeConfig(parameters.Ipv6, parameters.ServerPort, parameters.StrictRoute, parameters.EndpointIndependentNat, GetStringFromStack(parameters.Stack))) + if err != nil { + http.Error(w, fmt.Sprintf("Error: %v", err), http.StatusBadRequest) + return + } + +} +func StopHandler(w http.ResponseWriter, r *http.Request) { + err := global.StopService() + if err != nil { + http.Error(w, fmt.Sprintf("Error: %v", err), http.StatusBadRequest) + return + } +} +func GetStack(stack string) global.Stack { + switch stack { + case "system": + return global.System + case "gVisor": + return global.GVisor + case "mixed": + return global.Mixed + case "LWIP": + return global.LWIP + default: + return "UNKNOWN" + } +} +func GetStringFromStack(stack global.Stack) string { + switch stack { + case global.System: + return "system" + case global.GVisor: + return "gVisor" + case global.Mixed: + return "mixed" + case global.LWIP: + return "LWIP" + default: + return "UNKNOWN" + } +}