From a3054a01dfb6033b75b9ad31189b1202cbedcefc Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Mon, 25 Feb 2019 18:47:12 +0100 Subject: ipc: add base of IPC --- service/ipc_client.go | 87 +++++++++++++++++++++++ service/ipc_event.go | 30 ++++++++ service/ipc_pipe.go | 58 +++++++++++++++ service/ipc_server.go | 190 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 365 insertions(+) create mode 100644 service/ipc_client.go create mode 100644 service/ipc_event.go create mode 100644 service/ipc_pipe.go create mode 100644 service/ipc_server.go diff --git a/service/ipc_client.go b/service/ipc_client.go new file mode 100644 index 00000000..25575014 --- /dev/null +++ b/service/ipc_client.go @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package service + +import ( + "golang.org/x/sys/windows" + "golang.zx2c4.com/wireguard/windows/conf" + "net/rpc" + "os" +) + +type Tunnel struct { + Name string +} + +type TunnelState int + +const ( + TunnelUnknown TunnelState = iota + TunnelStarted + TunnelStopped + TunnelStarting + TunnelStopping + TunnelDeleting +) + +var rpcClient *rpc.Client + +func InitializeIPCClient(reader *os.File, writer *os.File) { + rpcClient = rpc.NewClient(&pipeRWC{reader, writer}) +} + +func (t *Tunnel) StoredConfig() (c conf.Config, err error) { + err = rpcClient.Call("ManagerService.StoredConfig", t.Name, &c) + return +} + +func (t *Tunnel) RuntimeConfig() (c conf.Config, err error) { + err = rpcClient.Call("ManagerService.RuntimeConfig", t.Name, &c) + return +} + +func (t *Tunnel) Start() (TunnelState, error) { + var state TunnelState + return state, rpcClient.Call("ManagerService.Start", t.Name, &state) +} + +func (t *Tunnel) Stop() (TunnelState, error) { + var state TunnelState + return state, rpcClient.Call("ManagerService.Stop", t.Name, &state) +} + +func (t *Tunnel) Delete() (TunnelState, error) { + var state TunnelState + return state, rpcClient.Call("ManagerService.Delete", t.Name, &state) +} + +func (t *Tunnel) State() (TunnelState, error) { + var state TunnelState + return state, rpcClient.Call("ManagerService.State", t.Name, &state) +} + +func IPCClientNewTunnel(conf *conf.Config) (Tunnel, error) { + var tunnel Tunnel + return tunnel, rpcClient.Call("ManagerService.Create", *conf, &tunnel) +} + +func IPCClientTunnels() ([]Tunnel, error) { + var tunnels []Tunnel + return tunnels, rpcClient.Call("ManagerService.Tunnels", 0, &tunnels) +} + +func IPCClientQuit(stopTunnelsOnQuit bool) (bool, error) { + var alreadyQuit bool + return alreadyQuit, rpcClient.Call("ManagerService.Quit", stopTunnelsOnQuit, &alreadyQuit) +} + +func IPCClientRegisterAsNotificationThread() error { + return rpcClient.Call("ManagerService.RegisterAsNotificationThread", windows.GetCurrentThreadId(), nil) +} + +func IPCClientUnregisterAsNotificationThread() error { + return rpcClient.Call("ManagerService.UnregisterAsNotificationThread", windows.GetCurrentThreadId(), nil) +} diff --git a/service/ipc_event.go b/service/ipc_event.go new file mode 100644 index 00000000..f56f289d --- /dev/null +++ b/service/ipc_event.go @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package service + +import "golang.org/x/sys/windows" + +//sys registerWindowMessage(name *uint16) (message uint, err error) = user32.RegisterWindowMessageW + +var ( + tunnelsChangedMessage uint + tunnelChangedMessage uint +) +func IPCRegisterEventMessages() error { + m, err := registerWindowMessage(windows.StringToUTF16Ptr("WireGuard Manager Event - Tunnels Changed")) + if err != nil { + return err + } + tunnelsChangedMessage = m + + m, err = registerWindowMessage(windows.StringToUTF16Ptr("WireGuard Manager Event - Tunnel Changed")) + if err != nil { + return err + } + tunnelChangedMessage = m + + return nil +} diff --git a/service/ipc_pipe.go b/service/ipc_pipe.go new file mode 100644 index 00000000..ee63f2d4 --- /dev/null +++ b/service/ipc_pipe.go @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package service + +import ( + "golang.org/x/sys/windows" + "os" + "strconv" +) + +type pipeRWC struct { + reader *os.File + writer *os.File +} + +func (p *pipeRWC) Read(b []byte) (int, error) { + return p.reader.Read(b) +} + +func (p *pipeRWC) Write(b []byte) (int, error) { + return p.writer.Write(b) +} + +func (p *pipeRWC) Close() error { + err1 := p.writer.Close() + err2 := p.reader.Close() + if err1 != nil { + return err1 + } + return err2 +} + +func inheritableSocketpairEmulation() (ourReader *os.File, theirReader *os.File, theirReaderStr string, ourWriter *os.File, theirWriter *os.File, theirWriterStr string, err error) { + ourReader, theirWriter, err = os.Pipe() + if err != nil { + return + } + err = windows.SetHandleInformation(windows.Handle(theirWriter.Fd()), windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT) + if err != nil { + return + } + theirWriterStr = strconv.FormatUint(uint64(theirWriter.Fd()), 10) + + theirReader, ourWriter, err = os.Pipe() + if err != nil { + return + } + err = windows.SetHandleInformation(windows.Handle(theirReader.Fd()), windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT) + if err != nil { + return + } + theirReaderStr = strconv.FormatUint(uint64(theirReader.Fd()), 10) + + return +} diff --git a/service/ipc_server.go b/service/ipc_server.go new file mode 100644 index 00000000..a2a4c9ee --- /dev/null +++ b/service/ipc_server.go @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package service + +import ( + "errors" + "golang.org/x/sys/windows" + "golang.zx2c4.com/wireguard/windows/conf" + "net/rpc" + "os" + "sync" + "sync/atomic" +) + +var managerServices = make(map[*ManagerService]bool) +var managerServicesLock sync.RWMutex +var haveQuit uint32 +var quitManagersChan = make(chan struct{}, 1) + +type ManagerService struct { + notifierHandles map[windows.Handle]bool + notifierHandlesLock sync.RWMutex +} + +func (s *ManagerService) StoredConfig(tunnelName string, config *conf.Config) error { + c, err := conf.LoadFromName(tunnelName) + if err != nil { + return err + } + *config = *c + return nil +} + +func (s *ManagerService) RuntimeConfig(tunnelName string, config *conf.Config) error { + //TODO + + return nil +} + +func (s *ManagerService) Start(tunnelName string, state *TunnelState) error { + c, err := conf.LoadFromName(tunnelName) + if err != nil { + return err + } + path, err := c.Path() + if err != nil { + return err + } + return InstallTunnel(path) + //TODO: write out *state +} + +func (s *ManagerService) Stop(tunnelName string, state *TunnelState) error { + return UninstallTunnel(tunnelName) + //TODO: This function should do nothing if the tunnel is already stopped + //TODO: write out *state +} + +func (s *ManagerService) Delete(tunnelName string, state *TunnelState) error { + err := s.Stop(tunnelName, state) + if err != nil { + return err + } + //TODO: wait for stopped somehow + if *state != TunnelStopped { + return errors.New("Unable to stop tunnel before deleting") + } + return conf.DeleteName(tunnelName) +} + +func (s *ManagerService) State(tunnelName string, state *TunnelState) error { + //TODO + + return nil +} + +func (s *ManagerService) Create(tunnelConfig conf.Config, tunnel *Tunnel) error { + err := tunnelConfig.Save() + if err != nil { + return err + } + *tunnel = Tunnel{tunnelConfig.Name} + return nil + //TODO: handle already existing situation + //TODO: handle already running and existing situation +} + +func (s *ManagerService) Tunnels(unused uintptr, tunnels *[]Tunnel) error { + names, err := conf.ListConfigNames() + if err != nil { + return err + } + *tunnels = make([]Tunnel, len(names)) + for i := 0; i < len(*tunnels); i++ { + (*tunnels)[i].Name = names[i] + } + return nil + //TODO: account for running ones that aren't in the configuration store somehow +} + +func (s *ManagerService) Quit(stopTunnelsOnQuit bool, alreadyQuit *bool) error { + if !atomic.CompareAndSwapUint32(&haveQuit, 0, 1) { + *alreadyQuit = true + return nil + } + *alreadyQuit = false + + // Work around potential race condition of delivering messages to the wrong process by removing from notifications. + managerServicesLock.Lock() + delete(managerServices, s) + managerServicesLock.Unlock() + + if stopTunnelsOnQuit { + names, err := conf.ListConfigNames() + if err != nil { + return err + } + for _, name := range names { + UninstallTunnel(name) + } + } + + quitManagersChan <- struct{}{} + return nil +} + +func (s *ManagerService) RegisterAsNotificationThread(handle windows.Handle, unused *uintptr) error { + s.notifierHandlesLock.Lock() + s.notifierHandles[handle] = true + s.notifierHandlesLock.Unlock() + return nil +} + +func (s *ManagerService) UnregisterAsNotificationThread(handle windows.Handle, unused *uintptr) error { + s.notifierHandlesLock.Lock() + delete(s.notifierHandles, handle) + s.notifierHandlesLock.Unlock() + return nil +} + +func IPCServerListen(reader *os.File, writer *os.File) error { + service := &ManagerService{notifierHandles: make(map[windows.Handle]bool)} + + server := rpc.NewServer() + err := server.Register(service) + if err != nil { + return err + } + + go func() { + managerServicesLock.Lock() + managerServices[service] = true + managerServicesLock.Unlock() + server.ServeConn(&pipeRWC{reader, writer}) + managerServicesLock.Lock() + delete(managerServices, service) + managerServicesLock.Unlock() + + }() + return nil +} + +//sys postMessage(hwnd windows.Handle, msg uint, wparam uintptr, lparam uintptr) (err error) = user32.PostMessageW + +func notifyAll(f func(handle windows.Handle)) { + managerServicesLock.RLock() + for m, _ := range managerServices { + m.notifierHandlesLock.RLock() + for handle, _ := range m.notifierHandles { + f(handle) + } + m.notifierHandlesLock.RUnlock() + } + managerServicesLock.RUnlock() +} + +func IPCServerNotifyTunnelChange(name string) { + notifyAll(func(handle windows.Handle) { + //TODO: postthreadmessage + }) +} + +func IPCServerNotifyTunnelsChange() { + notifyAll(func(handle windows.Handle) { + postMessage(handle, tunnelsChangedMessage, 0, 0) + }) +} -- cgit v1.2.3-59-g8ed1b