aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/service
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2019-03-10 02:37:34 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2019-03-10 03:37:23 +0100
commitda563a6ec66113fee96dad3ebf440607320637c6 (patch)
treeac98e26149c9829900e84977569452a2a9274a56 /service
parentinstaller: bring to foreground using shellexec process (diff)
downloadwireguard-windows-da563a6ec66113fee96dad3ebf440607320637c6.tar.xz
wireguard-windows-da563a6ec66113fee96dad3ebf440607320637c6.zip
service: keep track of proper errors
Diffstat (limited to 'service')
-rw-r--r--service/errors.go103
-rw-r--r--service/install.go4
-rw-r--r--service/ipc_client.go21
-rw-r--r--service/ipc_server.go12
-rw-r--r--service/service_manager.go56
-rw-r--r--service/service_tunnel.go73
-rw-r--r--service/tunneltracker.go12
7 files changed, 205 insertions, 76 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
}