189 lines
4.6 KiB
Go
189 lines
4.6 KiB
Go
package config
|
|
|
|
import (
|
|
context "context"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"time"
|
|
|
|
pb "github.com/hiddify/hiddify-core/hiddifyrpc"
|
|
"github.com/sagernet/sing-box/option"
|
|
dns "github.com/sagernet/sing-dns"
|
|
grpc "google.golang.org/grpc"
|
|
)
|
|
|
|
const (
|
|
serviceURL = "http://localhost:18020"
|
|
startEndpoint = "/start"
|
|
stopEndpoint = "/stop"
|
|
)
|
|
|
|
var tunnelServiceRunning = false
|
|
|
|
func isSupportedOS() bool {
|
|
return runtime.GOOS == "windows" || runtime.GOOS == "linux"
|
|
}
|
|
|
|
func ActivateTunnelService(opt HiddifyOptions) (bool, error) {
|
|
tunnelServiceRunning = true
|
|
// if !isSupportedOS() {
|
|
// return false, E.New("Unsupported OS: " + runtime.GOOS)
|
|
// }
|
|
|
|
go startTunnelRequestWithFailover(opt, true)
|
|
return true, nil
|
|
}
|
|
|
|
func DeactivateTunnelServiceForce() (bool, error) {
|
|
return stopTunnelRequest()
|
|
}
|
|
|
|
func DeactivateTunnelService() (bool, error) {
|
|
// if !isSupportedOS() {
|
|
// return true, nil
|
|
// }
|
|
|
|
if tunnelServiceRunning {
|
|
res, err := stopTunnelRequest()
|
|
if err != nil {
|
|
tunnelServiceRunning = false
|
|
}
|
|
return res, err
|
|
} else {
|
|
go stopTunnelRequest()
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func startTunnelRequestWithFailover(opt HiddifyOptions, installService bool) {
|
|
res, err := startTunnelRequest(opt, installService)
|
|
fmt.Printf("Start Tunnel Result: %v\n", res)
|
|
if err != nil {
|
|
fmt.Printf("Start Tunnel Failed! Stopping core... err=%v\n", err)
|
|
// StopAndAlert(pb.MessageType.MessageType_UNEXPECTED_ERROR, "Start Tunnel Failed! Stopping...")
|
|
}
|
|
}
|
|
|
|
func isPortInUse(port string) bool {
|
|
listener, err := net.Listen("tcp", "127.0.0.1:"+port)
|
|
if err != nil {
|
|
return true // Port is in use
|
|
}
|
|
defer listener.Close()
|
|
return false // Port is available
|
|
}
|
|
|
|
func startTunnelRequest(opt HiddifyOptions, installService bool) (bool, error) {
|
|
if !isPortInUse("18020") {
|
|
if installService {
|
|
return runTunnelService(opt)
|
|
}
|
|
return false, fmt.Errorf("service is not running")
|
|
}
|
|
conn, err := grpc.Dial("127.0.0.1:18020", grpc.WithInsecure())
|
|
if err != nil {
|
|
log.Printf("did not connect: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
c := pb.NewTunnelServiceClient(conn)
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
|
defer cancel()
|
|
_, _ = c.Stop(ctx, &pb.Empty{})
|
|
res, err := c.Start(ctx, &pb.TunnelStartRequest{
|
|
Ipv6: opt.IPv6Mode == option.DomainStrategy(dns.DomainStrategyUseIPv4),
|
|
ServerPort: int32(opt.InboundOptions.MixedPort),
|
|
StrictRoute: opt.InboundOptions.StrictRoute,
|
|
EndpointIndependentNat: true,
|
|
Stack: opt.InboundOptions.TUNStack,
|
|
})
|
|
if err != nil {
|
|
log.Printf("could not greet: %+v %+v", res, err)
|
|
|
|
if installService {
|
|
ExitTunnelService()
|
|
return runTunnelService(opt)
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func stopTunnelRequest() (bool, error) {
|
|
conn, err := grpc.Dial("127.0.0.1:18020", grpc.WithInsecure())
|
|
if err != nil {
|
|
log.Printf("did not connect: %v", err)
|
|
return false, err
|
|
}
|
|
defer conn.Close()
|
|
c := pb.NewTunnelServiceClient(conn)
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
|
defer cancel()
|
|
|
|
res, err := c.Stop(ctx, &pb.Empty{})
|
|
if err != nil {
|
|
log.Printf("did not Stopped: %v %v", res, err)
|
|
_, _ = c.Stop(ctx, &pb.Empty{})
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func ExitTunnelService() (bool, error) {
|
|
conn, err := grpc.Dial("127.0.0.1:18020", grpc.WithInsecure())
|
|
if err != nil {
|
|
log.Printf("did not connect: %v", err)
|
|
return false, err
|
|
}
|
|
defer conn.Close()
|
|
c := pb.NewTunnelServiceClient(conn)
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*1)
|
|
defer cancel()
|
|
|
|
res, err := c.Exit(ctx, &pb.Empty{})
|
|
if res != nil {
|
|
log.Printf("did not exit: %v %v", res, err)
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func runTunnelService(opt HiddifyOptions) (bool, error) {
|
|
executablePath := getTunnelServicePath()
|
|
fmt.Printf("Executable path is %s", executablePath)
|
|
out, err := ExecuteCmd(executablePath, false, "tunnel", "install")
|
|
fmt.Println("Shell command executed:", out, err)
|
|
if err != nil {
|
|
out, err = ExecuteCmd(executablePath, true, "tunnel", "run")
|
|
fmt.Println("Shell command executed without flag:", out, err)
|
|
}
|
|
if err == nil {
|
|
<-time.After(1 * time.Second) // wait until service loaded completely
|
|
}
|
|
return startTunnelRequest(opt, false)
|
|
}
|
|
|
|
func getTunnelServicePath() string {
|
|
var fullPath string
|
|
exePath, _ := os.Executable()
|
|
binFolder := filepath.Dir(exePath)
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
fullPath = "HiddifyCli.exe"
|
|
case "darwin":
|
|
fallthrough
|
|
default:
|
|
fullPath = "HiddifyCli"
|
|
}
|
|
|
|
abspath, _ := filepath.Abs(filepath.Join(binFolder, fullPath))
|
|
return abspath
|
|
}
|