Skip to content

Commit 56fd482

Browse files
committed
Add winwlanapi
1 parent 70388ca commit 56fd482

File tree

4 files changed

+391
-0
lines changed

4 files changed

+391
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//go:build windows
2+
3+
package winwlanapi
4+
5+
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go
6+
7+
// https://learn.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlanopenhandle
8+
//sys wlanOpenHandle(clientVersion uint32, reserved uintptr, negotiatedVersion *uint32, clientHandle *windows.Handle) (ret uint32) = wlanapi.WlanOpenHandle
9+
10+
// https://learn.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlanclosehandle
11+
//sys wlanCloseHandle(clientHandle windows.Handle, reserved uintptr) (ret uint32) = wlanapi.WlanCloseHandle
12+
13+
// https://learn.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlanenuminterfaces
14+
//sys wlanEnumInterfaces(clientHandle windows.Handle, reserved uintptr, interfaceList **InterfaceInfoList) (ret uint32) = wlanapi.WlanEnumInterfaces
15+
16+
// https://learn.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlanqueryinterface
17+
//sys wlanQueryInterface(clientHandle windows.Handle, interfaceGuid *windows.GUID, opCode uint32, reserved uintptr, dataSize *uint32, data *uintptr, opcodeValueType *uint32) (ret uint32) = wlanapi.WlanQueryInterface
18+
19+
// https://learn.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlanfreememory
20+
//sys wlanFreeMemory(memory uintptr) = wlanapi.WlanFreeMemory
21+
22+
// https://learn.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlanregisternotification
23+
//sys wlanRegisterNotification(clientHandle windows.Handle, notificationSource uint32, ignoreDuplicate bool, callback uintptr, callbackContext uintptr, reserved uintptr, prevNotificationSource *uint32) (ret uint32) = wlanapi.WlanRegisterNotification

