Ground-Zerro / Phobos Public
Code Issues Pull requests Actions Releases View on GitHub ↗
4.4 KB go
/* SPDX-License-Identifier: MIT
 *
 * Copyright (C) 2017-2026 WireGuard LLC. All Rights Reserved.
 * Phobos
 */

package wintun

import (
	"errors"
	"runtime"
	"syscall"
	"unsafe"

	"golang.org/x/sys/windows"
	"golang.zx2c4.com/wireguard/windows/dllloader"
	"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
)

const (
	RingCapacityMin = 0x20000
	RingCapacityMax = 0x4000000
	PacketSizeMax   = 0xffff
)

var (
	modwintun                         = dllloader.New("wintun.dll", nil)
	procWintunCreateAdapter           = modwintun.NewProc("WintunCreateAdapter")
	procWintunCloseAdapter            = modwintun.NewProc("WintunCloseAdapter")
	procWintunDeleteDriver            = modwintun.NewProc("WintunDeleteDriver")
	procWintunGetAdapterLUID          = modwintun.NewProc("WintunGetAdapterLUID")
	procWintunGetRunningDriverVersion = modwintun.NewProc("WintunGetRunningDriverVersion")
	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")
)

var ErrNoMorePackets = errors.New("wintun: no more packets are available")

type Adapter struct {
	handle uintptr
}

type Session struct {
	handle uintptr
}

func CreateAdapter(name, tunnelType string, requestedGUID *windows.GUID) (*Adapter, error) {
	name16, err := windows.UTF16PtrFromString(name)
	if err != nil {
		return nil, err
	}
	tunnelType16, err := windows.UTF16PtrFromString(tunnelType)
	if err != nil {
		return nil, err
	}
	handle, _, lastError := syscall.SyscallN(procWintunCreateAdapter.Addr(),
		uintptr(unsafe.Pointer(name16)), uintptr(unsafe.Pointer(tunnelType16)), uintptr(unsafe.Pointer(requestedGUID)))
	if handle == 0 {
		return nil, lastError
	}
	return &Adapter{handle: handle}, nil
}

func (a *Adapter) Close() error {
	if a.handle == 0 {
		return nil
	}
	_, _, lastError := syscall.SyscallN(procWintunCloseAdapter.Addr(), a.handle)
	a.handle = 0
	if lastError != windows.ERROR_SUCCESS {
		return lastError
	}
	return nil
}

func Uninstall() error {
	result, _, lastError := syscall.SyscallN(procWintunDeleteDriver.Addr())
	if result == 0 {
		return lastError
	}
	return nil
}

func RunningVersion() (uint32, error) {
	version, _, lastError := syscall.SyscallN(procWintunGetRunningDriverVersion.Addr())
	if version == 0 {
		return 0, lastError
	}
	return uint32(version), nil
}

func (a *Adapter) LUID() winipcfg.LUID {
	var luid uint64
	syscall.SyscallN(procWintunGetAdapterLUID.Addr(), a.handle, uintptr(unsafe.Pointer(&luid)))
	return winipcfg.LUID(luid)
}

func (a *Adapter) StartSession(capacity uint32) (*Session, error) {
	handle, _, lastError := syscall.SyscallN(procWintunStartSession.Addr(), a.handle, uintptr(capacity))
	if handle == 0 {
		return nil, lastError
	}
	return &Session{handle: handle}, nil
}

func (s *Session) End() {
	if s.handle == 0 {
		return
	}
	syscall.SyscallN(procWintunEndSession.Addr(), s.handle)
	s.handle = 0
}

func (s *Session) ReadWaitEvent() windows.Handle {
	handle, _, _ := syscall.SyscallN(procWintunGetReadWaitEvent.Addr(), s.handle)
	return windows.Handle(handle)
}

func (s *Session) ReceivePacket() ([]byte, error) {
	var size uint32
	packet, _, lastError := syscall.SyscallN(procWintunReceivePacket.Addr(), s.handle, uintptr(unsafe.Pointer(&size)))
	if packet == 0 {
		if lastError == windows.ERROR_NO_MORE_ITEMS {
			return nil, ErrNoMorePackets
		}
		return nil, lastError
	}
	return unsafe.Slice((*byte)(unsafe.Pointer(packet)), size), nil
}

func (s *Session) ReleaseReceivePacket(packet []byte) {
	syscall.SyscallN(procWintunReleaseReceivePacket.Addr(), s.handle, uintptr(unsafe.Pointer(unsafe.SliceData(packet))))
	runtime.KeepAlive(packet)
}

func (s *Session) AllocateSendPacket(size int) ([]byte, error) {
	packet, _, lastError := syscall.SyscallN(procWintunAllocateSendPacket.Addr(), s.handle, uintptr(size))
	if packet == 0 {
		return nil, lastError
	}
	return unsafe.Slice((*byte)(unsafe.Pointer(packet)), size), nil
}

func (s *Session) SendPacket(packet []byte) {
	syscall.SyscallN(procWintunSendPacket.Addr(), s.handle, uintptr(unsafe.Pointer(unsafe.SliceData(packet))))
	runtime.KeepAlive(packet)
}