wifi_windows.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. //go:build windows
  2. package settings
  3. import (
  4. "context"
  5. "fmt"
  6. "strings"
  7. "sync"
  8. "syscall"
  9. "github.com/sagernet/sing-box/adapter"
  10. "github.com/sagernet/sing/common/winwlanapi"
  11. "golang.org/x/sys/windows"
  12. )
  13. type windowsWIFIMonitor struct {
  14. handle windows.Handle
  15. callback func(adapter.WIFIState)
  16. cancel context.CancelFunc
  17. lastState adapter.WIFIState
  18. mutex sync.Mutex
  19. }
  20. func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
  21. handle, err := winwlanapi.OpenHandle()
  22. if err != nil {
  23. return nil, err
  24. }
  25. interfaces, err := winwlanapi.EnumInterfaces(handle)
  26. if err != nil {
  27. winwlanapi.CloseHandle(handle)
  28. return nil, err
  29. }
  30. if len(interfaces) == 0 {
  31. winwlanapi.CloseHandle(handle)
  32. return nil, fmt.Errorf("no wireless interfaces found")
  33. }
  34. return &windowsWIFIMonitor{
  35. handle: handle,
  36. callback: callback,
  37. }, nil
  38. }
  39. func (m *windowsWIFIMonitor) ReadWIFIState() adapter.WIFIState {
  40. interfaces, err := winwlanapi.EnumInterfaces(m.handle)
  41. if err != nil || len(interfaces) == 0 {
  42. return adapter.WIFIState{}
  43. }
  44. for _, iface := range interfaces {
  45. if iface.InterfaceState != winwlanapi.InterfaceStateConnected {
  46. continue
  47. }
  48. guid := iface.InterfaceGUID
  49. attrs, err := winwlanapi.QueryCurrentConnection(m.handle, &guid)
  50. if err != nil {
  51. continue
  52. }
  53. ssidLength := attrs.AssociationAttributes.SSID.Length
  54. if ssidLength == 0 || ssidLength > winwlanapi.Dot11SSIDMaxLength {
  55. continue
  56. }
  57. ssid := string(attrs.AssociationAttributes.SSID.SSID[:ssidLength])
  58. bssid := formatBSSID(attrs.AssociationAttributes.BSSID)
  59. return adapter.WIFIState{
  60. SSID: strings.TrimSpace(ssid),
  61. BSSID: bssid,
  62. }
  63. }
  64. return adapter.WIFIState{}
  65. }
  66. func formatBSSID(mac winwlanapi.Dot11MacAddress) string {
  67. return fmt.Sprintf("%02X%02X%02X%02X%02X%02X",
  68. mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])
  69. }
  70. func (m *windowsWIFIMonitor) Start() error {
  71. if m.callback == nil {
  72. return nil
  73. }
  74. ctx, cancel := context.WithCancel(context.Background())
  75. m.cancel = cancel
  76. m.lastState = m.ReadWIFIState()
  77. callbackFunc := func(data *winwlanapi.NotificationData, callbackContext uintptr) uintptr {
  78. if data.NotificationSource != winwlanapi.NotificationSourceACM {
  79. return 0
  80. }
  81. switch data.NotificationCode {
  82. case winwlanapi.NotificationACMConnectionComplete,
  83. winwlanapi.NotificationACMDisconnected:
  84. m.checkAndNotify()
  85. }
  86. return 0
  87. }
  88. callbackPointer := syscall.NewCallback(callbackFunc)
  89. err := winwlanapi.RegisterNotification(m.handle, winwlanapi.NotificationSourceACM, callbackPointer, 0)
  90. if err != nil {
  91. cancel()
  92. return err
  93. }
  94. go func() {
  95. <-ctx.Done()
  96. }()
  97. m.callback(m.lastState)
  98. return nil
  99. }
  100. func (m *windowsWIFIMonitor) checkAndNotify() {
  101. m.mutex.Lock()
  102. defer m.mutex.Unlock()
  103. state := m.ReadWIFIState()
  104. if state != m.lastState {
  105. m.lastState = state
  106. if m.callback != nil {
  107. m.callback(state)
  108. }
  109. }
  110. }
  111. func (m *windowsWIFIMonitor) Close() error {
  112. if m.cancel != nil {
  113. m.cancel()
  114. }
  115. winwlanapi.UnregisterNotification(m.handle)
  116. return winwlanapi.CloseHandle(m.handle)
  117. }