diff options
-rw-r--r-- | go.mod | 7 | ||||
-rw-r--r-- | go.sum | 14 | ||||
-rw-r--r-- | main.go | 2 | ||||
-rw-r--r-- | main_windows.go | 86 | ||||
-rw-r--r-- | tun/tun_windows.go | 286 | ||||
-rw-r--r-- | uapi_windows.go | 76 |
6 files changed, 462 insertions, 9 deletions
@@ -1,7 +1,8 @@ module git.zx2c4.com/wireguard-go require ( - golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 - golang.org/x/net v0.0.0-20181207154023-610586996380 - golang.org/x/sys v0.0.0-20181210030007-2a47403f2ae5 + github.com/Microsoft/go-winio v0.4.11 + golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 + golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 + golang.org/x/sys v0.0.0-20190204103248-980327fe3c65 ) @@ -1,6 +1,8 @@ -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/net v0.0.0-20181207154023-610586996380 h1:zPQexyRtNYBc7bcHmehl1dH6TB3qn8zytv8cBGLDNY0= -golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/sys v0.0.0-20181210030007-2a47403f2ae5 h1:SlFRMb9PEnqzqnBRCynVOhxv4vHjB2lnIoxK6p5nzFM= -golang.org/x/sys v0.0.0-20181210030007-2a47403f2ae5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc= +golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sys v0.0.0-20190204103248-980327fe3c65 h1:kWe3kjq30rdgl/nMB+ZR7kWcMnlZZ35LNkvwLN7yBco= +golang.org/x/sys v0.0.0-20190204103248-980327fe3c65/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1,3 +1,5 @@ +// +build !windows + /* SPDX-License-Identifier: MIT * * Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved. diff --git a/main_windows.go b/main_windows.go new file mode 100644 index 0000000..f6a0b88 --- /dev/null +++ b/main_windows.go @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved. + */ + +package main + +import ( + "fmt" + "git.zx2c4.com/wireguard-go/tun" + "os" + "os/signal" +) + +const ( + ExitSetupSuccess = 0 + ExitSetupFailed = 1 +) + +func main() { + + if len(os.Args) != 1 { + os.Exit(ExitSetupFailed) + } + //configFile := os.Args[1] + interfaceName := "TODO" + + logger := NewLogger( + LogLevelDebug, + fmt.Sprintf("(%s) ", interfaceName), + ) + logger.Info.Println("Starting wireguard-go version", WireGuardGoVersion) + logger.Debug.Println("Debug log enabled") + + tun, err := tun.CreateTUN(interfaceName) + if err == nil { + realInterfaceName, err2 := tun.Name() + if err2 == nil { + interfaceName = realInterfaceName + } + } else { + logger.Error.Println("Failed to create TUN device:", err) + os.Exit(ExitSetupFailed) + } + + device := NewDevice(tun, logger) + logger.Info.Println("Device started") + + uapi, err := UAPIListen(interfaceName) + if err != nil { + logger.Error.Println("Failed to listen on uapi socket:", err) + os.Exit(ExitSetupFailed) + } + + errs := make(chan error) + term := make(chan os.Signal, 1) + + go func() { + for { + conn, err := uapi.Accept() + if err != nil { + errs <- err + return + } + go ipcHandle(device, conn) + } + }() + logger.Info.Println("UAPI listener started") + + // wait for program to terminate + + signal.Notify(term, os.Interrupt) + + select { + case <-term: + case <-errs: + case <-device.Wait(): + } + + // clean up + + uapi.Close() + device.Close() + + logger.Info.Println("Shutting down") +} diff --git a/tun/tun_windows.go b/tun/tun_windows.go new file mode 100644 index 0000000..19a9d2d --- /dev/null +++ b/tun/tun_windows.go @@ -0,0 +1,286 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2018-2019 WireGuard LLC. All Rights Reserved. + */ + +package tun + +import ( + "errors" + "fmt" + "os" + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + TUN_MIN_PACKET_SIZE = 20 + TUN_MAX_PACKET_SIZE = 1600 + TUN_MAX_PACKET_EXCHANGE = 256 // Number of packets that can be exchanged at a time + TUN_EXCHANGE_BUFFER_SIZE = 410632 +) + +const ( + TUN_SIGNAL_DATA_AVAIL = 0 + TUN_SIGNAL_CLOSE = 1 + + TUN_SIGNAL_MAX = 2 +) + +type TunPacket struct { + size uint32 + data [TUN_MAX_PACKET_SIZE]byte +} + +type TunRWQueue struct { + numPackets uint32 + packets [TUN_MAX_PACKET_EXCHANGE]TunPacket + left uint32 +} + +type nativeTun struct { + ifname string + tunName string + signalName *uint16 + tunFile *os.File + wrBuff TunRWQueue + rdBuff TunRWQueue + signals [TUN_SIGNAL_MAX]windows.Handle + rdNextPacket uint32 + events chan TUNEvent + errors chan error +} + +func CreateTUN(ifname string) (TUNDevice, error) { + signalNameUTF16, err := windows.UTF16PtrFromString(fmt.Sprintf("Global\\TUN_EVENT_%s", ifname)) + if err != nil { + return nil, err + } + + // Create instance. + tun := &nativeTun{ + ifname: ifname, + tunName: fmt.Sprintf("\\\\.\\Global\\TUN_%s_DEVICE", ifname), + signalName: signalNameUTF16, + events: make(chan TUNEvent, 10), + errors: make(chan error, 1), + } + + // Create close event. + tun.signals[TUN_SIGNAL_CLOSE], err = windows.CreateEvent(nil, 1 /*TRUE*/, 0 /*FALSE*/, nil) + if err != nil { + return nil, err + } + + return tun, nil +} + +func (tun *nativeTun) openTUN() error { + for { + // Open interface data pipe. + // Data pipe must be opened first, as the interface data available event is created when somebody actually connects to the data pipe. + file, err := os.OpenFile(tun.tunName, os.O_RDWR|os.O_SYNC, 0600) + if err != nil { + // After examining possible error conditions, many arose that were only temporary: windows.ERROR_FILE_NOT_FOUND, "read <filename> closed", etc. + // To simplify, we will enter a retry-loop on _any_ error until session is closed by user. + switch evt, e := windows.WaitForSingleObject(tun.signals[TUN_SIGNAL_CLOSE], 1000); evt { + case windows.WAIT_OBJECT_0, windows.WAIT_ABANDONED: + return errors.New("TUN closed") + case windows.WAIT_TIMEOUT: + continue + default: + return fmt.Errorf("unexpected result from WaitForSingleObject:", e) + } + } + + // Open interface data available event. + event, err := windows.OpenEvent(windows.SYNCHRONIZE, false, tun.signalName) + if err != nil { + file.Close() + return fmt.Errorf("opening interface data ready event failed:", err) + } + + tun.tunFile = file + tun.signals[TUN_SIGNAL_DATA_AVAIL] = event + + return nil + } +} + +func (tun *nativeTun) closeTUN() (err error) { + if tun.signals[TUN_SIGNAL_DATA_AVAIL] != 0 { + // Close interface data ready event. + e := windows.CloseHandle(tun.signals[TUN_SIGNAL_DATA_AVAIL]) + if err != nil { + err = e + } + + tun.signals[TUN_SIGNAL_DATA_AVAIL] = 0 + } + + if tun.tunFile != nil { + // Close interface data pipe. + e := tun.tunFile.Close() + if err != nil { + err = e + } + + tun.tunFile = nil + } + + return +} + +func (tun *nativeTun) Name() (string, error) { + return tun.ifname, nil +} + +func (tun *nativeTun) File() *os.File { + return nil +} + +func (tun *nativeTun) Events() chan TUNEvent { + return tun.events +} + +func (tun *nativeTun) Close() error { + windows.SetEvent(tun.signals[TUN_SIGNAL_CLOSE]) + err := windows.CloseHandle(tun.signals[TUN_SIGNAL_CLOSE]) + + e := tun.closeTUN() + if err == nil { + err = e + } + + if tun.events != nil { + close(tun.events) + } + + return err +} + +func (tun *nativeTun) MTU() (int, error) { + return 1500, nil +} + +func (tun *nativeTun) Read(buff []byte, offset int) (int, error) { + select { + case err := <-tun.errors: + return 0, err + + default: + for { + if tun.rdNextPacket < tun.rdBuff.numPackets { + // Get packet from the queue. + tunPacket := &tun.rdBuff.packets[tun.rdNextPacket] + tun.rdNextPacket++ + + if tunPacket.size < TUN_MIN_PACKET_SIZE || TUN_MAX_PACKET_SIZE < tunPacket.size { + // Invalid packet size. + continue + } + + // Copy data. + copy(buff[offset:], tunPacket.data[:tunPacket.size]) + return int(tunPacket.size), nil + } + + if tun.signals[TUN_SIGNAL_DATA_AVAIL] == 0 { + // Data pipe and interface data available event are not open (yet). + err := tun.openTUN() + if err != nil { + return 0, err + } + } + + if tun.rdBuff.numPackets < TUN_MAX_PACKET_EXCHANGE || tun.rdBuff.left == 0 { + // Buffer was not full. Wait for the interface data or user close. + r, err := windows.WaitForMultipleObjects(tun.signals[:], false, windows.INFINITE) + if err != nil { + return 0, fmt.Errorf("waiting for data failed:", err) + } + switch r { + case windows.WAIT_OBJECT_0 + TUN_SIGNAL_DATA_AVAIL: + // Data is available. + case windows.WAIT_ABANDONED + TUN_SIGNAL_DATA_AVAIL: + // TUN stopped. Reopen it. + tun.closeTUN() + continue + case windows.WAIT_OBJECT_0 + TUN_SIGNAL_CLOSE, windows.WAIT_ABANDONED + TUN_SIGNAL_CLOSE: + return 0, errors.New("TUN closed") + case windows.WAIT_TIMEOUT: + // Congratulations, we reached infinity. Let's do it again! :) + continue + default: + return 0, errors.New("unexpected result from WaitForMultipleObjects") + } + } + + // Fill queue. + data := (*[TUN_EXCHANGE_BUFFER_SIZE]byte)(unsafe.Pointer(&tun.rdBuff)) + n, err := tun.tunFile.Read(data[:]) + tun.rdNextPacket = 0 + if n != TUN_EXCHANGE_BUFFER_SIZE || err != nil { + // TUN interface stopped, returned incomplete data, etc. + // Retry. + tun.rdBuff.numPackets = 0 + tun.closeTUN() + continue + } + } + } +} + +func (tun *nativeTun) flush() error { + // Flush write buffer. + data := (*[TUN_EXCHANGE_BUFFER_SIZE]byte)(unsafe.Pointer(&tun.wrBuff)) + n, err := tun.tunFile.Write(data[:]) + tun.wrBuff.numPackets = 0 + if err != nil { + return err + } + if n != TUN_EXCHANGE_BUFFER_SIZE { + return fmt.Errorf("%d byte(s) written, %d byte(s) expected", n, TUN_EXCHANGE_BUFFER_SIZE) + } + + return nil +} + +func (tun *nativeTun) putTunPacket(buff []byte) error { + size := len(buff) + if size == 0 { + return errors.New("empty packet") + } + if size > TUN_MAX_PACKET_SIZE { + return errors.New("packet too big") + } + + if tun.wrBuff.numPackets >= TUN_MAX_PACKET_EXCHANGE { + // Queue is full -> flush first. + err := tun.flush() + if err != nil { + return err + } + } + + // Push packet to the buffer. + tunPacket := &tun.wrBuff.packets[tun.wrBuff.numPackets] + tunPacket.size = uint32(size) + copy(tunPacket.data[:size], buff) + + tun.wrBuff.numPackets++ + + return nil +} + +func (tun *nativeTun) Write(buff []byte, offset int) (int, error) { + + err := tun.putTunPacket(buff[offset:]) + if err != nil { + return 0, err + } + + // Flush write buffer. + return len(buff) - offset, tun.flush() +} diff --git a/uapi_windows.go b/uapi_windows.go new file mode 100644 index 0000000..64917f5 --- /dev/null +++ b/uapi_windows.go @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved. + */ + +package main + +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 +} |