From 69f0fe67b63d90e523a5a1241fb1b46c2e8dbe03 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Sun, 3 Mar 2019 04:04:41 +0100 Subject: global: begin modularization --- ipc/uapi_bsd.go | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++ ipc/uapi_linux.go | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++ ipc/uapi_windows.go | 76 ++++++++++++++++++++ 3 files changed, 477 insertions(+) create mode 100644 ipc/uapi_bsd.go create mode 100644 ipc/uapi_linux.go create mode 100644 ipc/uapi_windows.go (limited to 'ipc') diff --git a/ipc/uapi_bsd.go b/ipc/uapi_bsd.go new file mode 100644 index 0000000..f66c386 --- /dev/null +++ b/ipc/uapi_bsd.go @@ -0,0 +1,202 @@ +// +build darwin freebsd openbsd + +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved. + */ + +package ipc + +import ( + "errors" + "fmt" + "golang.org/x/sys/unix" + "net" + "os" + "path" + "unsafe" +) + +var socketDirectory = "/var/run/wireguard" + +const ( + IpcErrorIO = -int64(unix.EIO) + IpcErrorProtocol = -int64(unix.EPROTO) + IpcErrorInvalid = -int64(unix.EINVAL) + IpcErrorPortInUse = -int64(unix.EADDRINUSE) + socketName = "%s.sock" +) + +type UAPIListener struct { + listener net.Listener // unix socket listener + connNew chan net.Conn + connErr chan error + kqueueFd int + keventFd int +} + +func (l *UAPIListener) Accept() (net.Conn, error) { + for { + select { + case conn := <-l.connNew: + return conn, nil + + case err := <-l.connErr: + return nil, err + } + } +} + +func (l *UAPIListener) Close() error { + err1 := unix.Close(l.kqueueFd) + err2 := unix.Close(l.keventFd) + err3 := l.listener.Close() + if err1 != nil { + return err1 + } + if err2 != nil { + return err2 + } + return err3 +} + +func (l *UAPIListener) Addr() net.Addr { + return l.listener.Addr() +} + +func UAPIListen(name string, file *os.File) (net.Listener, error) { + + // wrap file in listener + + listener, err := net.FileListener(file) + if err != nil { + return nil, err + } + + uapi := &UAPIListener{ + listener: listener, + connNew: make(chan net.Conn, 1), + connErr: make(chan error, 1), + } + + if unixListener, ok := listener.(*net.UnixListener); ok { + unixListener.SetUnlinkOnClose(true) + } + + socketPath := path.Join( + socketDirectory, + fmt.Sprintf(socketName, name), + ) + + // watch for deletion of socket + + uapi.kqueueFd, err = unix.Kqueue() + if err != nil { + return nil, err + } + uapi.keventFd, err = unix.Open(socketDirectory, unix.O_RDONLY, 0) + if err != nil { + unix.Close(uapi.kqueueFd) + return nil, err + } + + go func(l *UAPIListener) { + event := unix.Kevent_t{ + Filter: unix.EVFILT_VNODE, + Flags: unix.EV_ADD | unix.EV_ENABLE | unix.EV_ONESHOT, + Fflags: unix.NOTE_WRITE, + } + // Allow this assignment to work with both the 32-bit and 64-bit version + // of the above struct. If you know another way, please submit a patch. + *(*uintptr)(unsafe.Pointer(&event.Ident)) = uintptr(uapi.keventFd) + events := make([]unix.Kevent_t, 1) + n := 1 + var kerr error + for { + // start with lstat to avoid race condition + if _, err := os.Lstat(socketPath); os.IsNotExist(err) { + l.connErr <- err + return + } + if kerr != nil || n != 1 { + if kerr != nil { + l.connErr <- kerr + } else { + l.connErr <- errors.New("kqueue returned empty") + } + return + } + n, kerr = unix.Kevent(uapi.kqueueFd, []unix.Kevent_t{event}, events, nil) + } + }(uapi) + + // watch for new connections + + go func(l *UAPIListener) { + for { + conn, err := l.listener.Accept() + if err != nil { + l.connErr <- err + break + } + l.connNew <- conn + } + }(uapi) + + return uapi, nil +} + +func UAPIOpen(name string) (*os.File, error) { + + // check if path exist + + err := os.MkdirAll(socketDirectory, 0755) + if err != nil && !os.IsExist(err) { + return nil, err + } + + // open UNIX socket + + socketPath := path.Join( + socketDirectory, + fmt.Sprintf(socketName, name), + ) + + addr, err := net.ResolveUnixAddr("unix", socketPath) + if err != nil { + return nil, err + } + + oldUmask := unix.Umask(0077) + listener, err := func() (*net.UnixListener, error) { + + // initial connection attempt + + listener, err := net.ListenUnix("unix", addr) + if err == nil { + return listener, nil + } + + // check if socket already active + + _, err = net.Dial("unix", socketPath) + if err == nil { + return nil, errors.New("unix socket in use") + } + + // cleanup & attempt again + + err = os.Remove(socketPath) + if err != nil { + return nil, err + } + return net.ListenUnix("unix", addr) + }() + unix.Umask(oldUmask) + + if err != nil { + return nil, err + } + + return listener.File() +} diff --git a/ipc/uapi_linux.go b/ipc/uapi_linux.go new file mode 100644 index 0000000..8af3d8c --- /dev/null +++ b/ipc/uapi_linux.go @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved. + */ + +package ipc + +import ( + "errors" + "fmt" + "golang.org/x/sys/unix" + "golang.zx2c4.com/wireguard/rwcancel" + "net" + "os" + "path" +) + +var socketDirectory = "/var/run/wireguard" + +const ( + IpcErrorIO = -int64(unix.EIO) + IpcErrorProtocol = -int64(unix.EPROTO) + IpcErrorInvalid = -int64(unix.EINVAL) + IpcErrorPortInUse = -int64(unix.EADDRINUSE) + socketName = "%s.sock" +) + +type UAPIListener struct { + listener net.Listener // unix socket listener + connNew chan net.Conn + connErr chan error + inotifyFd int + inotifyRWCancel *rwcancel.RWCancel +} + +func (l *UAPIListener) Accept() (net.Conn, error) { + for { + select { + case conn := <-l.connNew: + return conn, nil + + case err := <-l.connErr: + return nil, err + } + } +} + +func (l *UAPIListener) Close() error { + err1 := unix.Close(l.inotifyFd) + err2 := l.inotifyRWCancel.Cancel() + err3 := l.listener.Close() + if err1 != nil { + return err1 + } + if err2 != nil { + return err2 + } + return err3 +} + +func (l *UAPIListener) Addr() net.Addr { + return l.listener.Addr() +} + +func UAPIListen(name string, file *os.File) (net.Listener, error) { + + // wrap file in listener + + listener, err := net.FileListener(file) + if err != nil { + return nil, err + } + + if unixListener, ok := listener.(*net.UnixListener); ok { + unixListener.SetUnlinkOnClose(true) + } + + uapi := &UAPIListener{ + listener: listener, + connNew: make(chan net.Conn, 1), + connErr: make(chan error, 1), + } + + // watch for deletion of socket + + socketPath := path.Join( + socketDirectory, + fmt.Sprintf(socketName, name), + ) + + uapi.inotifyFd, err = unix.InotifyInit() + if err != nil { + return nil, err + } + + _, err = unix.InotifyAddWatch( + uapi.inotifyFd, + socketPath, + unix.IN_ATTRIB| + unix.IN_DELETE| + unix.IN_DELETE_SELF, + ) + + if err != nil { + return nil, err + } + + uapi.inotifyRWCancel, err = rwcancel.NewRWCancel(uapi.inotifyFd) + if err != nil { + unix.Close(uapi.inotifyFd) + return nil, err + } + + go func(l *UAPIListener) { + var buff [0]byte + for { + // start with lstat to avoid race condition + if _, err := os.Lstat(socketPath); os.IsNotExist(err) { + l.connErr <- err + return + } + _, err := uapi.inotifyRWCancel.Read(buff[:]) + if err != nil { + l.connErr <- err + return + } + } + }(uapi) + + // watch for new connections + + go func(l *UAPIListener) { + for { + conn, err := l.listener.Accept() + if err != nil { + l.connErr <- err + break + } + l.connNew <- conn + } + }(uapi) + + return uapi, nil +} + +func UAPIOpen(name string) (*os.File, error) { + + // check if path exist + + err := os.MkdirAll(socketDirectory, 0755) + if err != nil && !os.IsExist(err) { + return nil, err + } + + // open UNIX socket + + socketPath := path.Join( + socketDirectory, + fmt.Sprintf(socketName, name), + ) + + addr, err := net.ResolveUnixAddr("unix", socketPath) + if err != nil { + return nil, err + } + + oldUmask := unix.Umask(0077) + listener, err := func() (*net.UnixListener, error) { + + // initial connection attempt + + listener, err := net.ListenUnix("unix", addr) + if err == nil { + return listener, nil + } + + // check if socket already active + + _, err = net.Dial("unix", socketPath) + if err == nil { + return nil, errors.New("unix socket in use") + } + + // cleanup & attempt again + + err = os.Remove(socketPath) + if err != nil { + return nil, err + } + return net.ListenUnix("unix", addr) + }() + unix.Umask(oldUmask) + + if err != nil { + return nil, err + } + + return listener.File() +} diff --git a/ipc/uapi_windows.go b/ipc/uapi_windows.go new file mode 100644 index 0000000..209d0d2 --- /dev/null +++ b/ipc/uapi_windows.go @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved. + */ + +package ipc + +import ( + "github.com/Microsoft/go-winio" + "net" +) + +//TODO: replace these with actual standard windows error numbers from the win package +const ( + IpcErrorIO = -int64(5) + IpcErrorProtocol = -int64(71) + IpcErrorInvalid = -int64(22) + IpcErrorPortInUse = -int64(98) +) + +type UAPIListener struct { + listener net.Listener // unix socket listener + connNew chan net.Conn + connErr chan error + kqueueFd int + keventFd int +} + +func (l *UAPIListener) Accept() (net.Conn, error) { + for { + select { + case conn := <-l.connNew: + return conn, nil + + case err := <-l.connErr: + return nil, err + } + } +} + +func (l *UAPIListener) Close() error { + return l.listener.Close() +} + +func (l *UAPIListener) Addr() net.Addr { + return l.listener.Addr() +} + +func UAPIListen(name string) (net.Listener, error) { + config := winio.PipeConfig{ + SecurityDescriptor: "", //TODO: we want this to be a very locked down pipe. + } + listener, err := winio.ListenPipe("\\\\.\\pipe\\wireguard\\"+name, &config) //TODO: choose sane name. + if err != nil { + return nil, err + } + + uapi := &UAPIListener{ + listener: listener, + connNew: make(chan net.Conn, 1), + connErr: make(chan error, 1), + } + + go func(l *UAPIListener) { + for { + conn, err := l.listener.Accept() + if err != nil { + l.connErr <- err + break + } + l.connNew <- conn + } + }(uapi) + + return uapi, nil +} -- cgit v1.2.3