修正 Win32 封装语义并补齐关键结构体/进程测试覆盖

- 修正 WTS 会话相关类型、枚举与活动会话选择逻辑
- 对齐 FILE_ID_DESCRIPTOR 布局与 FILE_ID_TYPE 语义,修复 OpenFileById 调用前提
- 修正 user32/shell32/kernel32 部分 API 的返回值、参数个数与错误处理
- 完善剪贴板更新格式读取的缓冲区重试逻辑
- 补充常用进程、线程、调试、桌面与会话 helper
- 增加结构体布局、会话查询、剪贴板、CreateProcess 等回归测试
- 将默认 CreateProcess 相关测试切到 helper 进程,并保留显式开启的 cmd.exe 集成覆盖
This commit is contained in:
兔子 2026-06-06 17:46:02 +08:00
parent 03b79c4247
commit 0f82ba044b
Signed by: b612
GPG Key ID: 99DD2222B612B612
23 changed files with 6087 additions and 307 deletions

View File

@ -1,7 +1,6 @@
package win32api
import (
"errors"
"syscall"
"unsafe"
@ -12,16 +11,11 @@ func DuplicateTokenEx(hExistingToken HANDLE, dwDesiredAccess DWORD,
lpTokenAttributes uintptr, ImpersonationLevel int,
TokenType TOKEN_TYPE, phNewToken *TOKEN) error {
advapi32, err := syscall.LoadLibrary("advapi32.dll")
Dup, err := getProcAddr("advapi32.dll", "DuplicateTokenEx")
if err != nil {
return errors.New("Can't Load Advapi32 API")
return err
}
defer syscall.FreeLibrary(advapi32)
Dup, err := syscall.GetProcAddress(syscall.Handle(advapi32), "DuplicateTokenEx")
if err != nil {
return errors.New("Can't Load WTSQueryUserToken API")
}
r, _, errno := syscall.Syscall6(uintptr(Dup), 6, uintptr(hExistingToken), uintptr(dwDesiredAccess), lpTokenAttributes, uintptr(ImpersonationLevel),
r, _, errno := syscall.Syscall6(Dup, 6, uintptr(hExistingToken), uintptr(dwDesiredAccess), lpTokenAttributes, uintptr(ImpersonationLevel),
uintptr(TokenType), uintptr(unsafe.Pointer(phNewToken)))
if r == 0 {
return error(errno)
@ -31,20 +25,19 @@ func DuplicateTokenEx(hExistingToken HANDLE, dwDesiredAccess DWORD,
func CreateProcessAsUser(hToken TOKEN, lpApplicationName, lpCommandLine string,
lpProcessAttributes, lpThreadAttributes, bInheritHandles uintptr,
dwCreationFlags uint16, lpEnvironment HANDLE, lpCurrentDirectory string,
dwCreationFlags DWORD, lpEnvironment HANDLE, lpCurrentDirectory string,
lpStartupInfo *StartupInfo, lpProcessInformation *ProcessInformation) error {
var (
commandLine uintptr = 0
workingDir uintptr = 0
applicationName uintptr
commandLine uintptr
workingDir uintptr
)
advapi32, err := syscall.LoadLibrary("advapi32.dll")
CPAU, err := getProcAddr("advapi32.dll", "CreateProcessAsUserW")
if err != nil {
return errors.New("Can't Load Advapi32 API")
return err
}
defer syscall.FreeLibrary(advapi32)
CPAU, err := syscall.GetProcAddress(syscall.Handle(advapi32), "CreateProcessAsUserW")
if err != nil {
return errors.New("Can't Load CreateProcessAsUserW API")
if len(lpApplicationName) > 0 {
applicationName = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(lpApplicationName)))
}
if len(lpCommandLine) > 0 {
commandLine = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(lpCommandLine)))
@ -52,7 +45,7 @@ func CreateProcessAsUser(hToken TOKEN, lpApplicationName, lpCommandLine string,
if len(lpCurrentDirectory) > 0 {
workingDir = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(lpCurrentDirectory)))
}
r, _, errno := syscall.Syscall12(uintptr(CPAU), 11, uintptr(hToken), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(lpApplicationName))),
r, _, errno := syscall.Syscall12(CPAU, 11, uintptr(hToken), applicationName,
commandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, uintptr(dwCreationFlags), uintptr(lpEnvironment),
workingDir, uintptr(unsafe.Pointer(lpStartupInfo)), uintptr(unsafe.Pointer(lpProcessInformation)), 0)
if r == 0 {
@ -62,18 +55,241 @@ func CreateProcessAsUser(hToken TOKEN, lpApplicationName, lpCommandLine string,
}
func GetTokenInformation(TokenHandle HANDLE, TokenInformationClass, TokenInformation,
TokenInformationLength uintptr, ReturnLength *uintptr) error {
advapi32, err := syscall.LoadLibrary("advapi32.dll")
GTI, err := getProcAddr("advapi32.dll", "GetTokenInformation")
if err != nil {
return errors.New("Can't Load Advapi32 API")
return err
}
defer syscall.FreeLibrary(advapi32)
GTI, err := syscall.GetProcAddress(syscall.Handle(advapi32), "GetTokenInformation")
if err != nil {
return errors.New("Can't Load GetTokenInformation API")
}
if r, _, errno := syscall.Syscall6(uintptr(GTI), 5, uintptr(TokenHandle), TokenInformationClass,
if r, _, errno := syscall.Syscall6(GTI, 5, uintptr(TokenHandle), TokenInformationClass,
TokenInformation, TokenInformationLength, uintptr(unsafe.Pointer(ReturnLength)), 0); r == 0 {
return error(errno)
}
return nil
}
func GetUserName() (string, error) {
gun, err := getProcAddr("advapi32.dll", "GetUserNameW")
if err != nil {
return "", err
}
size := uint32(64)
for {
buf := make([]uint16, size)
n := uint32(len(buf))
r, _, errno := syscall.Syscall(gun, 2, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0)
if r != 0 {
return syscall.UTF16ToString(buf), nil
}
if errno == syscall.ERROR_INSUFFICIENT_BUFFER {
if n > size {
size = n
} else {
size *= 2
}
continue
}
if errno != 0 {
return "", error(errno)
}
return "", syscall.EINVAL
}
}
func OpenProcessToken(ProcessHandle HANDLE, DesiredAccess DWORD, TokenHandle *TOKEN) error {
if TokenHandle == nil {
return syscall.EINVAL
}
proc, err := getProcAddr("advapi32.dll", "OpenProcessToken")
if err != nil {
return err
}
r, _, errno := syscall.Syscall(proc, 3, uintptr(ProcessHandle), uintptr(DesiredAccess), uintptr(unsafe.Pointer(TokenHandle)))
if r == 0 {
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
return nil
}
func CheckTokenMembership(tokenHandle HANDLE, sidToCheck unsafe.Pointer) (bool, error) {
if sidToCheck == nil {
return false, syscall.EINVAL
}
proc, err := getProcAddr("advapi32.dll", "CheckTokenMembership")
if err != nil {
return false, err
}
var isMember int32
r, _, errno := syscall.Syscall(proc, 3,
uintptr(tokenHandle),
uintptr(sidToCheck),
uintptr(unsafe.Pointer(&isMember)),
)
if r == 0 {
if errno != 0 {
return false, error(errno)
}
return false, syscall.EINVAL
}
return isMember != 0, nil
}
func LookupPrivilegeValue(lpSystemName, lpName string, lpLuid *LUID) error {
if lpLuid == nil {
return syscall.EINVAL
}
proc, err := getProcAddr("advapi32.dll", "LookupPrivilegeValueW")
if err != nil {
return err
}
var systemNamePtr uintptr
if len(lpSystemName) > 0 {
systemNamePtr = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(lpSystemName)))
}
r, _, errno := syscall.Syscall(proc, 3,
systemNamePtr,
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(lpName))),
uintptr(unsafe.Pointer(lpLuid)),
)
if r == 0 {
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
return nil
}
func AdjustTokenPrivileges(TokenHandle TOKEN, DisableAllPrivileges bool, NewState *TOKEN_PRIVILEGES, BufferLength DWORD,
PreviousState *TOKEN_PRIVILEGES, ReturnLength *DWORD) error {
proc, err := getProcAddr("advapi32.dll", "AdjustTokenPrivileges")
if err != nil {
return err
}
disableAll := uintptr(0)
if DisableAllPrivileges {
disableAll = 1
}
r, _, errno := syscall.Syscall6(proc, 6,
uintptr(TokenHandle),
disableAll,
uintptr(unsafe.Pointer(NewState)),
uintptr(BufferLength),
uintptr(unsafe.Pointer(PreviousState)),
uintptr(unsafe.Pointer(ReturnLength)),
)
if r == 0 {
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
if errno == windows.ERROR_NOT_ALL_ASSIGNED {
return error(errno)
}
return nil
}
func RevertToSelf() error {
proc, err := getProcAddr("advapi32.dll", "RevertToSelf")
if err != nil {
return err
}
r, _, errno := syscall.Syscall(proc, 0, 0, 0, 0)
if r == 0 {
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
return nil
}
func CreateProcessWithToken(hToken TOKEN, dwLogonFlags DWORD, lpApplicationName, lpCommandLine string,
dwCreationFlags DWORD, lpEnvironment HANDLE, lpCurrentDirectory string,
lpStartupInfo *StartupInfo, lpProcessInformation *ProcessInformation) error {
var (
applicationName uintptr
commandLine uintptr
currentDir uintptr
)
if len(lpApplicationName) > 0 {
applicationName = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(lpApplicationName)))
}
if len(lpCommandLine) > 0 {
commandLine = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(lpCommandLine)))
}
if len(lpCurrentDirectory) > 0 {
currentDir = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(lpCurrentDirectory)))
}
proc, err := getProcAddr("advapi32.dll", "CreateProcessWithTokenW")
if err != nil {
return err
}
r, _, errno := syscall.Syscall12(proc, 9,
uintptr(hToken),
uintptr(dwLogonFlags),
applicationName,
commandLine,
uintptr(dwCreationFlags),
uintptr(lpEnvironment),
currentDir,
uintptr(unsafe.Pointer(lpStartupInfo)),
uintptr(unsafe.Pointer(lpProcessInformation)),
0,
0,
0,
)
if r == 0 {
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
return nil
}
func IsTokenElevated(token TOKEN) (bool, error) {
var elevation TOKEN_ELEVATION
var retLen uintptr
if err := GetTokenInformation(
HANDLE(token),
TokenElevation,
uintptr(unsafe.Pointer(&elevation)),
uintptr(unsafe.Sizeof(elevation)),
&retLen,
); err != nil {
return false, err
}
return elevation.TokenIsElevated != 0, nil
}
func IsCurrentProcessElevated() (bool, error) {
processHandle, err := syscall.GetCurrentProcess()
if err != nil {
return false, err
}
var token TOKEN
if err := OpenProcessToken(HANDLE(processHandle), TOKEN_QUERY, &token); err != nil {
return false, err
}
defer func() {
_ = CloseHandle(HANDLE(token))
}()
return IsTokenElevated(token)
}
func IsCurrentUserInAdminGroup() (bool, error) {
adminSID, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
if err != nil {
return false, err
}
// Passing tokenHandle=0 lets Windows use the calling thread/process effective token.
return CheckTokenMembership(0, unsafe.Pointer(adminSID))
}

View File

@ -3,3 +3,53 @@ package win32api
type TOKEN_LINKED_TOKEN struct {
LinkedToken TOKEN
}
const (
TOKEN_ASSIGN_PRIMARY DWORD = 0x0001
TOKEN_DUPLICATE DWORD = 0x0002
TOKEN_IMPERSONATE DWORD = 0x0004
TOKEN_QUERY DWORD = 0x0008
TOKEN_QUERY_SOURCE DWORD = 0x0010
TOKEN_ADJUST_PRIVILEGES DWORD = 0x0020
TOKEN_ADJUST_GROUPS DWORD = 0x0040
TOKEN_ADJUST_DEFAULT DWORD = 0x0080
TOKEN_ADJUST_SESSIONID DWORD = 0x0100
TOKEN_ALL_ACCESS DWORD = 0xF01FF
)
const (
SE_PRIVILEGE_ENABLED DWORD = 0x00000002
)
const (
LOGON_WITH_PROFILE DWORD = 0x00000001
LOGON_NETCREDENTIALS_ONLY DWORD = 0x00000002
)
const (
SE_DEBUG_NAME = "SeDebugPrivilege"
SE_CHANGE_NOTIFY_NAME = "SeChangeNotifyPrivilege"
)
const (
TokenElevation uintptr = 20
)
type LUID struct {
LowPart DWORD
HighPart int32
}
type LUID_AND_ATTRIBUTES struct {
Luid LUID
Attributes DWORD
}
type TOKEN_PRIVILEGES struct {
PrivilegeCount DWORD
Privileges [1]LUID_AND_ATTRIBUTES
}
type TOKEN_ELEVATION struct {
TokenIsElevated DWORD
}

1478
common_api_test.go Normal file

File diff suppressed because it is too large Load Diff

11
doc.go Normal file
View File

@ -0,0 +1,11 @@
// Package win32api provides thin Win32 API wrappers for Go on Windows.
//
// The package keeps the exported shape close to the native APIs:
// strings are accepted as Go strings where practical, handles and structs are
// kept explicit, and higher-level helpers are added only for common workflows
// such as session, adapter, process, thread, and module enumeration.
//
// Current coverage focuses on the parts used by the surrounding projects:
// process and token control, file and memory operations, sessions and desktop
// access, window helpers, socket/network helpers, and basic debug workflows.
package win32api

350
iphlpapi.go Normal file
View File

