diff --git a/service.go b/service.go deleted file mode 100644 index e3a9db9c..00000000 --- a/service.go +++ /dev/null @@ -1,269 +0,0 @@ -package main - -import ( - "context" - "github.com/davecgh/go-spew/spew" - "github.com/parvit/qpep/version" - "github.com/parvit/qpep/workers/client" - "github.com/parvit/qpep/workers/server" - "os" - "os/signal" - "path/filepath" - "runtime" - "strings" - "syscall" - "time" - - service "github.com/parvit/kardianos-service" - - "github.com/parvit/qpep/api" - "github.com/parvit/qpep/flags" - . "github.com/parvit/qpep/logger" - "github.com/parvit/qpep/shared" - "github.com/parvit/qpep/windivert" -) - -const ( - startingSvc = iota - startedSvc - stoppingSvc - stoppedSvc - - WIN32_RUNNING_CODE = 0 - WIN32_STOPPED_CODE = 6 - WIN32_UNKNOWN_CODE = 255 - - serverService = "qpep-server" - clientService = "qpep-client" - - defaultLinuxWorkDir = "/var/run/qpep" -) - -type QPepService struct { - service.Service - - context context.Context - cancelFunc context.CancelFunc - status int - exitValue int -} - -func serviceMain() { - execPath, err := os.Executable() - if err != nil { - Info("Could not find executable: %s", err) - } - - workingDir := defaultLinuxWorkDir - if runtime.GOOS == "windows" { - workingDir = filepath.Dir(execPath) - setCurrentWorkingDir(workingDir) - } - Info("Set workingdir for service child: %s", workingDir) - - ctx, cancel := context.WithCancel(context.Background()) - svc := QPepService{ - context: ctx, - cancelFunc: cancel, - } - - Info("=== QPep version %s ===", version.Version()) - Info(spew.Sdump(flags.Globals)) - - serviceName := serverService - if flags.Globals.Client { - serviceName = clientService - } - svcConfig := &service.Config{ - Name: serviceName, - DisplayName: strings.ToTitle(serviceName), - Description: "QPep - high-latency network accelerator", - - Executable: "qpep.exe", - Option: make(service.KeyValue), - - WorkingDirectory: workingDir, - - EnvVars: make(map[string]string), - Arguments: []string{}, - } - - svcConfig.Option["StartType"] = "manual" - svcConfig.Option["OnFailure"] = "noaction" - - path, _ := os.LookupEnv("PATH") - svcConfig.EnvVars["PATH"] = workingDir + ";" + path - - if flags.Globals.Client { - svcConfig.Arguments = append(svcConfig.Arguments, `--client`) - } - - serviceInst, err := service.New(&svc, svcConfig) - if err != nil { - Info(err.Error()) - panic(err) - } - - svcCommand := flags.Globals.Service - - if len(svcCommand) != 0 { - // Service control / status run - if svcCommand == "status" { - status, err := serviceInst.Status() - if err != nil { - status = service.StatusUnknown - } - - switch status { - case service.StatusRunning: - os.Exit(WIN32_RUNNING_CODE) - - case service.StatusStopped: - os.Exit(WIN32_STOPPED_CODE) - - default: - fallthrough - case service.StatusUnknown: - os.Exit(WIN32_UNKNOWN_CODE) - } - } - - err = service.Control(serviceInst, svcCommand) - if err != nil { - Info("Error %v\nPossible actions: %q\n", err.Error(), service.ControlAction) - os.Exit(WIN32_UNKNOWN_CODE) - return - } - - if svcCommand == "install" { - _ = shared.ReadConfiguration(false) - setServiceUserPermissions(serviceName) - setInstallDirectoryPermissions(workingDir) - Info("Service installed correctly") - } - - Info("Service action %s executed\n", svcCommand) - return - } - - // As-service run - logName := "qpep-server.log" - if flags.Globals.Client { - logName = "qpep-client.log" - } - SetupLogger(logName, "info") - - err = serviceInst.Run() - if err != nil { - Info("Error while starting QPep service") - } - - Info("Exit errorcode: %d\n", svc.exitValue) - os.Exit(svc.exitValue) -} - -func (p *QPepService) Start(s service.Service) error { - Info("Start") - - p.status = startingSvc - - go p.Main() - - return nil // Service is now started -} - -func (p *QPepService) Stop(s service.Service) error { - defer func() { - if err := recover(); err != nil { - Info("PANIC: %v\n", err) - } - shared.SetSystemProxy(false) // be sure to clear proxy settings on exit - }() - - Info("Stop") - - if p.status != startedSvc { - p.status = stoppedSvc - return nil - } - p.status = stoppingSvc - - sendProcessInterrupt() // signal the child process to terminate - - execPath, _ := os.Executable() - name := filepath.Base(execPath) - - waitChildProcessTermination(name) // wait for its actual termination - p.status = stoppedSvc - - return nil -} - -func (p *QPepService) Main() { - defer func() { - if err := recover(); err != nil { - Info("PANIC: %v\n", err) - } - shared.SetSystemProxy(false) // be sure to clear proxy settings on exit - }() - - Info("Main") - - if err := shared.ReadConfiguration(false); err != nil { - panic(err) - } - - go api.RunServer(p.context, p.cancelFunc, true) // api server for local webgui - - if flags.Globals.Client { - if flags.Globals.Verbose { - SetupLogger("qpep-client.log", "debug") - } - runAsClient(p.context, p.cancelFunc) - } else { - if flags.Globals.Verbose { - SetupLogger("qpep-server.log", "debug") - } - runAsServer(p.context, p.cancelFunc) - } - - interruptListener := make(chan os.Signal, 1) - signal.Notify(interruptListener, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - -TERMINATIONLOOP: - for { - select { - case <-interruptListener: - break TERMINATIONLOOP - case <-p.context.Done(): - break TERMINATIONLOOP - case <-time.After(100 * time.Millisecond): - continue - } - } - - p.cancelFunc() - <-p.context.Done() - - Info("Shutdown...") - Info("%d", windivert.CloseWinDivertEngine()) - - <-time.After(1 * time.Second) - - Info("Exiting...") - os.Exit(1) -} - -func runAsClient(execContext context.Context, cancel context.CancelFunc) { - Info("Running Client") - - windivert.EnableDiverterLogging(shared.QPepConfig.Verbose) - - go client.RunClient(execContext, cancel) -} - -func runAsServer(execContext context.Context, cancel context.CancelFunc) { - Info("Running Server") - go server.RunServer(execContext, cancel) - go api.RunServer(execContext, cancel, false) -} diff --git a/service/service.go b/service/service.go index f0cfc784..f80c3732 100644 --- a/service/service.go +++ b/service/service.go @@ -176,13 +176,13 @@ func ServiceMain() int { logName = "qpep-client.log" } if flags.Globals.Verbose { - logLevel = "verbose" + logLevel = "debug" } logger.SetupLogger(logName, logLevel) err = serviceInst.Run() if err != nil { - logger.Error("Error while starting QPep service") + logger.Error("Error while starting QPep service: %v", err) } logger.Info("Exit errorcode: %d\n", svc.exitValue) diff --git a/service_linux.go b/service_linux.go deleted file mode 100644 index 1cf8399d..00000000 --- a/service_linux.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - . "github.com/parvit/qpep/logger" - "os" -) - -func setCurrentWorkingDir(path string) { - return // no-op -} - -func sendProcessInterrupt() { - pid := os.Getpid() - p, err := os.FindProcess(pid) - if err != nil { - Info("ERROR: %v\n", err) - os.Exit(1) - } - if err = p.Signal(os.Interrupt); err != nil { - Info("ERROR: %v\n", err) - os.Exit(1) - } -} - -func waitChildProcessTermination(name string) { - return // TODO -} - -func setServiceUserPermissions(serviceName string) { - return // TODO -} - -func setInstallDirectoryPermissions(installDir string) { - return // TODO -} diff --git a/service_windows.go b/service_windows.go deleted file mode 100644 index f0b12b4b..00000000 --- a/service_windows.go +++ /dev/null @@ -1,197 +0,0 @@ -package main - -import ( - "github.com/parvit/qpep/shared" - "os" - "os/exec" - "path/filepath" - "strings" - "syscall" - "time" - "unsafe" - - . "github.com/parvit/qpep/logger" - "golang.org/x/sys/windows" -) - -const ( - USER_ACCESS_LIST = `D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;RPWPCR;;;S-1-1-0)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)` -) - -func setCurrentWorkingDir(path string) { - Info("Set working path %s", path) - imagePath, err := windows.UTF16PtrFromString(path) - if err != nil { - Error("ERROR: %v\n", err) - os.Exit(1) - } - - if err = windows.SetCurrentDirectory(imagePath); err != nil { - Error("ERROR: %v\n", err) - os.Exit(1) - } -} - -func setServiceUserPermissions(serviceName string) { - Info("Set user permissions %s", serviceName) - - // allow all users to start/stop the service using the "Everyone" UserID to allow start / stop privileges - // to users without admin rights - cmdParams := []string{ - `sdset`, - serviceName, - USER_ACCESS_LIST, - } - - Info(`acl: "%s"`, USER_ACCESS_LIST) - - permssionsCmd := exec.Command(`sc.exe`, cmdParams...) - permssionsCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - - _, err := permssionsCmd.CombinedOutput() - if err != nil { - Error("%v", err) - return - } -} - -func setInstallDirectoryPermissions(workDir string) { - installDir := filepath.Dir(workDir) // Permissions should be set for the entire folder - - Info("Set install permissions %s", installDir) - - // reset access permission on the installation directory to allow writing logs - cmdParams := []string{ - installDir, - `/t`, `/q`, `/c`, `/reset`, - } - - permssionsCmd := exec.Command(`icacls`, cmdParams...) - permssionsCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - - _, err := permssionsCmd.CombinedOutput() - if err != nil { - Error("%v", err) - panic(err) - } - - // allow Everyone group to access installation directory to allow writing logs - cmdParams = []string{ - installDir, - `/t`, `/grant`, `Everyone:F`, - } - - permssionsCmd = exec.Command(`icacls`, cmdParams...) - permssionsCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - - _, err = permssionsCmd.CombinedOutput() - if err != nil { - Error("%v", err) - panic(err) - } -} - -func sendProcessInterrupt() { - Info("sendProcessInterrupt") - - shared.SetSystemProxy(false) - - dll := syscall.MustLoadDLL("kernel32.dll") - defer func() { - if dll != nil { - dll.Release() - } - }() - - p, err := dll.FindProc("GenerateConsoleCtrlEvent") - if err != nil { - Error("ERROR: %v\n", err) - os.Exit(1) - } - - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683155(v=vs.85).aspx - pid := os.Getpid() - r1, _, err := p.Call(syscall.CTRL_BREAK_EVENT, uintptr(pid)) - if r1 == 0 { - Error("ERROR: %v\n", err) - os.Exit(1) - } -} - -func waitChildProcessTermination(name string) { - Info("Wait child terminate %s", name) - - count := 0 - for timeout := 30; timeout > 0; timeout-- { - count = 0 - - list, err := processes() - if err != nil { - for _, p := range list { - if strings.EqualFold(p.Exe, name) { - count++ - } - } - if count < 2 { - return // only this process remains - } - } - - <-time.After(1 * time.Second) - } -} - -const TH32CS_SNAPPROCESS = 0x00000002 - -type windowsProcess struct { - ProcessID int - ParentProcessID int - Exe string -} - -func processes() ([]windowsProcess, error) { - handle, err := windows.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) - if err != nil { - return nil, err - } - defer windows.CloseHandle(handle) - - var entry windows.ProcessEntry32 - entry.Size = uint32(unsafe.Sizeof(entry)) - // get the first process - err = windows.Process32First(handle, &entry) - if err != nil { - return nil, err - } - - results := make([]windowsProcess, 0, 50) - for { - results = append(results, newWindowsProcess(&entry)) - - err = windows.Process32Next(handle, &entry) - if err != nil { - // windows sends ERROR_NO_MORE_FILES on last process - if err == syscall.ERROR_NO_MORE_FILES { - return results, nil - } - return nil, err - } - } -} - -func newWindowsProcess(e *windows.ProcessEntry32) windowsProcess { - // Find when the string ends for decoding - end := 0 - for { - if e.ExeFile[end] == 0 { - break - } - end++ - } - - return windowsProcess{ - ProcessID: int(e.ProcessID), - ParentProcessID: int(e.ParentProcessID), - Exe: syscall.UTF16ToString(e.ExeFile[:end]), - } -}