common/winwlanapi/wlanapi.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
//go:build windows
2+
3+
package winwlanapi
4+
5+
import (
6+
"os"
7+
"unsafe"
8+
9+
"golang.org/x/sys/windows"
10+
)
11+
12+
const (
13+
ClientVersion2 = 2
14+
15+
// InterfaceOpcode for WlanQueryInterface
16+
IntfOpcodeCurrentConnection = 7
17+
18+
// NotificationSource for WlanRegisterNotification
19+
NotificationSourceNone = 0
20+
NotificationSourceACM = 0x00000008
21+
22+
// NotificationACM codes
23+
NotificationACMConnectionComplete = 10
24+
NotificationACMDisconnected = 21
25+
26+
// InterfaceState
27+
InterfaceStateNotReady = 0
28+
InterfaceStateConnected = 1
29+
InterfaceStateAdHocNetworkFormed = 2
30+
InterfaceStateDisconnecting = 3
31+
InterfaceStateDisconnected = 4
32+
InterfaceStateAssociating = 5
33+
InterfaceStateDiscovering = 6
34+
InterfaceStateAuthenticating = 7
35+
36+
// DOT11_SSID
37+
Dot11SSIDMaxLength = 32
38+
)
39+
40+
type Dot11SSID struct {
41+
Length uint32
42+
SSID [Dot11SSIDMaxLength]byte
43+
}
44+
45+
type Dot11MacAddress [6]byte
46+
47+
type AssociationAttributes struct {
48+
SSID Dot11SSID
49+
BSSType uint32
50+
BSSID Dot11MacAddress
51+
_ [2]byte // padding for 4-byte alignment
52+
PhyType uint32
53+
PhyIndex uint32
54+
SignalQuality uint32
55+
RxRate uint32
56+
TxRate uint32
57+
}
58+
59+
type SecurityAttributes struct {
60+
SecurityEnabled int32 // Windows BOOL is 4 bytes
61+
OneXEnabled int32
62+
AuthAlgorithm uint32
63+
CipherAlgorithm uint32
64+
}
65+
66+
type ConnectionAttributes struct {
67+
InterfaceState uint32
68+
ConnectionMode uint32
69+
ProfileName [256]uint16
70+
AssociationAttributes AssociationAttributes
71+
SecurityAttributes SecurityAttributes
72+
}
73+
74+
type InterfaceInfo struct {
75+
InterfaceGUID windows.GUID
76+
InterfaceDescription [256]uint16
77+
InterfaceState uint32
78+
}
79+
80+
type InterfaceInfoList struct {
81+
NumberOfItems uint32
82+
Index uint32
83+
InterfaceInfo [1]InterfaceInfo
84+
}
85+
86+
type NotificationData struct {
87+
NotificationSource uint32
88+
NotificationCode uint32
89+
InterfaceGUID windows.GUID
90+
DataSize uint32
91+
Data uintptr
92+
}
93+
94+
// NotificationCallback is the type for notification callback functions.
95+
// Use syscall.NewCallback to create a callback from a Go function.
96+
type NotificationCallback func(data *NotificationData, context uintptr) uintptr
97+
98+
func OpenHandle() (windows.Handle, error) {
99+
var negotiatedVersion uint32
100+
var handle windows.Handle
101+
ret := wlanOpenHandle(ClientVersion2, 0, &negotiatedVersion, &handle)
102+
if ret != 0 {
103+
return 0, os.NewSyscallError("WlanOpenHandle", windows.Errno(ret))
104+
}
105+
return handle, nil
106+
}
107+
108+
func CloseHandle(handle windows.Handle) error {
109+
ret := wlanCloseHandle(handle, 0)
110+
if ret != 0 {
111+
return os.NewSyscallError("WlanCloseHandle", windows.Errno(ret))
112+
}
113+
return nil
114+
}
115+
116+
func EnumInterfaces(handle windows.Handle) ([]InterfaceInfo, error) {
117+
var list *InterfaceInfoList
118+
ret := wlanEnumInterfaces(handle, 0, &list)
119+
if ret != 0 {
120+
return nil, os.NewSyscallError("WlanEnumInterfaces", windows.Errno(ret))
121+
}
122+
defer wlanFreeMemory(uintptr(unsafe.Pointer(list)))
123+
124+
if list.NumberOfItems == 0 {
125+
return nil, nil
126+
}
127+
128+
interfaces := unsafe.Slice(&list.InterfaceInfo[0], list.NumberOfItems)
129+
result := make([]InterfaceInfo, list.NumberOfItems)
130+
copy(result, interfaces)
131+
return result, nil
132+
}
133+
134+
func QueryCurrentConnection(handle windows.Handle, interfaceGUID *windows.GUID) (*ConnectionAttributes, error) {
135+
var dataSize uint32
136+
var data uintptr
137+
var opcodeValueType uint32
138+
139+
ret := wlanQueryInterface(handle, interfaceGUID, IntfOpcodeCurrentConnection, 0, &dataSize, &data, &opcodeValueType)
140+
if ret != 0 {
141+
return nil, os.NewSyscallError("WlanQueryInterface", windows.Errno(ret))
142+
}
143+
defer wlanFreeMemory(data)
144+
145+
attrs := (*ConnectionAttributes)(unsafe.Pointer(data))
146+
result := *attrs
147+
return &result, nil
148+
}
149+
150+
func RegisterNotification(handle windows.Handle, notificationSource uint32, callback uintptr, context uintptr) error {
151+
var prevSource uint32
152+
ret := wlanRegisterNotification(handle, notificationSource, false, callback, context, 0, &prevSource)
153+
if ret != 0 {
154+
return os.NewSyscallError("WlanRegisterNotification", windows.Errno(ret))
155+
}
156+
return nil
157+
}
158+
159+
func UnregisterNotification(handle windows.Handle) error {
160+
return RegisterNotification(handle, NotificationSourceNone, 0, 0)
161+
}