@ -0,0 +1,350 @@
package win32api
import (
"fmt"
"strings"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
func GetAdaptersAddresses(family, flags uint32) ([]AdapterAddressInfo, error) {
size := uint32(15 * 1024)
for i := 0; i < 4; i++ {
buf := make([]byte, size)
head := (*windows.IpAdapterAddresses)(unsafe.Pointer(&buf[0]))
err := windows.GetAdaptersAddresses(family, flags, 0, head, &size)
if err == nil {
return collectAdapterAddressInfo(head), nil
}
if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERROR_BUFFER_OVERFLOW {
continue
}
return nil, err
}
return nil, fmt.Errorf("GetAdaptersAddresses exceeded retry limit")
}
func GetIfTable2() ([]MIB_IF_ROW2, error) {
proc, err := getProcAddr("iphlpapi.dll", "GetIfTable2")
if err != nil {
return nil, err
}
var table *MIB_IF_TABLE2
r, _, _ := syscall.Syscall(proc, 1, uintptr(unsafe.Pointer(&table)), 0, 0)
if r != 0 {
return nil, syscall.Errno(r)
}
if table == nil {
return nil, fmt.Errorf("GetIfTable2 returned nil table")
}
defer FreeMibTable(unsafe.Pointer(table))
count := int(table.NumEntries)
rows := make([]MIB_IF_ROW2, 0, count)
rowSize := unsafe.Sizeof(table.Table[0])
base := uintptr(unsafe.Pointer(&table.Table[0]))
for i := 0; i < count; i++ {
row := (*MIB_IF_ROW2)(unsafe.Pointer(base + uintptr(i)*rowSize))
rows = append(rows, *row)
}
return rows, nil
}
func GetIfEntry2(row *MIB_IF_ROW2) error {
if row == nil {
return syscall.EINVAL
}
proc, err := getProcAddr("iphlpapi.dll", "GetIfEntry2")
if err != nil {
return err
}
r, _, _ := syscall.Syscall(proc, 1, uintptr(unsafe.Pointer(row)), 0, 0)
if r != 0 {
return syscall.Errno(r)
}
return nil
}
func FreeMibTable(memory unsafe.Pointer) {
if memory == nil {
return
}
proc, err := getProcAddr("iphlpapi.dll", "FreeMibTable")
if err != nil {
return
}
syscall.Syscall(proc, 1, uintptr(memory), 0, 0)
}
func GetExtendedTcp4Table(order bool, tableClass TCP_TABLE_CLASS) ([]MIB_TCPROW_OWNER_PID, error) {
if err := validateTCPOwnerPIDTableClass(tableClass); err != nil {
return nil, err
}
buf, err := getExtendedTCPTable(order, AF_INET, tableClass)
if err != nil {
return nil, err
}
if len(buf) == 0 {
return nil, nil
}
table := (*MIB_TCPTABLE_OWNER_PID)(unsafe.Pointer(&buf[0]))
count := int(table.NumEntries)
rows := make([]MIB_TCPROW_OWNER_PID, 0, count)
rowSize := unsafe.Sizeof(table.Table[0])
base := uintptr(unsafe.Pointer(&table.Table[0]))
for i := 0; i < count; i++ {
row := (*MIB_TCPROW_OWNER_PID)(unsafe.Pointer(base + uintptr(i)*rowSize))
rows = append(rows, *row)
}
return rows, nil
}
func GetExtendedTcp6Table(order bool, tableClass TCP_TABLE_CLASS) ([]MIB_TCP6ROW_OWNER_PID, error) {
if err := validateTCPOwnerPIDTableClass(tableClass); err != nil {
return nil, err
}
buf, err := getExtendedTCPTable(order, AF_INET6, tableClass)
if err != nil {
return nil, err
}
if len(buf) == 0 {
return nil, nil
}
table := (*MIB_TCP6TABLE_OWNER_PID)(unsafe.Pointer(&buf[0]))
count := int(table.NumEntries)
rows := make([]MIB_TCP6ROW_OWNER_PID, 0, count)
rowSize := unsafe.Sizeof(table.Table[0])
base := uintptr(unsafe.Pointer(&table.Table[0]))
for i := 0; i < count; i++ {
row := (*MIB_TCP6ROW_OWNER_PID)(unsafe.Pointer(base + uintptr(i)*rowSize))
rows = append(rows, *row)
}
return rows, nil
}
func GetExtendedUdp4Table(order bool, tableClass UDP_TABLE_CLASS) ([]MIB_UDPROW_OWNER_PID, error) {
if err := validateUDPOwnerPIDTableClass(tableClass); err != nil {
return nil, err
}
buf, err := getExtendedUDPTable(order, AF_INET, tableClass)
if err != nil {
return nil, err
}
if len(buf) == 0 {
return nil, nil
}
table := (*MIB_UDPTABLE_OWNER_PID)(unsafe.Pointer(&buf[0]))
count := int(table.NumEntries)
rows := make([]MIB_UDPROW_OWNER_PID, 0, count)
rowSize := unsafe.Sizeof(table.Table[0])
base := uintptr(unsafe.Pointer(&table.Table[0]))
for i := 0; i < count; i++ {
row := (*MIB_UDPROW_OWNER_PID)(unsafe.Pointer(base + uintptr(i)*rowSize))
rows = append(rows, *row)
}
return rows, nil
}
func GetExtendedUdp6Table(order bool, tableClass UDP_TABLE_CLASS) ([]MIB_UDP6ROW_OWNER_PID, error) {
if err := validateUDPOwnerPIDTableClass(tableClass); err != nil {
return nil, err
}
buf, err := getExtendedUDPTable(order, AF_INET6, tableClass)
if err != nil {
return nil, err
}
if len(buf) == 0 {
return nil, nil
}
table := (*MIB_UDP6TABLE_OWNER_PID)(unsafe.Pointer(&buf[0]))
count := int(table.NumEntries)
rows := make([]MIB_UDP6ROW_OWNER_PID, 0, count)
rowSize := unsafe.Sizeof(table.Table[0])
base := uintptr(unsafe.Pointer(&table.Table[0]))
for i := 0; i < count; i++ {
row := (*MIB_UDP6ROW_OWNER_PID)(unsafe.Pointer(base + uintptr(i)*rowSize))
rows = append(rows, *row)
}
return rows, nil
}
func (row MIB_TCPROW_OWNER_PID) LocalPortHost() uint16 {
return Ntohs(uint16(row.LocalPort))
}
func (row MIB_TCPROW_OWNER_PID) RemotePortHost() uint16 {
return Ntohs(uint16(row.RemotePort))
}
func (row MIB_TCP6ROW_OWNER_PID) LocalPortHost() uint16 {
return Ntohs(uint16(row.LocalPort))
}
func (row MIB_TCP6ROW_OWNER_PID) RemotePortHost() uint16 {
return Ntohs(uint16(row.RemotePort))
}
func (row MIB_UDPROW_OWNER_PID) LocalPortHost() uint16 {
return Ntohs(uint16(row.LocalPort))
}
func (row MIB_UDP6ROW_OWNER_PID) LocalPortHost() uint16 {
return Ntohs(uint16(row.LocalPort))
}
func validateTCPOwnerPIDTableClass(tableClass TCP_TABLE_CLASS) error {
switch tableClass {
case TCP_TABLE_OWNER_PID_LISTENER, TCP_TABLE_OWNER_PID_CONNECTIONS, TCP_TABLE_OWNER_PID_ALL:
return nil
default:
return fmt.Errorf("unsupported TCP owner-pid table class: %d", tableClass)
}
}
func validateUDPOwnerPIDTableClass(tableClass UDP_TABLE_CLASS) error {
if tableClass == UDP_TABLE_OWNER_PID {
return nil
}
return fmt.Errorf("unsupported UDP owner-pid table class: %d", tableClass)
}
func getExtendedTCPTable(order bool, family int, tableClass TCP_TABLE_CLASS) ([]byte, error) {
proc, err := getProcAddr("iphlpapi.dll", "GetExtendedTcpTable")
if err != nil {
return nil, err
}
return getExtendedIPTable(proc, order, family, uint32(tableClass))
}
func getExtendedUDPTable(order bool, family int, tableClass UDP_TABLE_CLASS) ([]byte, error) {
proc, err := getProcAddr("iphlpapi.dll", "GetExtendedUdpTable")
if err != nil {
return nil, err
}
return getExtendedIPTable(proc, order, family, uint32(tableClass))
}
func getExtendedIPTable(proc uintptr, order bool, family int, tableClass uint32) ([]byte, error) {
size := uint32(0)
r, _, _ := syscall.Syscall6(proc, 6,
0,
uintptr(unsafe.Pointer(&size)),
boolToUintptr(order),
uintptr(family),
uintptr(tableClass),
0,
)
if r != 0 && syscall.Errno(r) != syscall.ERROR_INSUFFICIENT_BUFFER {
return nil, syscall.Errno(r)
}
if size == 0 {
return nil, nil
}
for i := 0; i < 4; i++ {
buf := make([]byte, size)
r, _, _ = syscall.Syscall6(proc, 6,
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&size)),
boolToUintptr(order),
uintptr(family),
uintptr(tableClass),
0,
)
if r == 0 {
return buf, nil
}
if syscall.Errno(r) != syscall.ERROR_INSUFFICIENT_BUFFER {
return nil, syscall.Errno(r)
}
}
return nil, fmt.Errorf("GetExtended IP table exceeded retry limit")
}
func boolToUintptr(v bool) uintptr {
if v {
return 1
}
return 0
}
func collectAdapterAddressInfo(head *windows.IpAdapterAddresses) []AdapterAddressInfo {
out := make([]AdapterAddressInfo, 0, 8)
for a := head; a != nil; a = a.Next {
item := AdapterAddressInfo{
IfIndex: a.IfIndex,
AdapterName: windows.BytePtrToString(a.AdapterName),
FriendlyName: windows.UTF16PtrToString(a.FriendlyName),
Description: windows.UTF16PtrToString(a.Description),
DNSSuffix: windows.UTF16PtrToString(a.DnsSuffix),
OperStatus: a.OperStatus,
Mtu: a.Mtu,
MACAddress: formatMACAddress(a.PhysicalAddress[:], a.PhysicalAddressLength),
PhysicalAddressLength: a.PhysicalAddressLength,
TransmitLinkSpeed: a.TransmitLinkSpeed,
ReceiveLinkSpeed: a.ReceiveLinkSpeed,
UnicastIPs: collectUnicastIPs(a.FirstUnicastAddress),
DNSServers: collectDNSServerIPs(a.FirstDnsServerAddress),
Gateways: collectGatewayIPs(a.FirstGatewayAddress),
}
out = append(out, item)
}
return out
}
func collectUnicastIPs(addr *windows.IpAdapterUnicastAddress) []string {
out := make([]string, 0, 4)
for ua := addr; ua != nil; ua = ua.Next {
ip := ua.Address.IP()
if ip == nil {
continue
}
out = append(out, ip.String())
}
return out
}
func collectDNSServerIPs(addr *windows.IpAdapterDnsServerAdapter) []string {
out := make([]string, 0, 2)
for cur := addr; cur != nil; cur = cur.Next {
ip := cur.Address.IP()
if ip == nil {
continue
}
out = append(out, ip.String())
}
return out
}
func collectGatewayIPs(addr *windows.IpAdapterGatewayAddress) []string {
out := make([]string, 0, 2)
for cur := addr; cur != nil; cur = cur.Next {
ip := cur.Address.IP()
if ip == nil {
continue
}
out = append(out, ip.String())
}
return out
}
func formatMACAddress(raw []byte, n uint32) string {
if n == 0 || len(raw) == 0 {
return ""
}
if int(n) > len(raw) {
n = uint32(len(raw))
}
var b strings.Builder
for i := 0; i < int(n); i++ {
if i > 0 {
b.WriteByte(':')
}
_, _ = fmt.Fprintf(&b, "%02X", raw[i])
}
return b.String()
}

154
iphlpapi_typedef.go Normal file
View File

@ -0,0 +1,154 @@
package win32api
const (
IF_MAX_STRING_SIZE = 256
IF_MAX_PHYS_ADDRESS_LENGTH = 32
)
type GUID struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]byte
}
type TCP_TABLE_CLASS uint32
const (
TCP_TABLE_BASIC_LISTENER TCP_TABLE_CLASS = iota
TCP_TABLE_BASIC_CONNECTIONS
TCP_TABLE_BASIC_ALL
TCP_TABLE_OWNER_PID_LISTENER
TCP_TABLE_OWNER_PID_CONNECTIONS
TCP_TABLE_OWNER_PID_ALL
TCP_TABLE_OWNER_MODULE_LISTENER
TCP_TABLE_OWNER_MODULE_CONNECTIONS
TCP_TABLE_OWNER_MODULE_ALL
)
type UDP_TABLE_CLASS uint32
const (
UDP_TABLE_BASIC UDP_TABLE_CLASS = iota
UDP_TABLE_OWNER_PID
UDP_TABLE_OWNER_MODULE
)
type MIB_TCP_STATE uint32
const (
MIB_TCP_STATE_CLOSED MIB_TCP_STATE = iota + 1
MIB_TCP_STATE_LISTEN
MIB_TCP_STATE_SYN_SENT
MIB_TCP_STATE_SYN_RCVD
MIB_TCP_STATE_ESTAB
MIB_TCP_STATE_FIN_WAIT1
MIB_TCP_STATE_FIN_WAIT2
MIB_TCP_STATE_CLOSE_WAIT
MIB_TCP_STATE_CLOSING
MIB_TCP_STATE_LAST_ACK
MIB_TCP_STATE_TIME_WAIT
MIB_TCP_STATE_DELETE_TCB
)
type MIB_IF_ROW2 struct {
InterfaceLuid uint64
InterfaceIndex uint32
InterfaceGuid GUID
Alias [IF_MAX_STRING_SIZE + 1]uint16
Description [IF_MAX_STRING_SIZE + 1]uint16
PhysicalAddressLength uint32
PhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]byte
PermanentPhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]byte
Mtu uint32
Type uint32
TunnelType uint32
MediaType uint32
PhysicalMediumType uint32
AccessType uint32
DirectionType uint32
InterfaceAndOperStatusFlags byte
OperStatus uint32
AdminStatus uint32
MediaConnectState uint32
NetworkGuid GUID
ConnectionType uint32
TransmitLinkSpeed uint64
ReceiveLinkSpeed uint64
InOctets uint64
InUcastPkts uint64
InNUcastPkts uint64
InDiscards uint64
InErrors uint64
InUnknownProtos uint64
InUcastOctets uint64
InMulticastOctets uint64
InBroadcastOctets uint64
OutOctets uint64
OutUcastPkts uint64
OutNUcastPkts uint64
OutDiscards uint64
OutErrors uint64
OutUcastOctets uint64
OutMulticastOctets uint64
OutBroadcastOctets uint64
OutQLen uint64
}
type MIB_IF_TABLE2 struct {
NumEntries uint32
Table [1]MIB_IF_ROW2
}
type MIB_TCPROW_OWNER_PID struct {
State MIB_TCP_STATE
LocalAddr uint32
LocalPort uint32
RemoteAddr uint32
RemotePort uint32
OwningPid uint32
}
type MIB_TCPTABLE_OWNER_PID struct {
NumEntries uint32
Table [1]MIB_TCPROW_OWNER_PID
}
type MIB_TCP6ROW_OWNER_PID struct {
LocalAddr [16]byte
LocalScopeId uint32
LocalPort uint32
RemoteAddr [16]byte
RemoteScopeId uint32
RemotePort uint32
State MIB_TCP_STATE
OwningPid uint32
}
type MIB_TCP6TABLE_OWNER_PID struct {
NumEntries uint32
Table [1]MIB_TCP6ROW_OWNER_PID
}
type MIB_UDPROW_OWNER_PID struct {
LocalAddr uint32
LocalPort uint32
OwningPid uint32
}
type MIB_UDPTABLE_OWNER_PID struct {
NumEntries uint32
Table [1]MIB_UDPROW_OWNER_PID
}
type MIB_UDP6ROW_OWNER_PID struct {
LocalAddr [16]byte
LocalScopeId uint32
LocalPort uint32
OwningPid uint32
}
type MIB_UDP6TABLE_OWNER_PID struct {
NumEntries uint32
Table [1]MIB_UDP6ROW_OWNER_PID
}

File diff suppressed because it is too large Load Diff

128
kernel32_helper.go Normal file
View File

@ -0,0 +1,128 @@
package win32api
import (
"bytes"
"syscall"
"unsafe"
)
func (entry PROCESSENTRY32) ExeFile() string {
n := bytes.IndexByte(entry.SzExeFile[:], 0)
if n < 0 {
n = len(entry.SzExeFile)
}
return string(entry.SzExeFile[:n])
}
func (entry MODULEENTRY32W) ModuleName() string {
return syscall.UTF16ToString(entry.SzModule[:])
}
func (entry MODULEENTRY32W) ExePath() string {
return syscall.UTF16ToString(entry.SzExePath[:])
}
func (info DebugEventInfo) String() string {
return info.CodeName
}
func EnumerateProcesses() ([]PROCESSENTRY32, error) {
snapshot, err := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
if err != nil {
return nil, err
}
defer func() {
_ = CloseHandle(snapshot)
}()
var entry PROCESSENTRY32
entry.DwSize = Ulong(unsafe.Sizeof(entry))
if err := Process32First(snapshot, &entry); err != nil {
return nil, err
}
processes := make([]PROCESSENTRY32, 0, 64)
for {
processes = append(processes, entry)
entry.DwSize = Ulong(unsafe.Sizeof(entry))
err = Process32Next(snapshot, &entry)
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == ERROR_NO_MORE_FILES {
break
}
if err == syscall.EINVAL {
break
}
return nil, err
}
}
return processes, nil
}
func EnumerateThreads(ownerProcessID DWORD) ([]THREADENTRY32, error) {
snapshot, err := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)
if err != nil {
return nil, err
}
defer func() {
_ = CloseHandle(snapshot)
}()
var entry THREADENTRY32
entry.DwSize = DWORD(unsafe.Sizeof(entry))
if err := Thread32First(snapshot, &entry); err != nil {
return nil, err
}
threads := make([]THREADENTRY32, 0, 64)
for {
if ownerProcessID == 0 || entry.Th32OwnerProcessID == ownerProcessID {
threads = append(threads, entry)
}
entry.DwSize = DWORD(unsafe.Sizeof(entry))
err = Thread32Next(snapshot, &entry)
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == ERROR_NO_MORE_FILES {
break
}
if err == syscall.EINVAL {
break
}
return nil, err
}
}
return threads, nil
}
func EnumerateModules(processID DWORD) ([]MODULEENTRY32W, error) {
snapshot, err := CreateToolhelp32Snapshot(TH32CS_SNAPMODULE|TH32CS_SNAPMODULE32, processID)
if err != nil {
return nil, err
}
defer func() {
_ = CloseHandle(snapshot)
}()
var entry MODULEENTRY32W
entry.DwSize = DWORD(unsafe.Sizeof(entry))
if err := Module32First(snapshot, &entry); err != nil {
return nil, err
}
modules := make([]MODULEENTRY32W, 0, 32)
for {
modules = append(modules, entry)
entry.DwSize = DWORD(unsafe.Sizeof(entry))
err = Module32Next(snapshot, &entry)
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == ERROR_NO_MORE_FILES {
break
}
if err == syscall.EINVAL {
break
}
return nil, err
}
}
return modules, nil
}

