From da563a6ec66113fee96dad3ebf440607320637c6 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Sun, 10 Mar 2019 02:37:34 +0100 Subject: service: keep track of proper errors --- service/errors.go | 103 +++++++++++++++++++++++++++++++++++++++------ service/install.go | 4 +- service/ipc_client.go | 21 +++++++-- service/ipc_server.go | 12 ++++-- service/service_manager.go | 56 +++++++++++++++--------- service/service_tunnel.go | 73 +++++++++++++++++++------------- service/tunneltracker.go | 12 +++++- ui/ui.go | 11 ++++- 8 files changed, 214 insertions(+), 78 deletions(-) diff --git a/service/errors.go b/service/errors.go index 04cedb45..ea6147a9 100644 --- a/service/errors.go +++ b/service/errors.go @@ -5,19 +5,94 @@ package service +import ( + "fmt" + "golang.org/x/sys/windows" + "syscall" +) + +type Error uint32 + +const ( + ErrorSuccess Error = iota + ErrorEventlogOpen + ErrorLoadConfiguration + ErrorCreateWintun + ErrorDetermineWintunName + ErrorUAPIListen + ErrorUAPISerialization + ErrorDeviceSetConfig + ErrorBindSocketsToDefaultRoutes + ErrorSetNetConfig + ErrorDetermineExecutablePath + ErrorFindAdministratorsSID + ErrorOpenNULFile + ErrorTrackTunnels + ErrorEnumerateSessions + ErrorWin32 +) + +func (e Error) Error() string { + switch e { + case ErrorSuccess: + return "No error." + case ErrorEventlogOpen: + return "Unable to open Windows event log." + case ErrorDetermineExecutablePath: + return "Unable to determine path of running executable." + case ErrorLoadConfiguration: + return "Unable to load configuration from path." + case ErrorCreateWintun: + return "Unable to create Wintun device." + case ErrorDetermineWintunName: + return "Unable to determine Wintun name." + case ErrorUAPIListen: + return "Unable to listen on named pipe." + case ErrorUAPISerialization: + return "Unable to serialize configuration into uapi form." + case ErrorDeviceSetConfig: + return "Unable to set device configuration." + case ErrorBindSocketsToDefaultRoutes: + return "Unable to bind sockets to default route." + case ErrorSetNetConfig: + return "Unable to set interface addresses, routes, dns, and/or adapter settings." + case ErrorFindAdministratorsSID: + return "Unable to find Administrators SID." + case ErrorOpenNULFile: + return "Unable to open NUL file." + case ErrorTrackTunnels: + return "Unable to track existing tunnels." + case ErrorEnumerateSessions: + return "Unable to enumerate current sessions." + case ErrorWin32: + return "An internal Windows error has occurred." + default: + return "An unknown error has occurred." + } +} + +func determineErrorCode(err error, serviceError Error) (bool, uint32) { + if syserr, ok := err.(syscall.Errno); ok { + return false, uint32(syserr) + } else if serviceError != ErrorSuccess { + return true, uint32(serviceError) + } else { + return false, windows.NO_ERROR + } +} + +func combineErrors(err error, serviceError Error) error { + if serviceError != ErrorSuccess { + if err != nil { + return fmt.Errorf("%v: %v", serviceError, err) + } else { + return serviceError + } + } + return err +} + const ( - ERROR_LOG_CONTAINER_OPEN_FAILED uint32 = 0x000019F1 - ERROR_INVALID_PARAMETER uint32 = 0x00000057 - ERROR_OPEN_FAILED uint32 = 0x0000006E - ERROR_ADAP_HDW_ERR uint32 = 0x00000039 - ERROR_PIPE_LISTENING uint32 = 0x00000218 - ERROR_BAD_LOGON_SESSION_STATE uint32 = 0x00000555 - ERROR_BAD_PATHNAME uint32 = 0x000000A1 - ERROR_FILE_NOT_FOUND uint32 = 0x00000002 - ERROR_SERVER_SID_MISMATCH uint32 = 0x00000274 - ERROR_NETWORK_BUSY uint32 = 0x00000036 - ERROR_NO_TRACKING_SERVICE uint32 = 0x00000494 - ERROR_OBJECT_ALREADY_EXISTS uint32 = 0x00001392 - ERROR_SERVICE_DOES_NOT_EXIST uint32 = 0x00000424 - ERROR_SERVICE_MARKED_FOR_DELETE uint32 = 0x00000430 + serviceDOES_NOT_EXIST uint32 = 0x00000424 + serviceMARKED_FOR_DELETE uint32 = 0x00000430 ) diff --git a/service/install.go b/service/install.go index 4203d066..87ac002d 100644 --- a/service/install.go +++ b/service/install.go @@ -144,7 +144,7 @@ func InstallTunnel(configPath string) error { } for { service, err = m.OpenService(serviceName) - if err != nil && err != syscall.Errno(ERROR_SERVICE_MARKED_FOR_DELETE) { + if err != nil && err != syscall.Errno(serviceMARKED_FOR_DELETE) { break } service.Close() @@ -183,7 +183,7 @@ func UninstallTunnel(name string) error { service.Control(svc.Stop) err = service.Delete() err2 := service.Close() - if err != nil && err != syscall.Errno(ERROR_SERVICE_MARKED_FOR_DELETE) { + if err != nil && err != syscall.Errno(serviceMARKED_FOR_DELETE) { return err } return err2 diff --git a/service/ipc_client.go b/service/ipc_client.go index adaca0b7..fca4a511 100644 --- a/service/ipc_client.go +++ b/service/ipc_client.go @@ -7,6 +7,7 @@ package service import ( "encoding/gob" + "errors" "golang.zx2c4.com/wireguard/windows/conf" "net/rpc" "os" @@ -37,7 +38,7 @@ const ( var rpcClient *rpc.Client type TunnelChangeCallback struct { - cb func(tunnel *Tunnel, state TunnelState) + cb func(tunnel *Tunnel, state TunnelState, err error) } var tunnelChangeCallbacks = make(map[*TunnelChangeCallback]bool) @@ -67,12 +68,24 @@ func InitializeIPCClient(reader *os.File, writer *os.File, events *os.File) { } var state TunnelState err = decoder.Decode(&state) - if err != nil || state == TunnelUnknown { + if err != nil { + continue + } + var errStr string + err = decoder.Decode(&errStr) + if err != nil { + continue + } + var retErr error + if len(errStr) > 0 { + retErr = errors.New(errStr) + } + if state == TunnelUnknown { continue } t := &Tunnel{tunnel} for cb := range tunnelChangeCallbacks { - cb.cb(t, state) + cb.cb(t, state, retErr) } case TunnelsChangeNotificationType: for cb := range tunnelsChangeCallbacks { @@ -129,7 +142,7 @@ func IPCClientQuit(stopTunnelsOnQuit bool) (bool, error) { return alreadyQuit, rpcClient.Call("ManagerService.Quit", stopTunnelsOnQuit, &alreadyQuit) } -func IPCClientRegisterTunnelChange(cb func(tunnel *Tunnel, state TunnelState)) *TunnelChangeCallback { +func IPCClientRegisterTunnelChange(cb func(tunnel *Tunnel, state TunnelState, err error)) *TunnelChangeCallback { s := &TunnelChangeCallback{cb} tunnelChangeCallbacks[s] = true return s diff --git a/service/ipc_server.go b/service/ipc_server.go index 5f16eab9..4130284c 100644 --- a/service/ipc_server.go +++ b/service/ipc_server.go @@ -84,7 +84,7 @@ func (s *ManagerService) Start(tunnelName string, unused *uintptr) error { func (s *ManagerService) Stop(tunnelName string, unused *uintptr) error { err := UninstallTunnel(tunnelName) - if err == syscall.Errno(ERROR_SERVICE_DOES_NOT_EXIST) { + if err == syscall.Errno(serviceDOES_NOT_EXIST) { _, notExistsError := conf.LoadFromName(tunnelName) if notExistsError == nil { return nil @@ -104,7 +104,7 @@ func (s *ManagerService) WaitForStop(tunnelName string, unused *uintptr) error { } for { service, err := m.OpenService(serviceName) - if err == nil || err == syscall.Errno(ERROR_SERVICE_MARKED_FOR_DELETE) { + if err == nil || err == syscall.Errno(serviceMARKED_FOR_DELETE) { service.Close() time.Sleep(time.Second / 3) } else { @@ -256,8 +256,12 @@ func notifyAll(notificationType NotificationType, ifaces ...interface{}) { managerServicesLock.RUnlock() } -func IPCServerNotifyTunnelChange(name string, state TunnelState) { - notifyAll(TunnelChangeNotificationType, name, state) +func IPCServerNotifyTunnelChange(name string, state TunnelState, err error) { + if err == nil { + notifyAll(TunnelChangeNotificationType, name, state, "") + } else { + notifyAll(TunnelChangeNotificationType, name, state, err.Error()) + } } func IPCServerNotifyTunnelsChange() { diff --git a/service/service_manager.go b/service/service_manager.go index 9b227592..91bb593d 100644 --- a/service/service_manager.go +++ b/service/service_manager.go @@ -6,13 +6,14 @@ package service import ( + "fmt" "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc" - "golang.org/x/sys/windows/svc/debug" "golang.org/x/sys/windows/svc/eventlog" "golang.zx2c4.com/wireguard/windows/conf" "log" "os" + "runtime/debug" "strconv" "sync" "syscall" @@ -83,7 +84,7 @@ func localWellKnownSid(sidType wellKnownSidType) (*windows.SID, error) { type managerService struct{} type elogger struct { - debug.Log + *eventlog.Log } func (elog elogger) Write(p []byte) (n int, err error) { @@ -96,45 +97,59 @@ func (elog elogger) Write(p []byte) (n int, err error) { func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) { changes <- svc.Status{State: svc.StartPending} + var elog *eventlog.Log + var err error + serviceError := ErrorSuccess + + defer func() { + svcSpecificEC, exitCode = determineErrorCode(err, serviceError) + logErr := combineErrors(err, serviceError) + if logErr != nil { + if elog != nil { + elog.Error(1, logErr.Error()) + } else { + fmt.Println(logErr.Error()) + } + } + changes <- svc.Status{State: svc.StopPending} + }() + //TODO: remember to clean this up in the msi uninstaller eventlog.InstallAsEventCreate("WireGuard", eventlog.Info|eventlog.Warning|eventlog.Error) - elog, err := eventlog.Open("WireGuard") + elog, err = eventlog.Open("WireGuard") if err != nil { - changes <- svc.Status{State: svc.StopPending} - exitCode = ERROR_LOG_CONTAINER_OPEN_FAILED + serviceError = ErrorEventlogOpen return } log.SetOutput(elogger{elog}) + defer func() { + if x := recover(); x != nil { + elog.Error(1, fmt.Sprintf("%v:\n%s", x, string(debug.Stack()))) + panic(x) + } + }() path, err := os.Executable() if err != nil { - elog.Error(1, "Unable to determine own executable path: "+err.Error()) - changes <- svc.Status{State: svc.StopPending} - exitCode = ERROR_BAD_PATHNAME + serviceError = ErrorDetermineExecutablePath return } adminSid, err := localWellKnownSid(winBuiltinAdministratorsSid) if err != nil { - elog.Error(1, "Unable to find Administrators SID: "+err.Error()) - changes <- svc.Status{State: svc.StopPending} - exitCode = ERROR_SERVER_SID_MISMATCH + serviceError = ErrorFindAdministratorsSID return } devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0) if err != nil { - elog.Error(1, "Unable to open NUL file: "+err.Error()) - changes <- svc.Status{State: svc.StopPending} - exitCode = ERROR_FILE_NOT_FOUND + serviceError = ErrorOpenNULFile return } err = trackExistingTunnels() if err != nil { - elog.Error(1, "Unable to track existing tunnels: "+err.Error()) - changes <- svc.Status{State: svc.StopPending} - exitCode = ERROR_NO_TRACKING_SERVICE + serviceError = ErrorTrackTunnels return } @@ -233,9 +248,7 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest var count uint32 err = wtsEnumerateSessions(0, 0, 1, &sessionsPointer, &count) if err != nil { - elog.Error(1, "Unable to enumerate current sessions: "+err.Error()) - changes <- svc.Status{State: svc.StopPending} - exitCode = ERROR_BAD_LOGON_SESSION_STATE + serviceError = ErrorEnumerateSessions return } sessions := *(*[]wtsSessionInfo)(unsafe.Pointer(&struct { @@ -287,8 +300,9 @@ loop: } procsLock.Unlock() } + default: - elog.Info(1, "Unexpected service control request "+strconv.Itoa(int(c.Cmd))) + elog.Info(1, fmt.Sprintf("Unexpected service control request #%d\n", c)) } } } diff --git a/service/service_tunnel.go b/service/service_tunnel.go index 3dc19f79..bf80b8b8 100644 --- a/service/service_tunnel.go +++ b/service/service_tunnel.go @@ -8,19 +8,17 @@ package service import ( "bufio" "fmt" + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/eventlog" "golang.zx2c4.com/winipcfg" + "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/ipc" + "golang.zx2c4.com/wireguard/tun" + "golang.zx2c4.com/wireguard/windows/conf" "log" "net" "runtime/debug" "strings" - - "golang.org/x/sys/windows/svc" - "golang.org/x/sys/windows/svc/eventlog" - - "golang.zx2c4.com/wireguard/device" - "golang.zx2c4.com/wireguard/tun" - "golang.zx2c4.com/wireguard/windows/conf" ) type confElogger struct { @@ -52,8 +50,22 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, var uapi net.Listener var routeChangeCallback *winipcfg.RouteChangeCallback var elog *eventlog.Log + var logger *device.Logger + var err error + serviceError := ErrorSuccess defer func() { + svcSpecificEC, exitCode = determineErrorCode(err, serviceError) + logErr := combineErrors(err, serviceError) + if logErr != nil { + if logger != nil { + logger.Error.Println(logErr.Error()) + } else if elog != nil { + elog.Error(1, logErr.Error()) + } else { + fmt.Println(logErr.Error()) + } + } changes <- svc.Status{State: svc.StopPending} if routeChangeCallback != nil { routeChangeCallback.Unregister() @@ -71,9 +83,9 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, //TODO: remember to clean this up in the msi uninstaller eventlog.InstallAsEventCreate("WireGuard", eventlog.Info|eventlog.Warning|eventlog.Error) - elog, err := eventlog.Open("WireGuard") + elog, err = eventlog.Open("WireGuard") if err != nil { - exitCode = ERROR_LOG_CONTAINER_OPEN_FAILED + serviceError = ErrorEventlogOpen return } log.SetOutput(elogger{elog}) @@ -86,12 +98,11 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, conf, err := conf.LoadFromPath(service.path) if err != nil { - elog.Error(1, "Unable to load configuration file from path "+service.path+": "+err.Error()) - exitCode = ERROR_OPEN_FAILED + serviceError = ErrorLoadConfiguration return } - logger := &device.Logger{ + logger = &device.Logger{ Debug: log.New(&confElogger{elog: elog, conf: conf, level: 1}, "", 0), Info: log.New(&confElogger{elog: elog, conf: conf, level: 2}, "", 0), Error: log.New(&confElogger{elog: elog, conf: conf, level: 3}, "", 0), @@ -101,16 +112,16 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, logger.Debug.Println("Debug log enabled") wintun, err := tun.CreateTUN(conf.Name) - if err == nil { - realInterfaceName, err2 := wintun.Name() - if err2 == nil { - conf.Name = realInterfaceName - } - } else { - logger.Error.Println("Failed to create TUN device:", err) - exitCode = ERROR_ADAP_HDW_ERR + if err != nil { + serviceError = ErrorCreateWintun return } + realInterfaceName, err := wintun.Name() + if err != nil { + serviceError = ErrorDetermineWintunName + return + } + conf.Name = realInterfaceName dev = device.NewDevice(wintun, logger) dev.Up() @@ -118,8 +129,7 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, uapi, err = ipc.UAPIListen(conf.Name) if err != nil { - logger.Error.Println("Failed to listen on uapi socket:", err) - exitCode = ERROR_PIPE_LISTENING + serviceError = ErrorUAPIListen return } @@ -136,24 +146,27 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, uapiConf, err := conf.ToUAPI() if err != nil { - logger.Error.Println("Failed to convert to UAPI serialization:", err) - exitCode = ERROR_INVALID_PARAMETER + serviceError = ErrorUAPISerialization return } - dev.IpcSetOperation(bufio.NewReader(strings.NewReader(uapiConf))) + ipcErr := dev.IpcSetOperation(bufio.NewReader(strings.NewReader(uapiConf))) + if ipcErr != nil { + err = ipcErr + serviceError = ErrorDeviceSetConfig + return + } + guid := wintun.(*tun.NativeTun).GUID() routeChangeCallback, err = monitorDefaultRoutes(dev, conf.Interface.Mtu == 0, &guid) if err != nil { - logger.Error.Println("Unable to bind sockets to default route:", err) - exitCode = ERROR_NETWORK_BUSY + serviceError = ErrorBindSocketsToDefaultRoutes return } err = configureInterface(conf, &guid) if err != nil { - logger.Error.Println("Unable to set interface addresses, routes, DNS, or IP settings:", err) - exitCode = ERROR_NETWORK_BUSY + serviceError = ErrorSetNetConfig return } @@ -168,7 +181,7 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, case svc.Interrogate: changes <- c.CurrentStatus default: - logger.Error.Printf("Unexpected service control request #%d", c) + logger.Error.Printf("Unexpected service control request #%d\n", c) } case <-dev.Wait(): return diff --git a/service/tunneltracker.go b/service/tunneltracker.go index 89de0c38..ca2b6a45 100644 --- a/service/tunneltracker.go +++ b/service/tunneltracker.go @@ -10,6 +10,7 @@ import ( "golang.org/x/sys/windows/svc/mgr" "golang.zx2c4.com/wireguard/windows/conf" "runtime" + "syscall" "unsafe" ) @@ -104,12 +105,21 @@ func trackTunnelService(tunnelName string, svc *mgr.Service) { if notifier.notificationStatus != 0 { return } + var tunnelError error state := TunnelUnknown if notifier.notificationTriggered&serviceNotify_DELETE_PENDING != 0 { state = TunnelDeleting } else if notifier.notificationTriggered&serviceNotify_STOPPED != 0 { state = TunnelStopped hasStopped = true + if notifier.win32ExitCode == uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR) { + maybeErr := Error(notifier.serviceSpecificExitCode) + if maybeErr != ErrorSuccess { + tunnelError = maybeErr + } + } else if notifier.win32ExitCode != uint32(windows.NO_ERROR) { + tunnelError = syscall.Errno(notifier.win32ExitCode) + } } else if notifier.notificationTriggered&serviceNotify_STOP_PENDING != 0 && hasStopped { state = TunnelStopping } else if notifier.notificationTriggered&serviceNotify_RUNNING != 0 { @@ -119,7 +129,7 @@ func trackTunnelService(tunnelName string, svc *mgr.Service) { state = TunnelStarting hasStopped = false } - IPCServerNotifyTunnelChange(tunnelName, state) + IPCServerNotifyTunnelChange(tunnelName, state, tunnelError) if state == TunnelDeleting { return } diff --git a/ui/ui.go b/ui/ui.go index 18c83e2d..db97823d 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -242,8 +242,15 @@ func RunUI() { } } } - service.IPCClientRegisterTunnelChange(func(tunnel *service.Tunnel, state service.TunnelState) { - setServiceState(tunnel, state, true) + service.IPCClientRegisterTunnelChange(func(tunnel *service.Tunnel, state service.TunnelState, err error) { + setServiceState(tunnel, state, err == nil) + if err != nil { + if mw.Visible() { + walk.MsgBox(mw, "Tunnel Error", err.Error()+"\n\nPlease consult the Windows Event Log for more information.", walk.MsgBoxIconWarning) + } else { + tray.ShowError("WireGuard Tunnel Error", err.Error()) + } + } }) go func() { tunnels, err := service.IPCClientTunnels() -- cgit v1.2.3-59-g8ed1b