common/winwlanapi/wlanapi_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
//go:build windows
2+
3+
package winwlanapi
4+
5+
import (
6+
"testing"
7+
)
8+
9+
func TestOpenHandle(t *testing.T) {
10+
handle, err := OpenHandle()
11+
if err != nil {
12+
t.Skipf("WLAN service not available: %v", err)
13+
}
14+
defer CloseHandle(handle)
15+
16+
if handle == 0 {
17+
t.Error("expected non-zero handle")
18+
}
19+
}
20+
21+
func TestEnumInterfaces(t *testing.T) {
22+
handle, err := OpenHandle()
23+
if err != nil {
24+
t.Skipf("WLAN service not available: %v", err)
25+
}
26+
defer CloseHandle(handle)
27+
28+
interfaces, err := EnumInterfaces(handle)
29+
if err != nil {
30+
t.Fatalf("EnumInterfaces failed: %v", err)
31+
}
32+
33+
t.Logf("Found %d WLAN interface(s)", len(interfaces))
34+
for i, iface := range interfaces {
35+
description := windowsUTF16ToString(iface.InterfaceDescription[:])
36+
t.Logf("Interface %d: %s (state=%d)", i, description, iface.InterfaceState)
37+
}
38+
}
39+
40+
func TestQueryCurrentConnection(t *testing.T) {
41+
handle, err := OpenHandle()
42+
if err != nil {
43+
t.Skipf("WLAN service not available: %v", err)
44+
}
45+
defer CloseHandle(handle)
46+
47+
interfaces, err := EnumInterfaces(handle)
48+
if err != nil {
49+
t.Fatalf("EnumInterfaces failed: %v", err)
50+
}
51+
52+
if len(interfaces) == 0 {
53+
t.Skip("no WLAN interfaces available")
54+
}
55+
56+
for _, iface := range interfaces {
57+
if iface.InterfaceState != InterfaceStateConnected {
58+
continue
59+
}
60+
61+
guid := iface.InterfaceGUID
62+
attrs, err := QueryCurrentConnection(handle, &guid)
63+
if err != nil {
64+
t.Errorf("QueryCurrentConnection failed: %v", err)
65+
continue
66+
}
67+
68+
ssidLen := attrs.AssociationAttributes.SSID.Length
69+
if ssidLen > 0 && ssidLen <= Dot11SSIDMaxLength {
70+
ssid := string(attrs.AssociationAttributes.SSID.SSID[:ssidLen])
71+
bssid := attrs.AssociationAttributes.BSSID
72+
t.Logf("Connected to SSID: %q, BSSID: %02X:%02X:%02X:%02X:%02X:%02X",
73+
ssid, bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5])
74+
}
75+
return
76+
}
77+
78+
t.Log("no connected WLAN interface found")
79+
}
80+
81+
func TestCloseHandle(t *testing.T) {
82+
handle, err := OpenHandle()
83+
if err != nil {
84+
t.Skipf("WLAN service not available: %v", err)
85+
}
86+
87+
err = CloseHandle(handle)
88+
if err != nil {
89+
t.Errorf("CloseHandle failed: %v", err)
90+
}
91+
92+
// closing again should fail
93+
err = CloseHandle(handle)
94+
if err == nil {
95+
t.Error("expected error when closing already closed handle")
96+
}
97+
}
98+
99+
func windowsUTF16ToString(s []uint16) string {
100+
for i, c := range s {
101+
if c == 0 {
102+
return string(utf16ToRunes(s[:i]))
103+
}
104+
}
105+
return string(utf16ToRunes(s))
106+
}
107+
108+
func utf16ToRunes(s []uint16) []rune {
109+
runes := make([]rune, 0, len(s))
110+
for i := 0; i < len(s); i++ {
111+
if s[i] >= 0xD800 && s[i] <= 0xDBFF && i+1 < len(s) && s[i+1] >= 0xDC00 && s[i+1] <= 0xDFFF {
112+
runes = append(runes, rune((int(s[i])-0xD800)<<10+(int(s[i+1])-0xDC00)+0x10000))
113+
i++
114+
} else {
115+
runes = append(runes, rune(s[i]))
116+
}
117+
}
118+
return runes
119+
}

common/winwlanapi/zsyscall_windows.go

Lines changed: 88 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)