View File

@ -23,6 +23,96 @@ type PROCESSENTRY32 struct {
SzExeFile [260]byte
}
type THREADENTRY32 struct {
DwSize DWORD
CntUsage DWORD
Th32ThreadID DWORD
Th32OwnerProcessID DWORD
TpBasePri int32
TpDeltaPri int32
DwFlags DWORD
}
type MODULEENTRY32W struct {
DwSize DWORD
Th32ModuleID DWORD
Th32ProcessID DWORD
GlblcntUsage DWORD
ProccntUsage DWORD
ModBaseAddr uintptr
ModBaseSize DWORD
HModule HMODULE
SzModule [MAX_MODULE_NAME32 + 1]uint16
SzExePath [syscall.MAX_PATH]uint16
}
type M128A struct {
Low uint64
High int64
}
// AMD64_CONTEXT mirrors the Windows x64 CONTEXT layout closely enough for
// GetThreadContext/SetThreadContext on amd64 processes.
type AMD64_CONTEXT struct {
P1Home uint64
P2Home uint64
P3Home uint64
P4Home uint64
P5Home uint64
P6Home uint64
ContextFlags DWORD
MxCsr DWORD
SegCs WORD
SegDs WORD
SegEs WORD
SegFs WORD
SegGs WORD
SegSs WORD
EFlags DWORD
Dr0 uint64
Dr1 uint64
Dr2 uint64
Dr3 uint64
Dr6 uint64
Dr7 uint64
Rax uint64
Rcx uint64
Rdx uint64
Rbx uint64
Rsp uint64
Rbp uint64
Rsi uint64
Rdi uint64
R8 uint64
R9 uint64
R10 uint64
R11 uint64
R12 uint64
R13 uint64
R14 uint64
R15 uint64
Rip uint64
ExtendedRegisters [512]byte
VectorRegister [26]M128A
VectorControl uint64
DebugControl uint64
LastBranchToRip uint64
LastBranchFromRip uint64
LastExceptionToRip uint64
LastExceptionFromRip uint64
}
type DEBUG_EVENT_HEADER struct {
DwDebugEventCode DWORD
DwProcessId DWORD
DwThreadId DWORD
}
type DebugEventInfo struct {
Header DEBUG_EVENT_HEADER
CodeName string
}
type MEMORYSTATUSEX struct {
DwLength DWORD
DwMemoryLoad DWORD
@ -35,6 +125,16 @@ type MEMORYSTATUSEX struct {
UllAvailExtendedVirtual DWORDLONG
}
type MEMORY_BASIC_INFORMATION struct {
BaseAddress uintptr
AllocationBase uintptr
AllocationProtect DWORD
RegionSize uintptr
State DWORD
Protect DWORD
Type DWORD
}
type USN_JOURNAL_DATA struct {
UsnJournalID DWORDLONG
FirstUsn USN
@ -78,42 +178,184 @@ type MFT_ENUM_DATA struct {
}
const (
FSCTL_ENUM_USN_DATA = 0x900B3
FSCTL_QUERY_USN_JOURNAL = 0x900F4
FSCTL_READ_USN_JOURNAL = 0x900BB
O_RDONLY = syscall.O_RDONLY
O_RDWR = syscall.O_RDWR
O_CREAT = syscall.O_CREAT
O_WRONLY = syscall.O_WRONLY
GENERIC_READ = syscall.GENERIC_READ
GENERIC_WRITE = syscall.GENERIC_WRITE
FILE_APPEND_DATA = syscall.FILE_APPEND_DATA
FILE_SHARE_READ = syscall.FILE_SHARE_READ
FILE_SHARE_WRITE = syscall.FILE_SHARE_WRITE
ERROR_FILE_NOT_FOUND = syscall.ERROR_FILE_NOT_FOUND
O_APPEND = syscall.O_APPEND
O_CLOEXEC = syscall.O_CLOEXEC
O_EXCL = syscall.O_EXCL
O_TRUNC = syscall.O_TRUNC
CREATE_ALWAYS = syscall.CREATE_ALWAYS
CREATE_NEW = syscall.CREATE_NEW
OPEN_ALWAYS = syscall.OPEN_ALWAYS
TRUNCATE_EXISTING = syscall.TRUNCATE_EXISTING
OPEN_EXISTING = syscall.OPEN_EXISTING
FILE_ATTRIBUTE_NORMAL = syscall.FILE_ATTRIBUTE_NORMAL
FILE_FLAG_BACKUP_SEMANTICS = syscall.FILE_FLAG_BACKUP_SEMANTICS
FILE_ATTRIBUTE_DIRECTORY = syscall.FILE_ATTRIBUTE_DIRECTORY
MAX_LONG_PATH = syscall.MAX_LONG_PATH
TH32CS_SNAPPROCESS DWORD = 0x00000002
TH32CS_SNAPTHREAD DWORD = 0x00000004
TH32CS_SNAPMODULE DWORD = 0x00000008
TH32CS_SNAPMODULE32 DWORD = 0x00000010
FSCTL_ENUM_USN_DATA = 0x900B3
FSCTL_QUERY_USN_JOURNAL = 0x900F4
FSCTL_READ_USN_JOURNAL = 0x900BB
O_RDONLY = syscall.O_RDONLY
O_RDWR = syscall.O_RDWR
O_CREAT = syscall.O_CREAT
O_WRONLY = syscall.O_WRONLY
GENERIC_READ = syscall.GENERIC_READ
GENERIC_WRITE = syscall.GENERIC_WRITE
FILE_APPEND_DATA = syscall.FILE_APPEND_DATA
FILE_SHARE_READ = syscall.FILE_SHARE_READ
FILE_SHARE_WRITE = syscall.FILE_SHARE_WRITE
ERROR_NO_MORE_FILES = syscall.ERROR_NO_MORE_FILES
ERROR_FILE_NOT_FOUND = syscall.ERROR_FILE_NOT_FOUND
O_APPEND = syscall.O_APPEND
O_CLOEXEC = syscall.O_CLOEXEC
O_EXCL = syscall.O_EXCL
O_TRUNC = syscall.O_TRUNC
CREATE_ALWAYS = syscall.CREATE_ALWAYS
CREATE_NEW = syscall.CREATE_NEW
OPEN_ALWAYS = syscall.OPEN_ALWAYS
TRUNCATE_EXISTING = syscall.TRUNCATE_EXISTING
OPEN_EXISTING = syscall.OPEN_EXISTING
FILE_ATTRIBUTE_NORMAL = syscall.FILE_ATTRIBUTE_NORMAL
FILE_FLAG_BACKUP_SEMANTICS = syscall.FILE_FLAG_BACKUP_SEMANTICS
FILE_ATTRIBUTE_DIRECTORY = syscall.FILE_ATTRIBUTE_DIRECTORY
MAX_LONG_PATH = syscall.MAX_LONG_PATH
)
const (
MAX_MODULE_NAME32 = 255
)
const (
PROCESS_CREATE_THREAD DWORD = 0x0002
PROCESS_TERMINATE DWORD = 0x0001
PROCESS_VM_OPERATION DWORD = 0x0008
PROCESS_VM_READ DWORD = 0x0010
PROCESS_VM_WRITE DWORD = 0x0020
PROCESS_QUERY_INFORMATION DWORD = 0x0400
PROCESS_QUERY_LIMITED_INFORMATION DWORD = 0x1000
PROCESS_SUSPEND_RESUME DWORD = 0x0800
SYNCHRONIZE DWORD = 0x00100000
PROCESS_NAME_NATIVE DWORD = 0x00000001
)
const (
THREAD_TERMINATE DWORD = 0x0001
THREAD_SUSPEND_RESUME DWORD = 0x0002
THREAD_GET_CONTEXT DWORD = 0x0008
THREAD_SET_CONTEXT DWORD = 0x0010
THREAD_QUERY_INFORMATION DWORD = 0x0040
THREAD_SET_INFORMATION DWORD = 0x0020
THREAD_QUERY_LIMITED_INFO DWORD = 0x0800
THREAD_SET_LIMITED_INFO DWORD = 0x0400
)
const (
CONTEXT_AMD64 DWORD = 0x00100000
CONTEXT_CONTROL DWORD = CONTEXT_AMD64 | 0x00000001
CONTEXT_INTEGER DWORD = CONTEXT_AMD64 | 0x00000002
CONTEXT_SEGMENTS DWORD = CONTEXT_AMD64 | 0x00000004
CONTEXT_FLOATING_POINT DWORD = CONTEXT_AMD64 | 0x00000008
CONTEXT_DEBUG_REGISTERS DWORD = CONTEXT_AMD64 | 0x00000010
CONTEXT_FULL DWORD = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT
CONTEXT_ALL DWORD = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS
)
const (
PAGE_NOACCESS DWORD = 0x01
PAGE_READONLY DWORD = 0x02
PAGE_READWRITE DWORD = 0x04
PAGE_WRITECOPY DWORD = 0x08
PAGE_EXECUTE DWORD = 0x10
PAGE_EXECUTE_READ DWORD = 0x20
PAGE_EXECUTE_READWRITE DWORD = 0x40
PAGE_EXECUTE_WRITECOPY DWORD = 0x80
PAGE_GUARD DWORD = 0x100
PAGE_NOCACHE DWORD = 0x200
PAGE_WRITECOMBINE DWORD = 0x400
)
const (
MEM_COMMIT DWORD = 0x00001000
MEM_RESERVE DWORD = 0x00002000
MEM_DECOMMIT DWORD = 0x00004000
MEM_RELEASE DWORD = 0x00008000
MEM_FREE DWORD = 0x00010000
MEM_PRIVATE DWORD = 0x00020000
MEM_MAPPED DWORD = 0x00040000
MEM_TOP_DOWN DWORD = 0x00100000
MEM_WRITE_WATCH DWORD = 0x00200000
MEM_PHYSICAL DWORD = 0x00400000
MEM_RESET DWORD = 0x00080000
MEM_RESET_UNDO DWORD = 0x01000000
MEM_LARGE_PAGES DWORD = 0x20000000
MEM_IMAGE DWORD = 0x01000000
)
const (
WAIT_OBJECT_0 DWORD = 0x00000000
WAIT_ABANDONED = 0x00000080
WAIT_TIMEOUT = 0x00000102
WAIT_FAILED = 0xFFFFFFFF
INFINITE = 0xFFFFFFFF
)
const (
STILL_ACTIVE DWORD = 259
INVALID_FILE_ATTRIBUTES DWORD = 0xFFFFFFFF
)
const (
MAXIMUM_WAIT_OBJECTS DWORD = 64
)
const (
MOVEFILE_REPLACE_EXISTING DWORD = 0x00000001
MOVEFILE_COPY_ALLOWED DWORD = 0x00000002
MOVEFILE_DELAY_UNTIL_REBOOT DWORD = 0x00000004
MOVEFILE_WRITE_THROUGH DWORD = 0x00000008
MOVEFILE_CREATE_HARDLINK DWORD = 0x00000010
MOVEFILE_FAIL_IF_NOT_TRACKABLE DWORD = 0x00000020
)
type FILE_ID_DESCRIPTOR struct {
DwSize DWORD
Type DWORD
FileId DWORDLONG
ObjectId DWORDLONG
ExtendedFileId DWORDLONG
DwSize DWORD
Type FILE_ID_TYPE
FileId DWORDLONG
_ [8]byte
}
type FILE_ID_TYPE DWORD
const (
FileIdType FILE_ID_TYPE = iota
ObjectIdType
ExtendedFileIdType
MaximumFileIdType
)
const (
FORMAT_MESSAGE_ALLOCATE_BUFFER DWORD = 0x00000100
FORMAT_MESSAGE_IGNORE_INSERTS DWORD = 0x00000200
FORMAT_MESSAGE_FROM_SYSTEM DWORD = 0x00001000
)
const (
FILE_SHARE_DELETE = syscall.FILE_SHARE_DELETE
)
const (
CREATE_SUSPENDED DWORD = 0x00000004
DEBUG_PROCESS DWORD = 0x00000001
DEBUG_ONLY_THIS_PROCESS DWORD = 0x00000002
)
const (
EXCEPTION_DEBUG_EVENT DWORD = 1
CREATE_THREAD_DEBUG_EVENT DWORD = 2
CREATE_PROCESS_DEBUG_EVENT DWORD = 3
EXIT_THREAD_DEBUG_EVENT DWORD = 4
EXIT_PROCESS_DEBUG_EVENT DWORD = 5
LOAD_DLL_DEBUG_EVENT DWORD = 6
UNLOAD_DLL_DEBUG_EVENT DWORD = 7
OUTPUT_DEBUG_STRING_EVENT DWORD = 8
RIP_EVENT DWORD = 9
)
const (
DBG_CONTINUE DWORD = 0x00010002
DBG_EXCEPTION_NOT_HANDLED DWORD = 0x80010001
)
const (
GMEM_MOVEABLE = 0x0002
GMEM_ZEROINIT = 0x0040

836
network_api_test.go Normal file
View File

@ -0,0 +1,836 @@
//go:build windows
package win32api
import (
"fmt"
"io"
"net"
"os"
"runtime"
"strings"
"testing"
"time"
"unsafe"
)
func TestGetHostName(t *testing.T) {
host, err := GetHostName()
if err != nil {
t.Fatalf("GetHostName failed: %v", err)
}
host = strings.TrimSpace(host)
if host == "" {
t.Fatal("GetHostName returned empty string")
}
}
func TestInetPtonNtopIPv4(t *testing.T) {
raw, err := InetPton(AF_INET, "127.0.0.1")
if err != nil {
t.Fatalf("InetPton ipv4 failed: %v", err)
}
if len(raw) != 4 {
t.Fatalf("InetPton ipv4 len mismatch: got=%d", len(raw))
}
ip, err := InetNtop(AF_INET, raw)
if err != nil {
t.Fatalf("InetNtop ipv4 failed: %v", err)
}
if !net.ParseIP(ip).Equal(net.ParseIP("127.0.0.1")) {
t.Fatalf("InetNtop ipv4 mismatch: got=%q", ip)
}
}
func TestInetPtonNtopIPv6(t *testing.T) {
raw, err := InetPton(AF_INET6, "::1")
if err != nil {
t.Fatalf("InetPton ipv6 failed: %v", err)
}
if len(raw) != 16 {
t.Fatalf("InetPton ipv6 len mismatch: got=%d", len(raw))
}
ip, err := InetNtop(AF_INET6, raw)
if err != nil {
t.Fatalf("InetNtop ipv6 failed: %v", err)
}
if !net.ParseIP(ip).Equal(net.ParseIP("::1")) {
t.Fatalf("InetNtop ipv6 mismatch: got=%q", ip)
}
}
func TestInetPtonInvalidInput(t *testing.T) {
if _, err := InetPton(AF_INET, "999.999.999.999"); err == nil {
t.Fatal("InetPton should fail on invalid ipv4 address")
}
}
func TestGetAddrInfoLocalhost(t *testing.T) {
hints := &ADDRINFOW{
Family: AF_UNSPEC,
Socktype: SOCK_STREAM,
Protocol: IPPROTO_TCP,
}
result, err := GetAddrInfo("localhost", "80", hints)
if err != nil {
t.Fatalf("GetAddrInfo failed: %v", err)
}
defer func() {
if freeErr := FreeAddrInfo(result); freeErr != nil {
t.Fatalf("FreeAddrInfo failed: %v", freeErr)
}
}()
total := 0
validIP := 0
for p := result; p != nil; p = p.Next {
total++
if p.Addr == nil {
continue
}
switch p.Family {
case AF_INET, AF_INET6:
ip, ipErr := SockaddrIPString(p.Addr)
if ipErr != nil {
t.Fatalf("SockaddrIPString failed: %v", ipErr)
}
if net.ParseIP(ip) == nil {
t.Fatalf("invalid ip parsed from addrinfo: %q", ip)
}
validIP++
}
}
if total == 0 {
t.Fatal("GetAddrInfo returned empty result list")
}
if validIP == 0 {
t.Fatal("GetAddrInfo returned no AF_INET/AF_INET6 entries")
}
}
func TestGetNameInfoNumericIPv4(t *testing.T) {
sa := SOCKADDR_IN{
Family: ADDRESS_FAMILY(AF_INET),
Port: htons(80),
Addr: [4]byte{127, 0, 0, 1},
}
host, service, err := GetNameInfo((*SOCKADDR)(unsafe.Pointer(&sa)), int32(unsafe.Sizeof(sa)), NI_NUMERICHOST|NI_NUMERICSERV)
if err != nil {
t.Fatalf("GetNameInfo failed: %v", err)
}
if host != "127.0.0.1" {
t.Fatalf("GetNameInfo host mismatch: got=%q", host)
}
if service != "80" {
t.Fatalf("GetNameInfo service mismatch: got=%q", service)
}
}
func TestHostNetworkByteOrderHelpers(t *testing.T) {
if Htons(0x1234) != 0x3412 {
t.Fatalf("Htons mismatch: got=%#x", Htons(0x1234))
}
if Ntohs(0x3412) != 0x1234 {
t.Fatalf("Ntohs mismatch: got=%#x", Ntohs(0x3412))
}
tcp4 := MIB_TCPROW_OWNER_PID{LocalPort: uint32(Htons(8080)), RemotePort: uint32(Htons(443))}
if tcp4.LocalPortHost() != 8080 || tcp4.RemotePortHost() != 443 {
t.Fatalf("tcp4 port helper mismatch: local=%d remote=%d", tcp4.LocalPortHost(), tcp4.RemotePortHost())
}
tcp6 := MIB_TCP6ROW_OWNER_PID{LocalPort: uint32(Htons(8081)), RemotePort: uint32(Htons(8443))}
if tcp6.LocalPortHost() != 8081 || tcp6.RemotePortHost() != 8443 {
t.Fatalf("tcp6 port helper mismatch: local=%d remote=%d", tcp6.LocalPortHost(), tcp6.RemotePortHost())
}
udp4 := MIB_UDPROW_OWNER_PID{LocalPort: uint32(Htons(5353))}
if udp4.LocalPortHost() != 5353 {
t.Fatalf("udp4 port helper mismatch: local=%d", udp4.LocalPortHost())
}
udp6 := MIB_UDP6ROW_OWNER_PID{LocalPort: uint32(Htons(5354))}
if udp6.LocalPortHost() != 5354 {
t.Fatalf("udp6 port helper mismatch: local=%d", udp6.LocalPortHost())
}
}
func TestIphlpapiTableLayouts(t *testing.T) {
wantIfTableOffset := uintptr(8)
wantIfRowSize := uintptr(1352)
if runtime.GOARCH == "386" {
wantIfTableOffset = 4
wantIfRowSize = 1348
}
if got := unsafe.Offsetof(MIB_IF_TABLE2{}.Table); got != wantIfTableOffset {
t.Fatalf("MIB_IF_TABLE2.Table offset mismatch: got=%d want=%d", got, wantIfTableOffset)
}
if got := unsafe.Sizeof(MIB_IF_ROW2{}); got != wantIfRowSize {
t.Fatalf("MIB_IF_ROW2 size mismatch: got=%d want=%d", got, wantIfRowSize)
}
if got := unsafe.Offsetof(MIB_TCPTABLE_OWNER_PID{}.Table); got != 4 {
t.Fatalf("MIB_TCPTABLE_OWNER_PID.Table offset mismatch: got=%d want=4", got)
}
if got := unsafe.Sizeof(MIB_TCPROW_OWNER_PID{}); got != 24 {
t.Fatalf("MIB_TCPROW_OWNER_PID size mismatch: got=%d want=24", got)
}
if got := unsafe.Offsetof(MIB_TCP6TABLE_OWNER_PID{}.Table); got != 4 {
t.Fatalf("MIB_TCP6TABLE_OWNER_PID.Table offset mismatch: got=%d want=4", got)
}
if got := unsafe.Sizeof(MIB_TCP6ROW_OWNER_PID{}); got != 56 {
t.Fatalf("MIB_TCP6ROW_OWNER_PID size mismatch: got=%d want=56", got)
}
if got := unsafe.Offsetof(MIB_UDPTABLE_OWNER_PID{}.Table); got != 4 {
t.Fatalf("MIB_UDPTABLE_OWNER_PID.Table offset mismatch: got=%d want=4", got)
}
if got := unsafe.Sizeof(MIB_UDPROW_OWNER_PID{}); got != 12 {
t.Fatalf("MIB_UDPROW_OWNER_PID size mismatch: got=%d want=12", got)
}
if got := unsafe.Offsetof(MIB_UDP6TABLE_OWNER_PID{}.Table); got != 4 {
t.Fatalf("MIB_UDP6TABLE_OWNER_PID.Table offset mismatch: got=%d want=4", got)
}
if got := unsafe.Sizeof(MIB_UDP6ROW_OWNER_PID{}); got != 28 {
t.Fatalf("MIB_UDP6ROW_OWNER_PID size mismatch: got=%d want=28", got)
}
}
func requireWSA(t *testing.T) {
t.Helper()
var data WSADATA
if err := WSAStartup(makeWord(2, 2), &data); err != nil {
t.Fatalf("WSAStartup failed: %v", err)
}
t.Cleanup(func() {
_ = WSACleanup()
})
}
func TestSocketConnectSendRecv(t *testing.T) {
requireWSA(t)
ln, err := net.Listen("tcp4", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen failed: %v", err)
}
defer func() {
_ = ln.Close()
}()
done := make(chan error, 1)
go func() {
conn, acceptErr := ln.Accept()
if acceptErr != nil {
done <- acceptErr
return
}
defer func() {
_ = conn.Close()
}()
buf := make([]byte, 4)
if _, readErr := io.ReadFull(conn, buf); readErr != nil {
done <- readErr
return
}
if string(buf) != "ping" {
done <- io.ErrUnexpectedEOF
return
}
_, writeErr := conn.Write([]byte("pong"))
done <- writeErr
}()
s, err := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
if err != nil {
t.Fatalf("Socket failed: %v", err)
}
defer func() {
_ = Closesocket(s)
}()
tcpAddr, ok := ln.Addr().(*net.TCPAddr)
if !ok {
t.Fatalf("listen addr type mismatch: %T", ln.Addr())
}
sa := SOCKADDR_IN{
Family: ADDRESS_FAMILY(AF_INET),
Port: htons(uint16(tcpAddr.Port)),
Addr: [4]byte{127, 0, 0, 1},
}
if err := Connect(s, (*SOCKADDR)(unsafe.Pointer(&sa)), int32(unsafe.Sizeof(sa))); err != nil {
t.Fatalf("Connect failed: %v", err)
}
payload := []byte("ping")
written, err := Send(s, payload, 0)
if err != nil {
t.Fatalf("Send failed: %v", err)
}
if written != len(payload) {
t.Fatalf("Send wrote mismatch: got=%d want=%d", written, len(payload))
}
reply := make([]byte, 4)
read, err := Recv(s, reply, 0)
if err != nil {
t.Fatalf("Recv failed: %v", err)
}
if string(reply[:read]) != "pong" {
t.Fatalf("Recv data mismatch: got=%q", string(reply[:read]))
}
if err := Shutdown(s, SD_BOTH); err != nil {
t.Fatalf("Shutdown failed: %v", err)
}
select {
case serverErr := <-done:
if serverErr != nil {
t.Fatalf("server goroutine failed: %v", serverErr)
}
case <-time.After(3 * time.Second):
t.Fatal("server goroutine timeout")
}
}
func TestBindListenEphemeral(t *testing.T) {
requireWSA(t)
s, err := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
if err != nil {
t.Fatalf("Socket failed: %v", err)
}
defer func() {
_ = Closesocket(s)
}()
sa := SOCKADDR_IN{
Family: ADDRESS_FAMILY(AF_INET),
Port: 0,
Addr: [4]byte{127, 0, 0, 1},
}
if err := Bind(s, (*SOCKADDR)(unsafe.Pointer(&sa)), int32(unsafe.Sizeof(sa))); err != nil {
t.Fatalf("Bind failed: %v", err)
}
if err := Listen(s, 1); err != nil {
t.Fatalf("Listen failed: %v", err)
}
}
func TestGetSockNameAndPeerName(t *testing.T) {
requireWSA(t)
ln, err := net.Listen("tcp4", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen failed: %v", err)
}
defer func() {
_ = ln.Close()
}()
connCh := make(chan net.Conn, 1)
errCh := make(chan error, 1)
go func() {
conn, acceptErr := ln.Accept()
if acceptErr != nil {
errCh <- acceptErr
return
}
connCh <- conn
}()
client, err := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
if err != nil {
t.Fatalf("Socket failed: %v", err)
}
defer func() {
_ = Closesocket(client)
}()
tcpAddr := ln.Addr().(*net.TCPAddr)
peer := SOCKADDR_IN{
Family: ADDRESS_FAMILY(AF_INET),
Port: htons(uint16(tcpAddr.Port)),
Addr: [4]byte{127, 0, 0, 1},
}
if err := Connect(client, (*SOCKADDR)(unsafe.Pointer(&peer)), int32(unsafe.Sizeof(peer))); err != nil {
t.Fatalf("Connect failed: %v", err)
}
var serverConn net.Conn
select {
case serverConn = <-connCh:
defer func() {
_ = serverConn.Close()
}()
case acceptErr := <-errCh:
t.Fatalf("accept failed: %v", acceptErr)
case <-time.After(3 * time.Second):
t.Fatal("accept timeout")
}
var local SOCKADDR_IN
localLen := int32(unsafe.Sizeof(local))
if err := GetSockName(client, (*SOCKADDR)(unsafe.Pointer(&local)), &localLen); err != nil {
t.Fatalf("GetSockName failed: %v", err)
}
if local.Family != ADDRESS_FAMILY(AF_INET) {
t.Fatalf("GetSockName family mismatch: got=%d", local.Family)
}
if htons(local.Port) == 0 {
t.Fatal("GetSockName returned zero local port")
}
var remote SOCKADDR_IN
remoteLen := int32(unsafe.Sizeof(remote))
if err := GetPeerName(client, (*SOCKADDR)(unsafe.Pointer(&remote)), &remoteLen); err != nil {
t.Fatalf("GetPeerName failed: %v", err)
}
if remote.Family != ADDRESS_FAMILY(AF_INET) {
t.Fatalf("GetPeerName family mismatch: got=%d", remote.Family)
}
if remote.Addr != [4]byte{127, 0, 0, 1} {
t.Fatalf("GetPeerName ip mismatch: got=%v", remote.Addr)
}
if int(htons(remote.Port)) != tcpAddr.Port {
t.Fatalf("GetPeerName port mismatch: got=%d want=%d", htons(remote.Port), tcpAddr.Port)
}
}
func TestSetGetSockOptInt(t *testing.T) {
requireWSA(t)
s, err := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
if err != nil {
t.Fatalf("Socket failed: %v", err)
}
defer func() {
_ = Closesocket(s)
}()
if err := SetSockOptInt(s, SOL_SOCKET, SO_REUSEADDR, 1); err != nil {
t.Fatalf("SetSockOptInt failed: %v", err)
}
v, err := GetSockOptInt(s, SOL_SOCKET, SO_REUSEADDR)
if err != nil {
t.Fatalf("GetSockOptInt failed: %v", err)
}
if v == 0 {
t.Fatalf("GetSockOptInt(SO_REUSEADDR) expected non-zero, got %d", v)
}
}
func TestSendToRecvFromUDP(t *testing.T) {
requireWSA(t)
receiver, err := Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
if err != nil {
t.Fatalf("receiver Socket failed: %v", err)
}
defer func() {
_ = Closesocket(receiver)
}()
bindAddr := SOCKADDR_IN{
Family: ADDRESS_FAMILY(AF_INET),
Port: 0,
Addr: [4]byte{127, 0, 0, 1},
}
if err := Bind(receiver, (*SOCKADDR)(unsafe.Pointer(&bindAddr)), int32(unsafe.Sizeof(bindAddr))); err != nil {
t.Fatalf("receiver Bind failed: %v", err)
}
var recvBound SOCKADDR_IN
recvBoundLen := int32(unsafe.Sizeof(recvBound))
if err := GetSockName(receiver, (*SOCKADDR)(unsafe.Pointer(&recvBound)), &recvBoundLen); err != nil {
t.Fatalf("receiver GetSockName failed: %v", err)
}
recvPort := htons(recvBound.Port)
if recvPort == 0 {
t.Fatal("receiver bound port is zero")
}
sender, err := Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
if err != nil {
t.Fatalf("sender Socket failed: %v", err)
}
defer func() {
_ = Closesocket(sender)
}()
target := SOCKADDR_IN{
Family: ADDRESS_FAMILY(AF_INET),
Port: recvBound.Port,
Addr: [4]byte{127, 0, 0, 1},
}
payload := []byte("udp-ping")
sent, err := SendTo(sender, payload, 0, (*SOCKADDR)(unsafe.Pointer(&target)), int32(unsafe.Sizeof(target)))
if err != nil {
t.Fatalf("SendTo failed: %v", err)
}
if sent != len(payload) {
t.Fatalf("SendTo length mismatch: got=%d want=%d", sent, len(payload))
}
buf := make([]byte, 64)
var from SOCKADDR_IN
fromLen := int32(unsafe.Sizeof(from))
n, err := RecvFrom(receiver, buf, 0, (*SOCKADDR)(unsafe.Pointer(&from)), &fromLen)
if err != nil {
t.Fatalf("RecvFrom failed: %v", err)
}
if string(buf[:n]) != string(payload) {
t.Fatalf("RecvFrom payload mismatch: got=%q want=%q", string(buf[:n]), string(payload))
}
if from.Family != ADDRESS_FAMILY(AF_INET) {
t.Fatalf("RecvFrom family mismatch: got=%d", from.Family)
}
if from.Addr != [4]byte{127, 0, 0, 1} {
t.Fatalf("RecvFrom source ip mismatch: got=%v", from.Addr)
}
if htons(from.Port) == 0 {
t.Fatal("RecvFrom source port is zero")
}
}
func TestAcceptSendRecvServerFlow(t *testing.T) {
requireWSA(t)
listener, err := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
if err != nil {
t.Fatalf("listener Socket failed: %v", err)
}
defer func() {
_ = Closesocket(listener)
}()
if err := SetSockOptInt(listener, SOL_SOCKET, SO_REUSEADDR, 1); err != nil {
t.Fatalf("SetSockOptInt(SO_REUSEADDR) failed: %v", err)
}
bindAddr := SOCKADDR_IN{
Family: ADDRESS_FAMILY(AF_INET),
Port: 0,
Addr: [4]byte{127, 0, 0, 1},
}
if err := Bind(listener, (*SOCKADDR)(unsafe.Pointer(&bindAddr)), int32(unsafe.Sizeof(bindAddr))); err != nil {
t.Fatalf("Bind failed: %v", err)
}
if err := Listen(listener, 1); err != nil {
t.Fatalf("Listen failed: %v", err)
}
var bound SOCKADDR_IN
boundLen := int32(unsafe.Sizeof(bound))
if err := GetSockName(listener, (*SOCKADDR)(unsafe.Pointer(&bound)), &boundLen); err != nil {
t.Fatalf("GetSockName(listener) failed: %v", err)
}
port := htons(bound.Port)
if port == 0 {
t.Fatal("listener port is zero")
}
serverDone := make(chan error, 1)
go func() {
var peer SOCKADDR_IN
peerLen := int32(unsafe.Sizeof(peer))
clientSock, acceptErr := Accept(listener, (*SOCKADDR)(unsafe.Pointer(&peer)), &peerLen)
if acceptErr != nil {
serverDone <- acceptErr
return
}
defer func() {
_ = Closesocket(clientSock)
}()
buf := make([]byte, 16)
n, recvErr := Recv(clientSock, buf, 0)
if recvErr != nil {
serverDone <- recvErr
return
}
if string(buf[:n]) != "accept-ping" {
serverDone <- fmt.Errorf("server recv mismatch: %q", string(buf[:n]))
return
}
if _, sendErr := Send(clientSock, []byte("accept-pong"), 0); sendErr != nil {
serverDone <- sendErr
return
}
serverDone <- nil
}()
conn, err := net.DialTimeout("tcp4", fmt.Sprintf("127.0.0.1:%d", port), 3*time.Second)
if err != nil {
t.Fatalf("DialTimeout failed: %v", err)
}
defer func() {
_ = conn.Close()
}()
if _, err := conn.Write([]byte("accept-ping")); err != nil {
t.Fatalf("client Write failed: %v", err)
}
reply := make([]byte, 16)
n, err := io.ReadFull(conn, reply[:11])
if err != nil {
t.Fatalf("client ReadFull failed: %v", err)
}
if string(reply[:n]) != "accept-pong" {
t.Fatalf("client recv mismatch: %q", string(reply[:n]))
}
select {
case err := <-serverDone:
if err != nil {
t.Fatalf("server flow failed: %v", err)
}
case <-time.After(3 * time.Second):
t.Fatal("server flow timeout")
}
}
func TestGetSockOptSOErrorAfterFailedConnect(t *testing.T) {
requireWSA(t)
s, err := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
if err != nil {
t.Fatalf("Socket failed: %v", err)
}
defer func() {
_ = Closesocket(s)
}()
target := SOCKADDR_IN{
Family: ADDRESS_FAMILY(AF_INET),
Port: htons(1),
Addr: [4]byte{127, 0, 0, 1},
}
connectErr := Connect(s, (*SOCKADDR)(unsafe.Pointer(&target)), int32(unsafe.Sizeof(target)))
if connectErr == nil {
t.Skip("port 1 is open in current environment, skip SO_ERROR failure-path test")
}
soErr, err := GetSockOptInt(s, SOL_SOCKET, SO_ERROR)
if err != nil {
t.Fatalf("GetSockOptInt(SO_ERROR) failed: %v", err)
}
if soErr == 0 {
// On some stacks the synchronous connect error can be reported directly and
// SO_ERROR may already be cleared; keep this as diagnostic instead of flaky fail.
t.Logf("SO_ERROR=0 after connect err=%v", connectErr)
}
}
func TestGetAdaptersAddresses(t *testing.T) {
adapters, err := GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX|GAA_FLAG_INCLUDE_GATEWAYS)
if err != nil {
t.Fatalf("GetAdaptersAddresses failed: %v", err)
}
if len(adapters) == 0 {
t.Fatal("GetAdaptersAddresses returned empty list")
}
foundNamed := false
for _, a := range adapters {
if strings.TrimSpace(a.FriendlyName) != "" || strings.TrimSpace(a.AdapterName) != "" {
foundNamed = true
break
}
}
if !foundNamed {
t.Fatal("GetAdaptersAddresses returned adapters without names")
}
for _, a := range adapters {
if a.PhysicalAddressLength > 0 {
if strings.TrimSpace(a.MACAddress) == "" {
t.Fatalf("adapter has physical address length %d but empty MACAddress", a.PhysicalAddressLength)
}
if _, err := net.ParseMAC(a.MACAddress); err != nil {
t.Fatalf("invalid MACAddress format %q: %v", a.MACAddress, err)
}
}
for _, ip := range a.UnicastIPs {
if net.ParseIP(ip) == nil {
t.Fatalf("invalid unicast ip in adapter info: %q", ip)
}
}
for _, ip := range a.DNSServers {
if net.ParseIP(ip) == nil {
t.Fatalf("invalid dns server ip in adapter info: %q", ip)
}
}
for _, ip := range a.Gateways {
if net.ParseIP(ip) == nil {
t.Fatalf("invalid gateway ip in adapter info: %q", ip)
}
}
}
}
func TestGetIfTable2AndEntry2(t *testing.T) {
rows, err := GetIfTable2()
if err != nil {
t.Fatalf("GetIfTable2 failed: %v", err)
}
if len(rows) == 0 {
t.Fatal("GetIfTable2 returned empty list")
}
row := rows[0]
if row.InterfaceIndex == 0 && row.InterfaceLuid == 0 {
t.Fatal("GetIfTable2 returned row without interface identity")
}
if err := GetIfEntry2(&row); err != nil {
t.Fatalf("GetIfEntry2 failed: %v", err)
}
if row.InterfaceIndex == 0 && row.InterfaceLuid == 0 {
t.Fatal("GetIfEntry2 cleared interface identity")
}
stats := []uint64{
row.InOctets,
row.OutOctets,
row.InUcastOctets,
row.OutUcastOctets,
row.InMulticastOctets,
row.OutMulticastOctets,
row.InBroadcastOctets,
row.OutBroadcastOctets,
}
if len(stats) != 8 {
t.Fatalf("unexpected stats field count: got=%d", len(stats))
}
}
func TestGetExtendedTcp4TableIncludesCurrentListener(t *testing.T) {
listener, err := net.Listen("tcp4", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen tcp4 failed: %v", err)
}
defer func() {
_ = listener.Close()
}()
port := listener.Addr().(*net.TCPAddr).Port
row := waitForTCP4OwnerPIDRow(t, uint32(os.Getpid()), port)
if row.State != MIB_TCP_STATE_LISTEN {
t.Fatalf("unexpected tcp state: got=%d want=%d", row.State, MIB_TCP_STATE_LISTEN)
}
}
func TestGetExtendedUdp4TableIncludesCurrentSocket(t *testing.T) {
packetConn, err := net.ListenPacket("udp4", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen udp4 failed: %v", err)
}
defer func() {
_ = packetConn.Close()
}()
port := packetConn.LocalAddr().(*net.UDPAddr).Port
_ = waitForUDP4OwnerPIDRow(t, uint32(os.Getpid()), port)
}
func TestGetExtendedTcp6TableIncludesCurrentListener(t *testing.T) {
listener, err := net.Listen("tcp6", "[::1]:0")
if err != nil {
t.Skipf("tcp6 unavailable: %v", err)
}
defer func() {
_ = listener.Close()
}()
port := listener.Addr().(*net.TCPAddr).Port
row := waitForTCP6OwnerPIDRow(t, uint32(os.Getpid()), port)
if row.State != MIB_TCP_STATE_LISTEN {
t.Fatalf("unexpected tcp6 state: got=%d want=%d", row.State, MIB_TCP_STATE_LISTEN)
}
}
func TestGetExtendedUdp6TableIncludesCurrentSocket(t *testing.T) {
packetConn, err := net.ListenPacket("udp6", "[::1]:0")
if err != nil {
t.Skipf("udp6 unavailable: %v", err)
}
defer func() {
_ = packetConn.Close()
}()
port := packetConn.LocalAddr().(*net.UDPAddr).Port
_ = waitForUDP6OwnerPIDRow(t, uint32(os.Getpid()), port)
}
func waitForTCP4OwnerPIDRow(t *testing.T, pid uint32, port int) MIB_TCPROW_OWNER_PID {
t.Helper()
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
rows, err := GetExtendedTcp4Table(false, TCP_TABLE_OWNER_PID_ALL)
if err != nil {
t.Fatalf("GetExtendedTcp4Table failed: %v", err)
}
for _, row := range rows {
if row.OwningPid == pid && int(row.LocalPortHost()) == port {
return row
}
}
time.Sleep(50 * time.Millisecond)
}
t.Fatalf("did not find tcp4 row for pid=%d port=%d", pid, port)
return MIB_TCPROW_OWNER_PID{}
}
func waitForTCP6OwnerPIDRow(t *testing.T, pid uint32, port int) MIB_TCP6ROW_OWNER_PID {
t.Helper()
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
rows, err := GetExtendedTcp6Table(false, TCP_TABLE_OWNER_PID_ALL)
if err != nil {
t.Fatalf("GetExtendedTcp6Table failed: %v", err)
}
for _, row := range rows {
if row.OwningPid == pid && int(row.LocalPortHost()) == port {
return row
}
}
time.Sleep(50 * time.Millisecond)
}
t.Fatalf("did not find tcp6 row for pid=%d port=%d", pid, port)
return MIB_TCP6ROW_OWNER_PID{}
}
func waitForUDP4OwnerPIDRow(t *testing.T, pid uint32, port int) MIB_UDPROW_OWNER_PID {
t.Helper()
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
rows, err := GetExtendedUdp4Table(false, UDP_TABLE_OWNER_PID)
if err != nil {
t.Fatalf("GetExtendedUdp4Table failed: %v", err)
}
for _, row := range rows {
if row.OwningPid == pid && int(row.LocalPortHost()) == port {
return row
}
}
time.Sleep(50 * time.Millisecond)
}
t.Fatalf("did not find udp4 row for pid=%d port=%d", pid, port)
return MIB_UDPROW_OWNER_PID{}
}
func waitForUDP6OwnerPIDRow(t *testing.T, pid uint32, port int) MIB_UDP6ROW_OWNER_PID {
t.Helper()
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
rows, err := GetExtendedUdp6Table(false, UDP_TABLE_OWNER_PID)
if err != nil {
t.Fatalf("GetExtendedUdp6Table failed: %v", err)
}
for _, row := range rows {
if row.OwningPid == pid && int(row.LocalPortHost()) == port {
return row
}
}
time.Sleep(50 * time.Millisecond)
}
t.Fatalf("did not find udp6 row for pid=%d port=%d", pid, port)
return MIB_UDP6ROW_OWNER_PID{}
}

