aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2021-11-04 12:47:08 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2021-11-04 12:49:00 +0100
commit415007cec224e00ca8633a5d70c258cf9ab0cddd (patch)
tree0f611b5778829c46f478b4541ebed31997d0ff4a
downloadwintun-go-415007cec224e00ca8633a5d70c258cf9ab0cddd.tar.xz
wintun-go-415007cec224e00ca8633a5d70c258cf9ab0cddd.zip
Initial import from wireguard-go 380ee85
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r--LICENSE17
-rw-r--r--README.md9
-rw-r--r--dll.go130
-rw-r--r--go.mod5
-rw-r--r--go.sum2
-rw-r--r--session.go92
-rw-r--r--wintun.go152
7 files changed, 407 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f85e365
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,17 @@
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bd73e0f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,9 @@
+## wintun-go
+
+This contains bindings to use [Wintun](https://www.wintun.net) from Go.
+
+```go
+import "golang.zx2c4.com/wintun"
+```
+
+- [Documentation](https://pkg.go.dev/golang.zx2c4.com/wintun)
diff --git a/dll.go b/dll.go
new file mode 100644
index 0000000..79ce91a
--- /dev/null
+++ b/dll.go
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
+ */
+
+//go:build windows
+
+package wintun
+
+import (
+ "fmt"
+ "sync"
+ "sync/atomic"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL {
+ return &lazyDLL{Name: name, onLoad: onLoad}
+}
+
+func (d *lazyDLL) NewProc(name string) *lazyProc {
+ return &lazyProc{dll: d, Name: name}
+}
+
+type lazyProc struct {
+ Name string
+ mu sync.Mutex
+ dll *lazyDLL
+ addr uintptr
+}
+
+func (p *lazyProc) Find() error {
+ if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr))) != nil {
+ return nil
+ }
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ if p.addr != 0 {
+ return nil
+ }
+
+ err := p.dll.Load()
+ if err != nil {
+ return fmt.Errorf("Error loading %v DLL: %w", p.dll.Name, err)
+ }
+ addr, err := p.nameToAddr()
+ if err != nil {
+ return fmt.Errorf("Error getting %v address: %w", p.Name, err)
+ }
+
+ atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr)), unsafe.Pointer(addr))
+ return nil
+}
+
+func (p *lazyProc) Addr() uintptr {
+ err := p.Find()
+ if err != nil {
+ panic(err)
+ }
+ return p.addr
+}
+
+type lazyDLL struct {
+ Name string
+ mu sync.Mutex
+ module windows.Handle
+ onLoad func(d *lazyDLL)
+}
+
+func (d *lazyDLL) Load() error {
+ if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil {
+ return nil
+ }
+ d.mu.Lock()
+ defer d.mu.Unlock()
+ if d.module != 0 {
+ return nil
+ }
+
+ const (
+ LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200
+ LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
+ )
+ module, err := windows.LoadLibraryEx(d.Name, 0, LOAD_LIBRARY_SEARCH_APPLICATION_DIR|LOAD_LIBRARY_SEARCH_SYSTEM32)
+ if err != nil {
+ return fmt.Errorf("Unable to load library: %w", err)
+ }
+
+ atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module))
+ if d.onLoad != nil {
+ d.onLoad(d)
+ }
+ return nil
+}
+
+func (p *lazyProc) nameToAddr() (uintptr, error) {
+ return windows.GetProcAddress(p.dll.module, p.Name)
+}
+
+// Version returns the version of the Wintun DLL.
+func Version() string {
+ if modwintun.Load() != nil {
+ return "unknown"
+ }
+ resInfo, err := windows.FindResource(modwintun.module, windows.ResourceID(1), windows.RT_VERSION)
+ if err != nil {
+ return "unknown"
+ }
+ data, err := windows.LoadResourceData(modwintun.module, resInfo)
+ if err != nil {
+ return "unknown"
+ }
+
+ var fixedInfo *windows.VS_FIXEDFILEINFO
+ fixedInfoLen := uint32(unsafe.Sizeof(*fixedInfo))
+ err = windows.VerQueryValue(unsafe.Pointer(&data[0]), `\`, unsafe.Pointer(&fixedInfo), &fixedInfoLen)
+ if err != nil {
+ return "unknown"
+ }
+ version := fmt.Sprintf("%d.%d", (fixedInfo.FileVersionMS>>16)&0xff, (fixedInfo.FileVersionMS>>0)&0xff)
+ if nextNibble := (fixedInfo.FileVersionLS >> 16) & 0xff; nextNibble != 0 {
+ version += fmt.Sprintf(".%d", nextNibble)
+ }
+ if nextNibble := (fixedInfo.FileVersionLS >> 0) & 0xff; nextNibble != 0 {
+ version += fmt.Sprintf(".%d", nextNibble)
+ }
+ return version
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..856595e
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module golang.zx2c4.com/wintun
+
+go 1.17
+
+require golang.org/x/sys v0.0.0-20211103235746-7861aae1554b
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..6e0fe0b
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
+golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/session.go b/session.go
new file mode 100644
index 0000000..ae966ca
--- /dev/null
+++ b/session.go
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
+ */
+
+//go:build windows
+
+package wintun
+
+import (
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+type Session struct {
+ handle uintptr
+}
+
+const (
+ PacketSizeMax = 0xffff // Maximum packet size
+ RingCapacityMin = 0x20000 // Minimum ring capacity (128 kiB)
+ RingCapacityMax = 0x4000000 // Maximum ring capacity (64 MiB)
+)
+
+// Packet with data
+type Packet struct {
+ Next *Packet // Pointer to next packet in queue
+ Size uint32 // Size of packet (max WINTUN_MAX_IP_PACKET_SIZE)
+ Data *[PacketSizeMax]byte // Pointer to layer 3 IPv4 or IPv6 packet
+}
+
+var (
+ procWintunAllocateSendPacket = modwintun.NewProc("WintunAllocateSendPacket")
+ procWintunEndSession = modwintun.NewProc("WintunEndSession")
+ procWintunGetReadWaitEvent = modwintun.NewProc("WintunGetReadWaitEvent")
+ procWintunReceivePacket = modwintun.NewProc("WintunReceivePacket")
+ procWintunReleaseReceivePacket = modwintun.NewProc("WintunReleaseReceivePacket")
+ procWintunSendPacket = modwintun.NewProc("WintunSendPacket")
+ procWintunStartSession = modwintun.NewProc("WintunStartSession")
+)
+
+func (wintun *Adapter) StartSession(capacity uint32) (session Session, err error) {
+ r0, _, e1 := syscall.Syscall(procWintunStartSession.Addr(), 2, uintptr(wintun.handle), uintptr(capacity), 0)
+ if r0 == 0 {
+ err = e1
+ } else {
+ session = Session{r0}
+ }
+ return
+}
+
+func (session Session) End() {
+ syscall.Syscall(procWintunEndSession.Addr(), 1, session.handle, 0, 0)
+ session.handle = 0
+}
+
+func (session Session) ReadWaitEvent() (handle windows.Handle) {
+ r0, _, _ := syscall.Syscall(procWintunGetReadWaitEvent.Addr(), 1, session.handle, 0, 0)
+ handle = windows.Handle(r0)
+ return
+}
+
+func (session Session) ReceivePacket() (packet []byte, err error) {
+ var packetSize uint32
+ r0, _, e1 := syscall.Syscall(procWintunReceivePacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packetSize)), 0)
+ if r0 == 0 {
+ err = e1
+ return
+ }
+ packet = unsafe.Slice((*byte)(unsafe.Pointer(r0)), packetSize)
+ return
+}
+
+func (session Session) ReleaseReceivePacket(packet []byte) {
+ syscall.Syscall(procWintunReleaseReceivePacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packet[0])), 0)
+}
+
+func (session Session) AllocateSendPacket(packetSize int) (packet []byte, err error) {
+ r0, _, e1 := syscall.Syscall(procWintunAllocateSendPacket.Addr(), 2, session.handle, uintptr(packetSize), 0)
+ if r0 == 0 {
+ err = e1
+ return
+ }
+ packet = unsafe.Slice((*byte)(unsafe.Pointer(r0)), packetSize)
+ return
+}
+
+func (session Session) SendPacket(packet []byte) {
+ syscall.Syscall(procWintunSendPacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packet[0])), 0)
+}
diff --git a/wintun.go b/wintun.go
new file mode 100644
index 0000000..404a2ff
--- /dev/null
+++ b/wintun.go
@@ -0,0 +1,152 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
+ */
+
+//go:build windows
+
+package wintun
+
+import (
+ "log"
+ "runtime"
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+type loggerLevel int
+
+const (
+ logInfo loggerLevel = iota
+ logWarn
+ logErr
+)
+
+const AdapterNameMax = 128
+
+type Adapter struct {
+ handle uintptr
+}
+
+var (
+ modwintun = newLazyDLL("wintun.dll", setupLogger)
+ procWintunCreateAdapter = modwintun.NewProc("WintunCreateAdapter")
+ procWintunOpenAdapter = modwintun.NewProc("WintunOpenAdapter")
+ procWintunCloseAdapter = modwintun.NewProc("WintunCloseAdapter")
+ procWintunDeleteDriver = modwintun.NewProc("WintunDeleteDriver")
+ procWintunGetAdapterLUID = modwintun.NewProc("WintunGetAdapterLUID")
+ procWintunGetRunningDriverVersion = modwintun.NewProc("WintunGetRunningDriverVersion")
+)
+
+type TimestampedWriter interface {
+ WriteWithTimestamp(p []byte, ts int64) (n int, err error)
+}
+
+func logMessage(level loggerLevel, timestamp uint64, msg *uint16) int {
+ if tw, ok := log.Default().Writer().(TimestampedWriter); ok {
+ tw.WriteWithTimestamp([]byte(log.Default().Prefix()+windows.UTF16PtrToString(msg)), (int64(timestamp)-116444736000000000)*100)
+ } else {
+ log.Println(windows.UTF16PtrToString(msg))
+ }
+ return 0
+}
+
+func setupLogger(dll *lazyDLL) {
+ var callback uintptr
+ if runtime.GOARCH == "386" {
+ callback = windows.NewCallback(func(level loggerLevel, timestampLow, timestampHigh uint32, msg *uint16) int {
+ return logMessage(level, uint64(timestampHigh)<<32|uint64(timestampLow), msg)
+ })
+ } else if runtime.GOARCH == "arm" {
+ callback = windows.NewCallback(func(level loggerLevel, _, timestampLow, timestampHigh uint32, msg *uint16) int {
+ return logMessage(level, uint64(timestampHigh)<<32|uint64(timestampLow), msg)
+ })
+ } else if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
+ callback = windows.NewCallback(logMessage)
+ }
+ syscall.Syscall(dll.NewProc("WintunSetLogger").Addr(), 1, callback, 0, 0)
+}
+
+func closeAdapter(wintun *Adapter) {
+ syscall.Syscall(procWintunCloseAdapter.Addr(), 1, wintun.handle, 0, 0)
+}
+
+// CreateAdapter creates a Wintun adapter. name is the cosmetic name of the adapter.
+// tunnelType represents the type of adapter and should be "Wintun". requestedGUID is
+// the GUID of the created network adapter, which then influences NLA generation
+// deterministically. If it is set to nil, the GUID is chosen by the system at random,
+// and hence a new NLA entry is created for each new adapter.
+func CreateAdapter(name string, tunnelType string, requestedGUID *windows.GUID) (wintun *Adapter, err error) {
+ var name16 *uint16
+ name16, err = windows.UTF16PtrFromString(name)
+ if err != nil {
+ return
+ }
+ var tunnelType16 *uint16
+ tunnelType16, err = windows.UTF16PtrFromString(tunnelType)
+ if err != nil {
+ return
+ }
+ r0, _, e1 := syscall.Syscall(procWintunCreateAdapter.Addr(), 3, uintptr(unsafe.Pointer(name16)), uintptr(unsafe.Pointer(tunnelType16)), uintptr(unsafe.Pointer(requestedGUID)))
+ if r0 == 0 {
+ err = e1
+ return
+ }
+ wintun = &Adapter{handle: r0}
+ runtime.SetFinalizer(wintun, closeAdapter)
+ return
+}
+
+// OpenAdapter opens an existing Wintun adapter by name.
+func OpenAdapter(name string) (wintun *Adapter, err error) {
+ var name16 *uint16
+ name16, err = windows.UTF16PtrFromString(name)
+ if err != nil {
+ return
+ }
+ r0, _, e1 := syscall.Syscall(procWintunOpenAdapter.Addr(), 1, uintptr(unsafe.Pointer(name16)), 0, 0)
+ if r0 == 0 {
+ err = e1
+ return
+ }
+ wintun = &Adapter{handle: r0}
+ runtime.SetFinalizer(wintun, closeAdapter)
+ return
+}
+
+// Close closes a Wintun adapter.
+func (wintun *Adapter) Close() (err error) {
+ runtime.SetFinalizer(wintun, nil)
+ r1, _, e1 := syscall.Syscall(procWintunCloseAdapter.Addr(), 1, wintun.handle, 0, 0)
+ if r1 == 0 {
+ err = e1
+ }
+ return
+}
+
+// Uninstall removes the driver from the system if no drivers are currently in use.
+func Uninstall() (err error) {
+ r1, _, e1 := syscall.Syscall(procWintunDeleteDriver.Addr(), 0, 0, 0, 0)
+ if r1 == 0 {
+ err = e1
+ }
+ return
+}
+
+// RunningVersion returns the version of the loaded driver.
+func RunningVersion() (version uint32, err error) {
+ r0, _, e1 := syscall.Syscall(procWintunGetRunningDriverVersion.Addr(), 0, 0, 0, 0)
+ version = uint32(r0)
+ if version == 0 {
+ err = e1
+ }
+ return
+}
+
+// LUID returns the LUID of the adapter.
+func (wintun *Adapter) LUID() (luid uint64) {
+ syscall.Syscall(procWintunGetAdapterLUID.Addr(), 2, uintptr(wintun.handle), uintptr(unsafe.Pointer(&luid)), 0)
+ return
+}