diff --git a/.gitignore b/.gitignore index 6daaa60..d75a0a6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ **/*.log .DS_Store + +**/*.syso \ No newline at end of file diff --git a/admin_service/cmd/admin_service.syso b/admin_service/cmd/admin_service.syso deleted file mode 100644 index 18651d2..0000000 Binary files a/admin_service/cmd/admin_service.syso and /dev/null differ diff --git a/cmd.bat b/cmd.bat new file mode 100644 index 0000000..c1e7b38 --- /dev/null +++ b/cmd.bat @@ -0,0 +1,4 @@ +@echo off +set TAGS=with_gvisor,with_quic,with_wireguard,with_ech,with_utls,with_clash_api,with_grpc +@REM set TAGS=with_dhcp,with_low_memory,with_conntrack +go run --tags %TAGS% ./cmd %* \ No newline at end of file diff --git a/config/admin_service_cmd_runner.go b/config/admin_service_cmd_runner.go new file mode 100644 index 0000000..e22b6f2 --- /dev/null +++ b/config/admin_service_cmd_runner.go @@ -0,0 +1,38 @@ +//go:build !windows + +package config + +import ( + "fmt" + "os" + "os/exec" +) + +func ExecuteCmd(executablePath, args string) (string, error) { + err := execCmdImp([]string{"gksu", executablePath, args}) + if err == nil { + return "Ok", nil + } + err := execCmdImp([]string{"pkexec", executablePath, args}) + if err == nil { + return "Ok", nil + } + err := execCmdImp([]string{"/bin/sh", "-c", "sudo " + executablePath + " " + args}) + if err == nil { + return "Ok", nil + } + return "", err + +} + +func execCmdImp(cmd []string) error { + cmd := exec.Command(cmd[0], cmd[1:]) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + fmt.Printf("Running command: %v", cmd.String()) + if err := cmd.Run(); err != nil { + fmt.Printf("Error: %v\n", err) + return err + } + return nil +} diff --git a/config/admin_service_cmd_runner_windows.go b/config/admin_service_cmd_runner_windows.go new file mode 100644 index 0000000..e01b704 --- /dev/null +++ b/config/admin_service_cmd_runner_windows.go @@ -0,0 +1,28 @@ +//go:build windows + +package config + +import ( + "os" + "syscall" + + "golang.org/x/sys/windows" +) + +func ExecuteCmd(exe string, args string) (string, error) { + verb := "runas" + cwd, _ := os.Getwd() + + verbPtr, _ := syscall.UTF16PtrFromString(verb) + exePtr, _ := syscall.UTF16PtrFromString(exe) + cwdPtr, _ := syscall.UTF16PtrFromString(cwd) + argPtr, _ := syscall.UTF16PtrFromString(args) + + var showCmd int32 = 1 //SW_NORMAL + + err := windows.ShellExecute(0, verbPtr, exePtr, argPtr, cwdPtr, showCmd) + if err != nil { + return "", err + } + return "", nil +} diff --git a/config/admin_service_commander.go b/config/admin_service_commander.go new file mode 100644 index 0000000..f45a3a6 --- /dev/null +++ b/config/admin_service_commander.go @@ -0,0 +1,116 @@ +package config + +import ( + "fmt" + "io" + + "net/http" + "net/url" + "os" + "path/filepath" + "runtime" + + "github.com/sagernet/sing-box/option" + dns "github.com/sagernet/sing-dns" + E "github.com/sagernet/sing/common/exceptions" +) + +const ( + serviceURL = "http://localhost:18020" + startEndpoint = "/start" + stopEndpoint = "/stop" +) + +func isSupportedOS() bool { + return runtime.GOOS == "windows" || runtime.GOOS == "linux" +} +func ActivateTunnelService(opt ConfigOptions) (bool, error) { + if !isSupportedOS() { + return false, E.New("Unsupported OS: " + runtime.GOOS) + } + + go startTunnelRequest(opt, true) + return true, nil +} + +func DeactivateTunnelService() (bool, error) { + if !isSupportedOS() { + return true, nil + } + + go stopTunnelRequest() + return true, nil +} + +func startTunnelRequest(opt ConfigOptions, installService bool) (bool, error) { + params := map[string]interface{}{ + "Ipv6": opt.IPv6Mode == option.DomainStrategy(dns.DomainStrategyUseIPv4), + "ServerPort": opt.InboundOptions.MixedPort, + "StrictRoute": opt.InboundOptions.StrictRoute, + "EndpointIndependentNat": true, + "Stack": opt.InboundOptions.TUNStack, + } + + values := url.Values{} + for key, value := range params { + values.Add(key, fmt.Sprint(value)) + } + + url := fmt.Sprintf("%s%s?%s", serviceURL, startEndpoint, values.Encode()) + fmt.Printf("URL: %s\n", url) + response, err := http.Get(url) + if err != nil { + if installService { + return runTunnelService(opt) + } + return false, err + } + defer response.Body.Close() + body, err := io.ReadAll(response.Body) + fmt.Printf("Response Code: %d %s. Response Body: %s Error:%v\n", response.StatusCode, response.Status, body, err) + if err != nil || response.StatusCode != http.StatusOK { + return false, fmt.Errorf("Unexpected Status Code: %d %s. Response Body: %s error:%v", response.StatusCode, response.Status, body, err) + } + + return true, nil +} + +func stopTunnelRequest() (bool, error) { + response, err := http.Get(serviceURL + stopEndpoint) + if err != nil { + return false, fmt.Errorf("HTTP Request Error: %v", err) + } + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + fmt.Printf("Response Code: %d %s. Response Body: %s Error:%v\n", response.StatusCode, response.Status, body, err) + if err != nil || response.StatusCode != http.StatusOK { + return false, fmt.Errorf("Unexpected Status Code: %d %s. Response Body: %s error:%v", response.StatusCode, response.Status, body, err) + } + + return true, nil +} + +func runTunnelService(opt ConfigOptions) (bool, error) { + executablePath := getTunnelServicePath() + + out, err := ExecuteCmd(executablePath, "install") + fmt.Println("Shell command executed:", out, err) + return startTunnelRequest(opt, false) +} + +func getTunnelServicePath() string { + var fullPath string + binFolder := filepath.Dir(os.Args[0]) + + switch runtime.GOOS { + case "windows": + fullPath = "HiddifyService.exe" + case "darwin": + fallthrough + default: + fullPath = "HiddifyService" + } + + return filepath.Join(binFolder, fullPath) +} diff --git a/config/config.go b/config/config.go index f4804d4..95f16c7 100644 --- a/config/config.go +++ b/config/config.go @@ -135,40 +135,42 @@ func BuildConfig(opt ConfigOptions, input option.Options) (*option.Options, erro } if opt.EnableTun { - tunInbound := option.Inbound{ - Type: C.TypeTun, - Tag: InboundTUNTag, - TunOptions: option.TunInboundOptions{ - Stack: opt.TUNStack, - MTU: opt.MTU, - AutoRoute: true, - StrictRoute: opt.StrictRoute, - EndpointIndependentNat: true, - InboundOptions: option.InboundOptions{ - SniffEnabled: true, - SniffOverrideDestination: true, - DomainStrategy: inboundDomainStrategy, + if ok, _ := ActivateTunnelService(opt); !ok { + tunInbound := option.Inbound{ + Type: C.TypeTun, + Tag: InboundTUNTag, + TunOptions: option.TunInboundOptions{ + Stack: opt.TUNStack, + MTU: opt.MTU, + AutoRoute: true, + StrictRoute: opt.StrictRoute, + EndpointIndependentNat: true, + InboundOptions: option.InboundOptions{ + SniffEnabled: true, + SniffOverrideDestination: true, + DomainStrategy: inboundDomainStrategy, + }, }, - }, + } + switch opt.IPv6Mode { + case option.DomainStrategy(dns.DomainStrategyUseIPv4): + tunInbound.TunOptions.Inet4Address = []netip.Prefix{ + netip.MustParsePrefix("172.19.0.1/28"), + } + case option.DomainStrategy(dns.DomainStrategyUseIPv6): + tunInbound.TunOptions.Inet6Address = []netip.Prefix{ + netip.MustParsePrefix("fdfe:dcba:9876::1/126"), + } + default: + tunInbound.TunOptions.Inet4Address = []netip.Prefix{ + netip.MustParsePrefix("172.19.0.1/28"), + } + tunInbound.TunOptions.Inet6Address = []netip.Prefix{ + netip.MustParsePrefix("fdfe:dcba:9876::1/126"), + } + } + options.Inbounds = append(options.Inbounds, tunInbound) } - switch opt.IPv6Mode { - case option.DomainStrategy(dns.DomainStrategyUseIPv4): - tunInbound.TunOptions.Inet4Address = []netip.Prefix{ - netip.MustParsePrefix("172.19.0.1/28"), - } - case option.DomainStrategy(dns.DomainStrategyUseIPv6): - tunInbound.TunOptions.Inet6Address = []netip.Prefix{ - netip.MustParsePrefix("fdfe:dcba:9876::1/126"), - } - default: - tunInbound.TunOptions.Inet4Address = []netip.Prefix{ - netip.MustParsePrefix("172.19.0.1/28"), - } - tunInbound.TunOptions.Inet6Address = []netip.Prefix{ - netip.MustParsePrefix("fdfe:dcba:9876::1/126"), - } - } - options.Inbounds = append(options.Inbounds, tunInbound) } options.Inbounds = append( diff --git a/config/debug.go b/config/debug.go index 71713e4..fab5a9e 100644 --- a/config/debug.go +++ b/config/debug.go @@ -16,7 +16,7 @@ func SaveCurrentConfig(path string, options option.Options) error { if err != nil { return err } - p, err := filepath.Abs(filepath.Join(path, "current-config.json")) + p, err := filepath.Abs(path) fmt.Printf("Saving config to %v %+v\n", p, err) if err != nil { return err diff --git a/custom/custom.go b/custom/custom.go index f1dc683..d7b732f 100644 --- a/custom/custom.go +++ b/custom/custom.go @@ -156,7 +156,7 @@ func startService(delayStart bool) error { return fmt.Errorf("error building config: %w", err) } - config.SaveCurrentConfig(sWorkingPath, *patchedOptions) + config.SaveCurrentConfig(filepath.Join(sWorkingPath, "current-config.json"), *patchedOptions) err = startCommandServer(*logFactory) if err != nil { @@ -188,6 +188,7 @@ func stop() (CErr *C.char) { defer config.DeferPanicToError("stop", func(err error) { CErr = C.CString(err.Error()) }) + config.DeactivateTunnelService() if status != Started { return C.CString("") diff --git a/global/global.go b/global/global.go index 907d35d..49e2f48 100644 --- a/global/global.go +++ b/global/global.go @@ -122,7 +122,7 @@ func startService(delayStart bool) error { return fmt.Errorf("error building config: %w", err) } - config.SaveCurrentConfig(sWorkingPath, *patchedOptions) + config.SaveCurrentConfig(filepath.Join(sWorkingPath, "tunnel-current-config.json"), *patchedOptions) err = startCommandServer(*logFactory) if err != nil { @@ -248,11 +248,11 @@ func StartServiceC(delayStart bool, content string) error { // options = *patchedOptions - err = config.SaveCurrentConfig(sWorkingPath, options) - if err != nil { - fmt.Printf("Error in saving config: %v\n", err) - return err - } + // config.SaveCurrentConfig(filepath.Join(sWorkingPath, "custom-current-config.json"), options) + // if err != nil { + // fmt.Printf("Error in saving config: %v\n", err) + // return err + // } // err = startCommandServer(*logFactory) // if err != nil { diff --git a/go.mod b/go.mod index 28d4c97..b369729 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( require ( berty.tech/go-libtor v1.0.385 // indirect github.com/ajg/form v1.5.1 // indirect + github.com/akavel/rsrc v0.10.2 // indirect github.com/andybalholm/brotli v1.0.6 // indirect github.com/caddyserver/certmagic v0.20.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect diff --git a/go.sum b/go.sum index facdbeb..21110f9 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw= +github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=