41
proc_cache.go Normal file
View File

@ -0,0 +1,41 @@
package win32api
import (
"fmt"
"sync"
"syscall"
)
var (
procCacheMu sync.Mutex
dllCache = map[string]syscall.Handle{}
procCache = map[string]uintptr{}
)
func getProcAddr(dllName, procName string) (uintptr, error) {
cacheKey := dllName + "!" + procName
procCacheMu.Lock()
defer procCacheMu.Unlock()
if proc, ok := procCache[cacheKey]; ok {
return proc, nil
}
dll, ok := dllCache[dllName]
if !ok {
var err error
dll, err = syscall.LoadLibrary(dllName)
if err != nil {
return 0, fmt.Errorf("load %s: %w", dllName, err)
}
dllCache[dllName] = dll
}
proc, err := syscall.GetProcAddress(syscall.Handle(dll), procName)
if err != nil {
return 0, fmt.Errorf("resolve %s!%s: %w", dllName, procName, err)
}
procCache[cacheKey] = proc
return proc, nil
}

View File

@ -8,15 +8,10 @@ import (
)
func ShellExecute(hwnd HWND, lpOperation, lpFile, lpParameters, lpDirectory string, nShowCmd int) error {
shell32, err := syscall.LoadLibrary("shell32.dll")
ShellExecute, err := getProcAddr("shell32.dll", "ShellExecuteW")
var op, param, directory uintptr
if err != nil {
return errors.New("Can't Load Shell32 API")
}
defer syscall.FreeLibrary(shell32)
ShellExecute, err := syscall.GetProcAddress(syscall.Handle(shell32), "ShellExecuteW")
if err != nil {
return errors.New("Can't Load ShellExecute API")
return err
}
if len(lpOperation) != 0 {
op = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpOperation)))
@ -27,7 +22,7 @@ func ShellExecute(hwnd HWND, lpOperation, lpFile, lpParameters, lpDirectory stri
if len(lpDirectory) != 0 {
directory = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpDirectory)))
}
r, _, _ := syscall.Syscall6(uintptr(ShellExecute), 6,
r, _, _ := syscall.Syscall6(ShellExecute, 6,
uintptr(hwnd),
op,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpFile))),
@ -70,30 +65,25 @@ func ShellExecuteEX2(hwnd HWND, lpVerb, lpFile, lpParameters, lpDirectory string
*/
func ShellExecuteEx(muzika *SHELLEXECUTEINFOW) error {
shell32, err := syscall.LoadLibrary("shell32.dll")
if err != nil {
return errors.New("Can't Load Shell32 API")
if muzika == nil {
return syscall.EINVAL
}
defer syscall.FreeLibrary(shell32)
ShellExecuteEx, err := syscall.GetProcAddress(syscall.Handle(shell32), "ShellExecuteExW")
ShellExecuteEx, err := getProcAddr("shell32.dll", "ShellExecuteExW")
if err != nil {
return errors.New("Can't Load ShellExecuteEx API")
return err
}
r, _, errno := syscall.Syscall6(ShellExecuteEx, 1, uintptr(unsafe.Pointer(muzika)), 0, 0, 0, 0, 0)
if r == 0 {
return error(errno)
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
return nil
}
func DragQueryFile(hDrop HDROP, iFile DWORD, lpszFile *uint16, cch DWORD) (DWORD, error) {
shell32, err := syscall.LoadLibrary("shell32.dll")
if err != nil {
return 0, err
}
defer syscall.FreeLibrary(shell32)
dqf, err := syscall.GetProcAddress(syscall.Handle(shell32), "DragQueryFileW")
dqf, err := getProcAddr("shell32.dll", "DragQueryFileW")
if err != nil {
return 0, err
}

View File

@ -8,7 +8,7 @@ type SHELLEXECUTEINFOW struct {
LpFile uintptr
LpParameters uintptr
LpDirectory uintptr
NShow int
NShow int32
HInstApp HINSTANCE
LpIDList LPVOID
LpClass uintptr

265
user32.go
View File

@ -65,7 +65,7 @@ func OpenClipboard(hWnd HWND) error {
}
return nil
}
if r, _, errno := syscall.Syscall(oc, 0, 0, 0, 0); r == 0 {
if r, _, errno := syscall.Syscall(oc, 1, 0, 0, 0); r == 0 {
return error(errno)
}
return nil
@ -116,6 +116,9 @@ func EnumClipboardFormats(uFormat DWORD) (DWORD, error) {
}
r, _, errno := syscall.Syscall(ecf, 1, uintptr(uFormat), 0, 0)
if r == 0 {
if errno == 0 {
return 0, nil
}
return 0, error(errno)
}
return DWORD(r), nil
@ -123,12 +126,17 @@ func EnumClipboardFormats(uFormat DWORD) (DWORD, error) {
func EnumAllClipboardFormats() ([]DWORD, error) {
var formats []DWORD
for i := 0; ; i++ {
format, err := EnumClipboardFormats(DWORD(i))
var current DWORD
for {
format, err := EnumClipboardFormats(current)
if err != nil {
return nil, err
}
if format == 0 {
break
}
formats = append(formats, format)
current = format
}
return formats, nil
}
@ -193,6 +201,9 @@ func CountClipboardFormats() (int, error) {
}
r, _, errno := syscall.Syscall(ccf, 0, 0, 0, 0)
if r == 0 {
if errno == 0 {
return 0, nil
}
return 0, error(errno)
}
return int(r), nil
@ -210,6 +221,9 @@ func GetClipboardOwner() (HWND, error) {
}
r, _, errno := syscall.Syscall(gco, 0, 0, 0, 0)
if r == 0 {
if errno == 0 {
return 0, nil
}
return 0, error(errno)
}
return HWND(r), nil
@ -232,18 +246,41 @@ func GetUpdatedClipboardFormats(lpuiFormats unsafe.Pointer, cFormats int, pcForm
return int(r), nil
}
type updatedClipboardFormatsFunc func(lpuiFormats unsafe.Pointer, cFormats int, pcFormats unsafe.Pointer) (int, error)
func getUpdatedClipboardFormatsAll(fetch updatedClipboardFormatsFunc) ([]DWORD, error) {
if fetch == nil {
fetch = GetUpdatedClipboardFormats
}
for size := 32; ; {
formats := make([]uint32, size)
var count uint32
_, err := fetch(unsafe.Pointer(&formats[0]), len(formats), unsafe.Pointer(&count))
if err != nil {
if errors.Is(err, syscall.ERROR_INSUFFICIENT_BUFFER) {
nextSize := size * 2
if count > uint32(size) {
nextSize = int(count)
}
size = nextSize
continue
}
return nil, err
}
if count > uint32(len(formats)) {
size = int(count)
continue
}
res := make([]DWORD, 0, int(count))
for i := 0; i < int(count); i++ {
res = append(res, DWORD(formats[i]))
}
return res, nil
}
}
func GetUpdatedClipboardFormatsAll() ([]DWORD, error) {
var res []DWORD
formats := make([]uint32, 32)
var count uint32
_, err := GetUpdatedClipboardFormats(unsafe.Pointer(&formats[0]), len(formats), unsafe.Pointer(&count))
if err != nil {
return nil, err
}
for i := 0; i < int(count); i++ {
res = append(res, DWORD(formats[i]))
}
return res, err
return getUpdatedClipboardFormatsAll(GetUpdatedClipboardFormats)
}
func IsClipboardFormatAvailable(uFormat DWORD) (bool, error) {
@ -270,8 +307,14 @@ func AddClipboardFormatListener(hWnd HWND) (bool, error) {
if err != nil {
return false, err
}
r, _, _ := syscall.Syscall(acfl, 1, uintptr(hWnd), 0, 0)
return r != 0, nil
r, _, errno := syscall.Syscall(acfl, 1, uintptr(hWnd), 0, 0)
if r == 0 {
if errno != 0 {
return false, error(errno)
}
return false, syscall.EINVAL
}
return true, nil
}
func RemoveClipboardFormatListener(hWnd HWND) (bool, error) {
@ -284,8 +327,14 @@ func RemoveClipboardFormatListener(hWnd HWND) (bool, error) {
if err != nil {
return false, err
}
r, _, _ := syscall.Syscall(rcfl, 1, uintptr(hWnd), 0, 0)
return r != 0, nil
r, _, errno := syscall.Syscall(rcfl, 1, uintptr(hWnd), 0, 0)
if r == 0 {
if errno != 0 {
return false, error(errno)
}
return false, syscall.EINVAL
}
return true, nil
}
func SetClipboardData(uFormat DWORD, hMem HGLOBAL) (HGLOBAL, error) {
@ -317,6 +366,9 @@ func SetClipboardViewer(hWndNewViewer HWND) (HWND, error) {
}
r, _, errno := syscall.Syscall(scv, 1, uintptr(hWndNewViewer), 0, 0)
if r == 0 {
if errno == 0 {
return 0, nil
}
return 0, error(errno)
}
return HWND(r), nil
@ -349,7 +401,7 @@ func CreateWindowEx(dwExStyle DWORD, lpClassName, lpWindowName string, dwStyle D
if err != nil {
return 0, errors.New("Can't Load CreateWindowEx API")
}
r, _, errno := syscall.Syscall12(cwe, 11, uintptr(dwExStyle), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpClassName))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpWindowName))), uintptr(dwStyle), uintptr(x), uintptr(y), uintptr(nWidth), uintptr(nHeight), uintptr(hWndParent), uintptr(hMenu), uintptr(hInstance), uintptr(lpParam))
r, _, errno := syscall.Syscall12(cwe, 12, uintptr(dwExStyle), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpClassName))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpWindowName))), uintptr(dwStyle), uintptr(x), uintptr(y), uintptr(nWidth), uintptr(nHeight), uintptr(hWndParent), uintptr(hMenu), uintptr(hInstance), uintptr(lpParam))
if r == 0 {
return 0, error(errno)
}
@ -366,8 +418,14 @@ func DestroyWindow(hWnd HWND) (bool, error) {
if err != nil {
return false, errors.New("Can't Load DestroyWindow API")
}
r, _, _ := syscall.Syscall(dw, 1, uintptr(hWnd), 0, 0)
return r != 0, nil
r, _, errno := syscall.Syscall(dw, 1, uintptr(hWnd), 0, 0)
if r == 0 {
if errno != 0 {
return false, error(errno)
}
return false, syscall.EINVAL
}
return true, nil
}
func GetMessage(lpMsg *MSG, hWnd HWND, wMsgFilterMin, wMsgFilterMax DWORD) (DWORD, error) {
@ -381,7 +439,7 @@ func GetMessage(lpMsg *MSG, hWnd HWND, wMsgFilterMin, wMsgFilterMax DWORD) (DWOR
return 0, errors.New("Can't Load GetMessage API")
}
r, _, errno := syscall.Syscall6(gm, 4, uintptr(unsafe.Pointer(lpMsg)), uintptr(hWnd), uintptr(wMsgFilterMin), uintptr(wMsgFilterMax), 0, 0)
if r == 0 {
if int32(r) == -1 {
return 0, error(errno)
}
return DWORD(r), nil
@ -411,8 +469,8 @@ func DispatchMessage(lpMsg *MSG) (LRESULT, error) {
if err != nil {
return 0, err
}
r, _, err := syscall.Syscall(proc, 1, uintptr(unsafe.Pointer(lpMsg)), 0, 0)
return LRESULT(r), err
r, _, _ := syscall.Syscall(proc, 1, uintptr(unsafe.Pointer(lpMsg)), 0, 0)
return LRESULT(r), nil
}
func DefWindowProc(hWnd HWND, uMsg UINT, wParam WPARAM, lParam LPARAM) LRESULT {
@ -439,8 +497,14 @@ func PostMessage(hWnd HWND, msg UINT, wParam WPARAM, lParam LPARAM) (bool, error
if err != nil {
return false, err
}
r, _, _ := syscall.Syscall6(proc, 4, uintptr(hWnd), uintptr(msg), uintptr(wParam), uintptr(lParam), 0, 0)
return r != 0, nil
r, _, errno := syscall.Syscall6(proc, 4, uintptr(hWnd), uintptr(msg), uintptr(wParam), uintptr(lParam), 0, 0)
if r == 0 {
if errno != 0 {
return false, error(errno)
}
return false, syscall.EINVAL
}
return true, nil
}
func PostQuitMessage(nExitCode int) {
@ -472,3 +536,152 @@ func RegisterClassEx(lpWndClass *WNDCLASSEX) (uint16, error) {
}
return uint16(r), nil
}
func OpenInputDesktop(dwFlags DWORD, fInherit bool, dwDesiredAccess DWORD) (HDESK, error) {
proc, err := getProcAddr("user32.dll", "OpenInputDesktop")
if err != nil {
return 0, err
}
inherit := uintptr(0)
if fInherit {
inherit = 1
}
r, _, errno := syscall.Syscall(proc, 3, uintptr(dwFlags), inherit, uintptr(dwDesiredAccess))
if r == 0 {
if errno != 0 {
return 0, error(errno)
}
return 0, syscall.EINVAL
}
return HDESK(r), nil
}
func CloseDesktop(hDesktop HDESK) error {
proc, err := getProcAddr("user32.dll", "CloseDesktop")
if err != nil {
return err
}
r, _, errno := syscall.Syscall(proc, 1, uintptr(hDesktop), 0, 0)
if r == 0 {
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
return nil
}
func SwitchDesktop(hDesktop HDESK) error {
proc, err := getProcAddr("user32.dll", "SwitchDesktop")
if err != nil {
return err
}
r, _, errno := syscall.Syscall(proc, 1, uintptr(hDesktop), 0, 0)
if r == 0 {
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
return nil
}
func GetThreadDesktop(dwThreadId DWORD) (HDESK, error) {
proc, err := getProcAddr("user32.dll", "GetThreadDesktop")
if err != nil {
return 0, err
}
r, _, errno := syscall.Syscall(proc, 1, uintptr(dwThreadId), 0, 0)
if r == 0 {
if errno != 0 {
return 0, error(errno)
}
return 0, syscall.EINVAL
}
return HDESK(r), nil
}
func SetThreadDesktop(hDesktop HDESK) error {
proc, err := getProcAddr("user32.dll", "SetThreadDesktop")
if err != nil {
return err
}
r, _, errno := syscall.Syscall(proc, 1, uintptr(hDesktop), 0, 0)
if r == 0 {
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
return nil
}
func GetDesktopWindow() HWND {
proc, err := getProcAddr("user32.dll", "GetDesktopWindow")
if err != nil {
return 0
}
r, _, _ := syscall.Syscall(proc, 0, 0, 0, 0)
return HWND(r)
}
func GetShellWindow() HWND {
proc, err := getProcAddr("user32.dll", "GetShellWindow")
if err != nil {
return 0
}
r, _, _ := syscall.Syscall(proc, 0, 0, 0, 0)
return HWND(r)
}
func GetForegroundWindow() HWND {
proc, err := getProcAddr("user32.dll", "GetForegroundWindow")
if err != nil {
return 0
}
r, _, _ := syscall.Syscall(proc, 0, 0, 0, 0)
return HWND(r)
}
func GetWindowThreadProcessId(hWnd HWND) (DWORD, DWORD, error) {
proc, err := getProcAddr("user32.dll", "GetWindowThreadProcessId")
if err != nil {
return 0, 0, err
}
var processID DWORD
r, _, errno := syscall.Syscall(proc, 2, uintptr(hWnd), uintptr(unsafe.Pointer(&processID)), 0)
threadID := DWORD(r)
if threadID == 0 {
if errno != 0 {
return 0, processID, error(errno)
}
return 0, processID, syscall.EINVAL
}
return threadID, processID, nil
}
func GetWindowText(hWnd HWND) (string, error) {
lenProc, err := getProcAddr("user32.dll", "GetWindowTextLengthW")
if err != nil {
return "", err
}
textProc, err := getProcAddr("user32.dll", "GetWindowTextW")
if err != nil {
return "", err
}
n, _, _ := syscall.Syscall(lenProc, 1, uintptr(hWnd), 0, 0)
size := uint32(n) + 1
if size < 2 {
size = 2
}
buf := make([]uint16, size)
r, _, errno := syscall.Syscall(textProc, 3, uintptr(hWnd), uintptr(unsafe.Pointer(&buf[0])), uintptr(size))
if r == 0 {
if errno != 0 {
return "", error(errno)
}
return "", nil
}
return syscall.UTF16ToString(buf[:r]), nil
}

View File

@ -2,6 +2,7 @@ package win32api
import (
"fmt"
"reflect"
"syscall"
"testing"
"unsafe"
@ -76,3 +77,42 @@ func TestGetUpdatedClipboardFormatsAll(t *testing.T) {
}
fmt.Println(d)
}
func TestGetUpdatedClipboardFormatsAllRetriesOnInsufficientBuffer(t *testing.T) {
calls := 0
got, err := getUpdatedClipboardFormatsAll(func(lpuiFormats unsafe.Pointer, cFormats int, pcFormats unsafe.Pointer) (int, error) {
calls++
count := (*uint32)(pcFormats)
switch calls {
case 1:
if cFormats != 32 {
t.Fatalf("first call cFormats = %d, want 32", cFormats)
}
*count = 40
return 0, syscall.ERROR_INSUFFICIENT_BUFFER
case 2:
if cFormats != 40 {
t.Fatalf("second call cFormats = %d, want 40", cFormats)
}
*count = 3
formats := (*[1 << 12]uint32)(lpuiFormats)[:cFormats:cFormats]
formats[0] = uint32(CF_TEXT)
formats[1] = uint32(CF_UNICODETEXT)
formats[2] = 0xC000
return 1, nil
default:
t.Fatalf("unexpected call count %d", calls)
return 0, nil
}
})
if err != nil {
t.Fatalf("getUpdatedClipboardFormatsAll failed: %v", err)
}
want := []DWORD{CF_TEXT, CF_UNICODETEXT, 0xC000}
if !reflect.DeepEqual(got, want) {
t.Fatalf("formats = %v, want %v", got, want)
}
if calls != 2 {
t.Fatalf("calls = %d, want 2", calls)
}
}

View File

@ -247,3 +247,15 @@ const (
WM_QUIT = 0x0012
WM_DESTROY = 0x0002
)
const (
DESKTOP_READOBJECTS DWORD = 0x0001
DESKTOP_CREATEWINDOW DWORD = 0x0002
DESKTOP_CREATEMENU DWORD = 0x0004
DESKTOP_HOOKCONTROL DWORD = 0x0008
DESKTOP_JOURNALRECORD DWORD = 0x0010
DESKTOP_JOURNALPLAYBACK DWORD = 0x0020
DESKTOP_ENUMERATE DWORD = 0x0040
DESKTOP_WRITEOBJECTS DWORD = 0x0080
DESKTOP_SWITCHDESKTOP DWORD = 0x0100
)

129
user32_window_test.go Normal file
View File

@ -0,0 +1,129 @@
//go:build windows
package win32api
import (
"syscall"
"testing"
)
func TestDesktopWindowAndThreadProcess(t *testing.T) {
desktop := GetDesktopWindow()
if desktop == 0 {
t.Fatal("GetDesktopWindow returned 0")
}
threadID, processID, err := GetWindowThreadProcessId(desktop)
if err != nil {
t.Fatalf("GetWindowThreadProcessId(desktop) failed: %v", err)
}
if threadID == 0 {
t.Fatal("GetWindowThreadProcessId(desktop) threadID is 0")
}
if processID == 0 {
t.Fatal("GetWindowThreadProcessId(desktop) processID is 0")
}
if _, err := GetWindowText(desktop); err != nil {
t.Fatalf("GetWindowText(desktop) failed: %v", err)
}
}
func TestShellAndForegroundWindow(t *testing.T) {
shell := GetShellWindow()
if shell != 0 {
if _, _, err := GetWindowThreadProcessId(shell); err != nil {
t.Fatalf("GetWindowThreadProcessId(shell) failed: %v", err)
}
if _, err := GetWindowText(shell); err != nil {
t.Fatalf("GetWindowText(shell) failed: %v", err)
}
}
fg := GetForegroundWindow()
if fg != 0 {
if _, _, err := GetWindowThreadProcessId(fg); err != nil {
t.Fatalf("GetWindowThreadProcessId(foreground) failed: %v", err)
}
if _, err := GetWindowText(fg); err != nil {
t.Fatalf("GetWindowText(foreground) failed: %v", err)
}
}
}
func TestOpenInputDesktopAndSwitch(t *testing.T) {
desk, err := OpenInputDesktop(0, false, DESKTOP_READOBJECTS|DESKTOP_SWITCHDESKTOP)
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERROR_ACCESS_DENIED {
t.Skipf("OpenInputDesktop access denied in current context: %v", err)
}
t.Fatalf("OpenInputDesktop failed: %v", err)
}
defer func() {
_ = CloseDesktop(desk)
}()
if err := SwitchDesktop(desk); err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERROR_ACCESS_DENIED {
t.Skipf("SwitchDesktop access denied in current context: %v", err)
}
t.Fatalf("SwitchDesktop failed: %v", err)
}
}
func TestGetThreadDesktopAndSetCurrentDesktop(t *testing.T) {
threadID := GetCurrentThreadId()
if threadID == 0 {
t.Fatal("GetCurrentThreadId returned 0")
}
desk, err := GetThreadDesktop(threadID)
if err != nil {
t.Fatalf("GetThreadDesktop failed: %v", err)
}
if desk == 0 {
t.Fatal("GetThreadDesktop returned 0")
}
if err := SetThreadDesktop(desk); err != nil {
if errno, ok := err.(syscall.Errno); ok {
if errno == syscall.Errno(170) || errno == syscall.ERROR_ACCESS_DENIED {
t.Skipf("SetThreadDesktop is restricted in current context: %v", err)
}
}
t.Fatalf("SetThreadDesktop failed: %v", err)
}
}
func TestGetMessageReturnsNilOnPostedQuit(t *testing.T) {
PostQuitMessage(23)
var msg MSG
ret, err := GetMessage(&msg, 0, 0, 0)
if err != nil {
t.Fatalf("GetMessage returned error for WM_QUIT: %v", err)
}
if ret != 0 {
t.Fatalf("GetMessage return = %d, want 0", ret)
}
if msg.Message != WM_QUIT {
t.Fatalf("message = %#x, want WM_QUIT", msg.Message)
}
if msg.WParam != 23 {
t.Fatalf("WM_QUIT exit code = %d, want 23", msg.WParam)
}
}
func TestUser32BoolWrappersReturnErrors(t *testing.T) {
if ok, err := AddClipboardFormatListener(0); err == nil || ok {
t.Fatalf("AddClipboardFormatListener(0) = (%v, %v), want failure with error", ok, err)
}
if ok, err := RemoveClipboardFormatListener(0); err == nil || ok {
t.Fatalf("RemoveClipboardFormatListener(0) = (%v, %v), want failure with error", ok, err)
}
if ok, err := DestroyWindow(0); err == nil || ok {
t.Fatalf("DestroyWindow(0) = (%v, %v), want failure with error", ok, err)
}
if ok, err := PostMessage(HWND(1), WM_USER, 0, 0); err == nil || ok {
t.Fatalf("PostMessage(invalid) = (%v, %v), want failure with error", ok, err)
}
}

View File

@ -1,7 +1,6 @@
package win32api
import (
"errors"
"syscall"
"unsafe"
)
@ -15,18 +14,34 @@ BOOL CreateEnvironmentBlock(
*/
func CreateEnvironmentBlock(lpEnvironment *HANDLE, hToken TOKEN, bInherit uintptr) error {
userenv, err := syscall.LoadLibrary("userenv.dll")
if err != nil {
return errors.New("Can't Load Userenv API")
if lpEnvironment == nil {
return syscall.EINVAL
}
defer syscall.FreeLibrary(userenv)
Dup, err := syscall.GetProcAddress(syscall.Handle(userenv), "CreateEnvironmentBlock")
Dup, err := getProcAddr("userenv.dll", "CreateEnvironmentBlock")
if err != nil {
return errors.New("Can't Load WTSQueryUserToken API")
return err
}
r, _, errno := syscall.Syscall6(uintptr(Dup), 3, uintptr(unsafe.Pointer(lpEnvironment)), uintptr(hToken), bInherit, 0, 0, 0)
r, _, errno := syscall.Syscall6(Dup, 3, uintptr(unsafe.Pointer(lpEnvironment)), uintptr(hToken), bInherit, 0, 0, 0)
if r == 0 {
return error(errno)
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
return nil
}
func DestroyEnvironmentBlock(lpEnvironment HANDLE) error {
proc, err := getProcAddr("userenv.dll", "DestroyEnvironmentBlock")
if err != nil {
return err
}
r, _, errno := syscall.Syscall(proc, 1, uintptr(lpEnvironment), 0, 0)
if r == 0 {
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
return nil
}

View File

@ -22,6 +22,7 @@ type (
HBRUSH HANDLE
HCURSOR HANDLE
HDC HANDLE
HDESK HANDLE
HDROP HANDLE
HDWP HANDLE
HENHMETAFILE HANDLE
@ -54,13 +55,14 @@ type (
ULONG uint32
ULONG_PTR uintptr
WPARAM uintptr
WTS_CONNECTSTATE_CLASS int
WTS_CONNECTSTATE_CLASS int32
WTS_INFO_CLASS int32
TRACEHANDLE uintptr
TOKEN HANDLE
LPWSTR *uint16
TOKEN_TYPE int
SW int
SECURITY_IMPERSONATION_LEVEL int
TOKEN_TYPE int32
SW int32
SECURITY_IMPERSONATION_LEVEL int32
WCHAR uint16
WORD uint16
USN int64
@ -70,13 +72,14 @@ type (
)
type WTS_SESSION_INFO struct {
SessionID HANDLE
SessionID DWORD
WinStationName *uint16
State WTS_CONNECTSTATE_CLASS
}
const (
WTS_CURRENT_SERVER_HANDLE uintptr = 0
WTS_CURRENT_SESSION DWORD = 0xFFFFFFFF
)
const (
WTSActive WTS_CONNECTSTATE_CLASS = iota
@ -90,6 +93,38 @@ const (
WTSDown
WTSInit
)
const (
WTSInitialProgram WTS_INFO_CLASS = iota
WTSApplicationName
WTSWorkingDirectory
WTSOEMId
WTSSessionId
WTSUserName
WTSWinStationName
WTSDomainName
WTSConnectState
WTSClientBuildNumber
WTSClientName
WTSClientDirectory
WTSClientProductId
WTSClientHardwareId
WTSClientAddress
WTSClientDisplay
WTSClientProtocolType
WTSIdleTime
WTSLogonTime
WTSIncomingBytes
WTSOutgoingBytes
WTSIncomingFrames
WTSOutgoingFrames
WTSClientInfo
WTSSessionInfo
WTSSessionInfoEx
WTSConfigInfo
WTSValidationInfo
WTSSessionAddressV4
WTSIsRemoteSession
)
const (
SecurityAnonymous SECURITY_IMPERSONATION_LEVEL = iota
SecurityIdentification
@ -117,9 +152,9 @@ const (
SW_MAX = 1
)
const (
CREATE_UNICODE_ENVIRONMENT uint16 = 0x00000400
CREATE_NO_WINDOW = 0x08000000
CREATE_NEW_CONSOLE = 0x00000010
CREATE_UNICODE_ENVIRONMENT DWORD = 0x00000400
CREATE_NO_WINDOW DWORD = 0x08000000
CREATE_NEW_CONSOLE DWORD = 0x00000010
)
type StartupInfo struct {

591
ws2_32.go Normal file
View File

@ -0,0 +1,591 @@
package win32api
import (
"bytes"
"fmt"
"syscall"
"unsafe"
)
func makeWord(low, high byte) WORD {
return WORD(uint16(low) | uint16(high)<<8)
}
func WSAStartup(versionRequested WORD, data *WSADATA) error {
proc, err := getProcAddr("ws2_32.dll", "WSAStartup")
if err != nil {
return err
}
r, _, _ := syscall.Syscall(proc, 2, uintptr(versionRequested), uintptr(unsafe.Pointer(data)), 0)
if r != 0 {
return syscall.Errno(r)
}
return nil
}
func WSACleanup() error {
proc, err := getProcAddr("ws2_32.dll", "WSACleanup")
if err != nil {
return err
}
r, _, _ := syscall.Syscall(proc, 0, 0, 0, 0)
if int32(r) == -1 {
last := WSAGetLastError()
if last != 0 {
return syscall.Errno(last)
}
return syscall.EINVAL
}
return nil
}
func WSAGetLastError() int {
proc, err := getProcAddr("ws2_32.dll", "WSAGetLastError")
if err != nil {
return 0
}
r, _, _ := syscall.Syscall(proc, 0, 0, 0, 0)
return int(int32(r))
}
func wsaLastErrorOr(defaultErr error) error {
last := WSAGetLastError()
if last != 0 {
return syscall.Errno(last)
}
if defaultErr != nil {
return defaultErr
}
return syscall.EINVAL
}
func GetHostName() (string, error) {
var data WSADATA
if err := WSAStartup(makeWord(2, 2), &data); err != nil {
return "", err
}
defer func() {
_ = WSACleanup()
}()
proc, err := getProcAddr("ws2_32.dll", "gethostname")
if err != nil {
return "", err
}
buf := make([]byte, 256)
r, _, _ := syscall.Syscall(proc, 2, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), 0)
if int32(r) == -1 {
last := WSAGetLastError()
if last != 0 {
return "", syscall.Errno(last)
}
return "", syscall.EINVAL
}
idx := bytes.IndexByte(buf, 0)
if idx < 0 {
idx = len(buf)
}
host := string(buf[:idx])
if host == "" {
return "", fmt.Errorf("gethostname returned empty host")
}
return host, nil
}
func InetPton(family int, ip string) ([]byte, error) {
var data WSADATA
if err := WSAStartup(makeWord(2, 2), &data); err != nil {
return nil, err
}
defer func() {
_ = WSACleanup()
}()
proc, err := getProcAddr("ws2_32.dll", "InetPtonW")
if err != nil {
return nil, err
}
var out []byte
switch family {
case AF_INET:
out = make([]byte, 4)
case AF_INET6:
out = make([]byte, 16)
default:
return nil, fmt.Errorf("unsupported address family: %d", family)
}
r, _, _ := syscall.Syscall(proc, 3,
uintptr(family),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(ip))),
uintptr(unsafe.Pointer(&out[0])),
)
if int32(r) == 1 {
return out, nil
}
if int32(r) == 0 {
return nil, fmt.Errorf("invalid ip address: %s", ip)
}
last := WSAGetLastError()
if last != 0 {
return nil, syscall.Errno(last)
}
return nil, syscall.EINVAL
}
func InetNtop(family int, addr []byte) (string, error) {
var data WSADATA
if err := WSAStartup(makeWord(2, 2), &data); err != nil {
return "", err
}
defer func() {
_ = WSACleanup()
}()
switch family {
case AF_INET:
if len(addr) != 4 {
return "", fmt.Errorf("inet4 requires 4 bytes, got %d", len(addr))
}
case AF_INET6:
if len(addr) != 16 {
return "", fmt.Errorf("inet6 requires 16 bytes, got %d", len(addr))
}
default:
return "", fmt.Errorf("unsupported address family: %d", family)
}
proc, err := getProcAddr("ws2_32.dll", "InetNtopW")
if err != nil {
return "", err
}
buf := make([]uint16, 65)
r, _, _ := syscall.Syscall6(proc, 4,
uintptr(family),
uintptr(unsafe.Pointer(&addr[0])),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(len(buf)),
0,
0,
)
if r == 0 {
last := WSAGetLastError()
if last != 0 {
return "", syscall.Errno(last)
}
return "", syscall.EINVAL
}
return syscall.UTF16ToString(buf), nil
}
func Socket(family, socketType, protocol int32) (SOCKET, error) {
proc, err := getProcAddr("ws2_32.dll", "socket")
if err != nil {
return INVALID_SOCKET, err
}
r, _, _ := syscall.Syscall(proc, 3, uintptr(family), uintptr(socketType), uintptr(protocol))
s := SOCKET(r)
if s == INVALID_SOCKET {
return INVALID_SOCKET, wsaLastErrorOr(syscall.EINVAL)
}
return s, nil
}
func Closesocket(s SOCKET) error {
proc, err := getProcAddr("ws2_32.dll", "closesocket")
if err != nil {
return err
}
r, _, _ := syscall.Syscall(proc, 1, uintptr(s), 0, 0)
if int32(r) == SOCKET_ERROR {
return wsaLastErrorOr(syscall.EINVAL)
}
return nil
}
func Connect(s SOCKET, name *SOCKADDR, namelen int32) error {
if name == nil {
return fmt.Errorf("sockaddr is nil")
}
proc, err := getProcAddr("ws2_32.dll", "connect")
if err != nil {
return err
}
r, _, _ := syscall.Syscall(proc, 3, uintptr(s), uintptr(unsafe.Pointer(name)), uintptr(namelen))
if int32(r) == SOCKET_ERROR {
return wsaLastErrorOr(syscall.EINVAL)
}
return nil
}
func Bind(s SOCKET, name *SOCKADDR, namelen int32) error {
if name == nil {
return fmt.Errorf("sockaddr is nil")
}
proc, err := getProcAddr("ws2_32.dll", "bind")
if err != nil {
return err
}
r, _, _ := syscall.Syscall(proc, 3, uintptr(s), uintptr(unsafe.Pointer(name)), uintptr(namelen))
if int32(r) == SOCKET_ERROR {
return wsaLastErrorOr(syscall.EINVAL)
}
return nil
}
func Listen(s SOCKET, backlog int32) error {
proc, err := getProcAddr("ws2_32.dll", "listen")
if err != nil {
return err
}
r, _, _ := syscall.Syscall(proc, 2, uintptr(s), uintptr(backlog), 0)
if int32(r) == SOCKET_ERROR {
return wsaLastErrorOr(syscall.EINVAL)
}
return nil
}
func Accept(s SOCKET, addr *SOCKADDR, addrlen *int32) (SOCKET, error) {
proc, err := getProcAddr("ws2_32.dll", "accept")
if err != nil {
return INVALID_SOCKET, err
}
var addrPtr uintptr
if addr != nil {
addrPtr = uintptr(unsafe.Pointer(addr))
}
var addrLenPtr uintptr
if addrlen != nil {
addrLenPtr = uintptr(unsafe.Pointer(addrlen))
}
r, _, _ := syscall.Syscall(proc, 3, uintptr(s), addrPtr, addrLenPtr)
client := SOCKET(r)
if client == INVALID_SOCKET {
return INVALID_SOCKET, wsaLastErrorOr(syscall.EINVAL)
}
return client, nil
}
func Send(s SOCKET, buf []byte, flags int32) (int, error) {
proc, err := getProcAddr("ws2_32.dll", "send")
if err != nil {
return 0, err
}
var bufPtr uintptr
if len(buf) > 0 {
bufPtr = uintptr(unsafe.Pointer(&buf[0]))
}
r, _, _ := syscall.Syscall6(proc, 4, uintptr(s), bufPtr, uintptr(len(buf)), uintptr(flags), 0, 0)
if int32(r) == SOCKET_ERROR {
return 0, wsaLastErrorOr(syscall.EINVAL)
}
return int(r), nil
}
func Recv(s SOCKET, buf []byte, flags int32) (int, error) {
proc, err := getProcAddr("ws2_32.dll", "recv")
if err != nil {
return 0, err
}
var bufPtr uintptr
if len(buf) > 0 {
bufPtr = uintptr(unsafe.Pointer(&buf[0]))
}
r, _, _ := syscall.Syscall6(proc, 4, uintptr(s), bufPtr, uintptr(len(buf)), uintptr(flags), 0, 0)
if int32(r) == SOCKET_ERROR {
return 0, wsaLastErrorOr(syscall.EINVAL)
}
return int(r), nil
}
func Shutdown(s SOCKET, how int32) error {
proc, err := getProcAddr("ws2_32.dll", "shutdown")
if err != nil {
return err
}
r, _, _ := syscall.Syscall(proc, 2, uintptr(s), uintptr(how), 0)
if int32(r) == SOCKET_ERROR {
return wsaLastErrorOr(syscall.EINVAL)
}
return nil
}
func GetSockName(s SOCKET, name *SOCKADDR, nameLen *int32) error {
if name == nil || nameLen == nil {
return syscall.EINVAL
}
proc, err := getProcAddr("ws2_32.dll", "getsockname")
if err != nil {
return err
}
r, _, _ := syscall.Syscall(proc, 3, uintptr(s), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)))
if int32(r) == SOCKET_ERROR {
return wsaLastErrorOr(syscall.EINVAL)
}
return nil
}
func GetPeerName(s SOCKET, name *SOCKADDR, nameLen *int32) error {
if name == nil || nameLen == nil {
return syscall.EINVAL
}
proc, err := getProcAddr("ws2_32.dll", "getpeername")
if err != nil {
return err
}
r, _, _ := syscall.Syscall(proc, 3, uintptr(s), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)))
if int32(r) == SOCKET_ERROR {
return wsaLastErrorOr(syscall.EINVAL)
}
return nil
}
func SetSockOpt(s SOCKET, level, optName int32, optValue []byte) error {
proc, err := getProcAddr("ws2_32.dll", "setsockopt")
if err != nil {
return err
}
var valuePtr uintptr
if len(optValue) > 0 {
valuePtr = uintptr(unsafe.Pointer(&optValue[0]))
}
r, _, _ := syscall.Syscall6(proc, 5,
uintptr(s),
uintptr(level),
uintptr(optName),
valuePtr,
uintptr(len(optValue)),
0,
)
if int32(r) == SOCKET_ERROR {
return wsaLastErrorOr(syscall.EINVAL)
}
return nil
}
func GetSockOpt(s SOCKET, level, optName int32, optValue []byte) (int32, error) {
proc, err := getProcAddr("ws2_32.dll", "getsockopt")
if err != nil {
return 0, err
}
var valuePtr uintptr
if len(optValue) > 0 {
valuePtr = uintptr(unsafe.Pointer(&optValue[0]))
}
optLen := int32(len(optValue))
r, _, _ := syscall.Syscall6(proc, 5,
uintptr(s),
uintptr(level),
uintptr(optName),
valuePtr,
uintptr(unsafe.Pointer(&optLen)),
0,
)
if int32(r) == SOCKET_ERROR {
return 0, wsaLastErrorOr(syscall.EINVAL)
}
return optLen, nil
}
func SetSockOptInt(s SOCKET, level, optName int32, value int32) error {
buf := (*[4]byte)(unsafe.Pointer(&value))[:]
return SetSockOpt(s, level, optName, buf)
}
func GetSockOptInt(s SOCKET, level, optName int32) (int32, error) {
var value int32
buf := (*[4]byte)(unsafe.Pointer(&value))[:]
optLen, err := GetSockOpt(s, level, optName, buf)
if err != nil {
return 0, err
}
if optLen < 4 {
return 0, fmt.Errorf("getsockopt returned short length: %d", optLen)
}
return value, nil
}
func SendTo(s SOCKET, buf []byte, flags int32, to *SOCKADDR, toLen int32) (int, error) {
proc, err := getProcAddr("ws2_32.dll", "sendto")
if err != nil {
return 0, err
}
var bufPtr uintptr
if len(buf) > 0 {
bufPtr = uintptr(unsafe.Pointer(&buf[0]))
}
var toPtr uintptr
if to != nil {
toPtr = uintptr(unsafe.Pointer(to))
}
r, _, _ := syscall.Syscall6(proc, 6,
uintptr(s),
bufPtr,
uintptr(len(buf)),
uintptr(flags),
toPtr,
uintptr(toLen),
)
if int32(r) == SOCKET_ERROR {
return 0, wsaLastErrorOr(syscall.EINVAL)
}
return int(r), nil
}
func RecvFrom(s SOCKET, buf []byte, flags int32, from *SOCKADDR, fromLen *int32) (int, error) {
proc, err := getProcAddr("ws2_32.dll", "recvfrom")
if err != nil {
return 0, err
}
var bufPtr uintptr
if len(buf) > 0 {
bufPtr = uintptr(unsafe.Pointer(&buf[0]))
}
var fromPtr uintptr
if from != nil {
fromPtr = uintptr(unsafe.Pointer(from))
}
var fromLenPtr uintptr
if fromLen != nil {
fromLenPtr = uintptr(unsafe.Pointer(fromLen))
}
r, _, _ := syscall.Syscall6(proc, 6,
uintptr(s),
bufPtr,
uintptr(len(buf)),
uintptr(flags),
fromPtr,
fromLenPtr,
)
if int32(r) == SOCKET_ERROR {
return 0, wsaLastErrorOr(syscall.EINVAL)
}
return int(r), nil
}
func GetAddrInfo(node, service string, hints *ADDRINFOW) (*ADDRINFOW, error) {
var data WSADATA
if err := WSAStartup(makeWord(2, 2), &data); err != nil {
return nil, err
}
defer func() {
_ = WSACleanup()
}()
proc, err := getProcAddr("ws2_32.dll", "GetAddrInfoW")
if err != nil {
return nil, err
}
var nodePtr *uint16
if node != "" {
nodePtr = syscall.StringToUTF16Ptr(node)
}
var servicePtr *uint16
if service != "" {
servicePtr = syscall.StringToUTF16Ptr(service)
}
var result *ADDRINFOW
r, _, _ := syscall.Syscall6(proc, 4,
uintptr(unsafe.Pointer(nodePtr)),
uintptr(unsafe.Pointer(servicePtr)),
uintptr(unsafe.Pointer(hints)),
uintptr(unsafe.Pointer(&result)),
0,
0,
)
if int32(r) != 0 {
return nil, fmt.Errorf("GetAddrInfoW failed: %d", int32(r))
}
if result == nil {
return nil, fmt.Errorf("GetAddrInfoW returned nil result")
}
return result, nil
}
func FreeAddrInfo(result *ADDRINFOW) error {
if result == nil {
return nil
}
proc, err := getProcAddr("ws2_32.dll", "FreeAddrInfoW")
if err != nil {
return err
}
syscall.Syscall(proc, 1, uintptr(unsafe.Pointer(result)), 0, 0)
return nil
}
func GetNameInfo(sa *SOCKADDR, saLen int32, flags int32) (string, string, error) {
if sa == nil {
return "", "", fmt.Errorf("sockaddr is nil")
}
var data WSADATA
if err := WSAStartup(makeWord(2, 2), &data); err != nil {
return "", "", err
}
defer func() {
_ = WSACleanup()
}()
proc, err := getProcAddr("ws2_32.dll", "GetNameInfoW")
if err != nil {
return "", "", err
}
host := make([]uint16, NI_MAXHOST)
service := make([]uint16, NI_MAXSERV)
r, _, _ := syscall.Syscall9(proc, 7,
uintptr(unsafe.Pointer(sa)),
uintptr(saLen),
uintptr(unsafe.Pointer(&host[0])),
uintptr(len(host)),
uintptr(unsafe.Pointer(&service[0])),
uintptr(len(service)),
uintptr(flags),
0,
0,
)
if int32(r) != 0 {
return "", "", fmt.Errorf("GetNameInfoW failed: %d", int32(r))
}
return syscall.UTF16ToString(host), syscall.UTF16ToString(service), nil
}
func SockaddrIPString(sa *SOCKADDR) (string, error) {
if sa == nil {
return "", fmt.Errorf("sockaddr is nil")
}
switch int(sa.Family) {
case AF_INET:
addr := (*SOCKADDR_IN)(unsafe.Pointer(sa))
return InetNtop(AF_INET, addr.Addr[:])
case AF_INET6:
addr := (*SOCKADDR_IN6)(unsafe.Pointer(sa))
return InetNtop(AF_INET6, addr.Addr[:])
default:
return "", fmt.Errorf("unsupported sockaddr family: %d", sa.Family)
}
}
func Htons(v uint16) uint16 {
return (v << 8) | (v >> 8)
}
func Ntohs(v uint16) uint16 {
return Htons(v)
}
func htons(v uint16) uint16 {
return Htons(v)
}

137
ws2_32typedef.go Normal file
View File

@ -0,0 +1,137 @@
package win32api
const (
WSADESCRIPTION_LEN = 256
WSASYS_STATUS_LEN = 128
)
const (
AF_UNSPEC = 0
AF_INET = 2
AF_INET6 = 23
)
const (
SOCK_STREAM = 1
SOCK_DGRAM = 2
)
const (
SOL_SOCKET = 0xffff
)
const (
SO_REUSEADDR = 0x0004
SO_ERROR = 0x1007
)
const (
IPPROTO_IP = 0
IPPROTO_TCP = 6
IPPROTO_UDP = 17
)
const (
AI_PASSIVE = 0x00000001
)
const (
GAA_FLAG_SKIP_UNICAST = 0x00000001
GAA_FLAG_SKIP_ANYCAST = 0x00000002
GAA_FLAG_SKIP_MULTICAST = 0x00000004
GAA_FLAG_SKIP_DNS_SERVER = 0x00000008
GAA_FLAG_INCLUDE_PREFIX = 0x00000010
GAA_FLAG_SKIP_FRIENDLY_NAME = 0x00000020
GAA_FLAG_INCLUDE_WINS_INFO = 0x00000040
GAA_FLAG_INCLUDE_GATEWAYS = 0x00000080
GAA_FLAG_INCLUDE_ALL_INTERFACES = 0x00000100
GAA_FLAG_INCLUDE_ALL_COMPARTMENTS = 0x00000200
GAA_FLAG_INCLUDE_TUNNEL_BINDINGORDER = 0x00000400
)
const (
NI_MAXHOST = 1025
NI_MAXSERV = 32
)
const (
NI_NUMERICHOST = 0x00000002
NI_NUMERICSERV = 0x00000008
)
type SOCKET uintptr
const (
INVALID_SOCKET SOCKET = ^SOCKET(0)
SOCKET_ERROR int32 = -1
)
const (
SD_RECEIVE = 0
SD_SEND = 1
SD_BOTH = 2
)
const (
SOMAXCONN int32 = 0x7fffffff
)
type WSADATA struct {
WVersion WORD
WHighVersion WORD
SzDescription [WSADESCRIPTION_LEN + 1]byte
SzSystemStatus [WSASYS_STATUS_LEN + 1]byte
IMaxSockets WORD
IMaxUdpDg WORD
LPVendorInfo *byte
}
type ADDRESS_FAMILY uint16
type SOCKADDR struct {
Family ADDRESS_FAMILY
Data [14]byte
}
type SOCKADDR_IN struct {
Family ADDRESS_FAMILY
Port uint16
Addr [4]byte
Zero [8]byte
}
type SOCKADDR_IN6 struct {
Family ADDRESS_FAMILY
Port uint16
Flowinfo uint32
Addr [16]byte
ScopeID uint32
}
type ADDRINFOW struct {
Flags int32
Family int32
Socktype int32
Protocol int32
Addrlen uintptr
Canonname *uint16
Addr *SOCKADDR
Next *ADDRINFOW
}
type AdapterAddressInfo struct {
IfIndex uint32
AdapterName string
FriendlyName string
Description string
DNSSuffix string
OperStatus uint32
Mtu uint32
MACAddress string
PhysicalAddressLength uint32
TransmitLinkSpeed uint64
ReceiveLinkSpeed uint64
UnicastIPs []string
DNSServers []string
Gateways []string
}

View File

@ -1,42 +1,74 @@
package win32api
import (
"errors"
"syscall"
"unsafe"
)
func WTSQueryUserToken(SessionId HANDLE, phToken *HANDLE) error {
wtsapi32, err := syscall.LoadLibrary("wtsapi32.dll")
func WTSQueryUserToken(SessionId DWORD, phToken *HANDLE) error {
WTGet, err := getProcAddr("wtsapi32.dll", "WTSQueryUserToken")
if err != nil {
return errors.New("Can't Load Wtsapi32 API")
return err
}
defer syscall.FreeLibrary(wtsapi32)
WTGet, err := syscall.GetProcAddress(syscall.Handle(wtsapi32), "WTSQueryUserToken")
if err != nil {
return errors.New("Can't Load WTSQueryUserToken API")
}
r, _, errno := syscall.Syscall(uintptr(WTGet), 2, uintptr(SessionId), uintptr(unsafe.Pointer(phToken)), 0)
r, _, errno := syscall.Syscall(WTGet, 2, uintptr(SessionId), uintptr(unsafe.Pointer(phToken)), 0)
if r == 0 {
return error(errno)
} else {
return nil
}
}
func WTSEnumerateSessions(hServer HANDLE, Reserved, Version DWORD, ppSessionInfo *HANDLE, pCount *int) error {
wtsapi32, err := syscall.LoadLibrary("wtsapi32.dll")
if err != nil {
return errors.New("Can't Load Wtsapi32 API")
}
defer syscall.FreeLibrary(wtsapi32)
WT, err := syscall.GetProcAddress(syscall.Handle(wtsapi32), "WTSEnumerateSessionsW")
if err != nil {
return errors.New("Can't Load WTSQueryUserToken API")
}
r, _, errno := syscall.Syscall6(uintptr(WT), 5, uintptr(hServer), uintptr(Reserved), uintptr(Version), uintptr(unsafe.Pointer(ppSessionInfo)), uintptr(unsafe.Pointer(pCount)), 0)
if r == 0 {
return error(errno)
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
return nil
}
func WTSEnumerateSessions(hServer HANDLE, Reserved, Version DWORD, ppSessionInfo *HANDLE, pCount *DWORD) error {
WT, err := getProcAddr("wtsapi32.dll", "WTSEnumerateSessionsW")
if err != nil {
return err
}
r, _, errno := syscall.Syscall6(WT, 5, uintptr(hServer), uintptr(Reserved), uintptr(Version), uintptr(unsafe.Pointer(ppSessionInfo)), uintptr(unsafe.Pointer(pCount)), 0)
if r == 0 {
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
return nil
}
func WTSFreeMemory(pMemory HANDLE) error {
wfm, err := getProcAddr("wtsapi32.dll", "WTSFreeMemory")
if err != nil {
return err
}
syscall.Syscall(wfm, 1, uintptr(pMemory), 0, 0)
return nil
}
func WTSQuerySessionInformation(hServer HANDLE, sessionID DWORD, wtsInfoClass WTS_INFO_CLASS, ppBuffer *HANDLE, pBytesReturned *DWORD) error {
if ppBuffer == nil || pBytesReturned == nil {
return syscall.EINVAL
}
proc, err := getProcAddr("wtsapi32.dll", "WTSQuerySessionInformationW")
if err != nil {
return err
}
r, _, errno := syscall.Syscall6(
proc,
5,
uintptr(hServer),
uintptr(sessionID),
uintptr(wtsInfoClass),
uintptr(unsafe.Pointer(ppBuffer)),
uintptr(unsafe.Pointer(pBytesReturned)),
0,
)
if r == 0 {
if errno != 0 {
return error(errno)
}
return syscall.EINVAL
}
return nil
}

148
wtsapi32_helper.go Normal file
View File

@ -0,0 +1,148 @@
package win32api
import (
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
type WTSSession struct {
SessionID DWORD
WinStationName string
State WTS_CONNECTSTATE_CLASS
}
type WTSSessionDetails struct {
SessionID DWORD
UserName string
DomainName string
WinStationName string
ConnectState WTS_CONNECTSTATE_CLASS
IsRemote bool
}
func EnumerateSessions() ([]WTSSession, error) {
var sessionInfo HANDLE
var sessionCount DWORD
if err := WTSEnumerateSessions(0, 0, 1, &sessionInfo, &sessionCount); err != nil {
return nil, err
}
if sessionInfo == 0 || sessionCount <= 0 {
return []WTSSession{}, nil
}
defer func() {
_ = WTSFreeMemory(sessionInfo)
}()
structSize := unsafe.Sizeof(WTS_SESSION_INFO{})
current := uintptr(sessionInfo)
sessions := make([]WTSSession, 0, int(sessionCount))
for i := DWORD(0); i < sessionCount; i++ {
info := (*WTS_SESSION_INFO)(unsafe.Pointer(current))
name := ""
if info.WinStationName != nil {
name = windows.UTF16PtrToString(info.WinStationName)
}
sessions = append(sessions, WTSSession{
SessionID: info.SessionID,
WinStationName: name,
State: info.State,
})
current += structSize
}
return sessions, nil
}
func ActiveSessionID() (DWORD, error) {
sessions, err := EnumerateSessions()
if err == nil {
for _, session := range sessions {
if session.State == WTSActive {
return session.SessionID, nil
}
}
}
sessionID, sessionErr := WTSGetActiveConsoleSessionId()
if sessionID != WTS_CURRENT_SESSION {
return sessionID, nil
}
if err != nil {
return sessionID, fmt.Errorf("enumerate sessions: %w; active console session fallback: %v", err, sessionErr)
}
if sessionErr != nil {
return sessionID, fmt.Errorf("get active console session id: %w", sessionErr)
}
return sessionID, fmt.Errorf("WTSGetActiveConsoleSessionId returned invalid session id")
}
func WTSQuerySessionString(hServer HANDLE, sessionID DWORD, infoClass WTS_INFO_CLASS) (string, error) {
var buffer HANDLE
var bytesReturned DWORD
if err := WTSQuerySessionInformation(hServer, sessionID, infoClass, &buffer, &bytesReturned); err != nil {
return "", err
}
if buffer == 0 || bytesReturned == 0 {
return "", nil
}
defer func() {
_ = WTSFreeMemory(buffer)
}()
return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(buffer))), nil
}
func WTSQuerySessionDWORD(hServer HANDLE, sessionID DWORD, infoClass WTS_INFO_CLASS) (DWORD, error) {
var buffer HANDLE
var bytesReturned DWORD
if err := WTSQuerySessionInformation(hServer, sessionID, infoClass, &buffer, &bytesReturned); err != nil {
return 0, err
}
if buffer == 0 || bytesReturned < DWORD(unsafe.Sizeof(DWORD(0))) {
return 0, syscall.EINVAL
}
defer func() {
_ = WTSFreeMemory(buffer)
}()
return *(*DWORD)(unsafe.Pointer(buffer)), nil
}
func GetSessionDetails(hServer HANDLE, sessionID DWORD) (WTSSessionDetails, error) {
userName, err := WTSQuerySessionString(hServer, sessionID, WTSUserName)
if err != nil {
return WTSSessionDetails{}, err
}
domainName, err := WTSQuerySessionString(hServer, sessionID, WTSDomainName)
if err != nil {
return WTSSessionDetails{}, err
}
winStationName, err := WTSQuerySessionString(hServer, sessionID, WTSWinStationName)
if err != nil {
return WTSSessionDetails{}, err
}
stateRaw, err := WTSQuerySessionDWORD(hServer, sessionID, WTSConnectState)
if err != nil {
return WTSSessionDetails{}, err
}
isRemote := false
if remoteRaw, remoteErr := WTSQuerySessionDWORD(hServer, sessionID, WTSIsRemoteSession); remoteErr == nil {
isRemote = remoteRaw != 0
}
return WTSSessionDetails{
SessionID: sessionID,
UserName: userName,
DomainName: domainName,
WinStationName: winStationName,
ConnectState: WTS_CONNECTSTATE_CLASS(stateRaw),
IsRemote: isRemote,
}, nil
}
func CurrentProcessSessionID() (DWORD, error) {
return ProcessIdToSessionId(GetCurrentProcessId())
}