wifi_linux_nm.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. //go:build linux
  2. package settings
  3. import (
  4. "context"
  5. "strings"
  6. "time"
  7. "github.com/sagernet/sing-box/adapter"
  8. "github.com/godbus/dbus/v5"
  9. )
  10. type networkManagerMonitor struct {
  11. conn *dbus.Conn
  12. callback func(adapter.WIFIState)
  13. cancel context.CancelFunc
  14. signalChan chan *dbus.Signal
  15. }
  16. func newNetworkManagerMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
  17. conn, err := dbus.ConnectSystemBus()
  18. if err != nil {
  19. return nil, err
  20. }
  21. nmObj := conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
  22. ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
  23. defer cancel()
  24. var state uint32
  25. err = nmObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager", "State").Store(&state)
  26. if err != nil {
  27. conn.Close()
  28. return nil, err
  29. }
  30. return &networkManagerMonitor{conn: conn, callback: callback}, nil
  31. }
  32. func (m *networkManagerMonitor) ReadWIFIState() adapter.WIFIState {
  33. ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
  34. defer cancel()
  35. nmObj := m.conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
  36. var activeConnectionPaths []dbus.ObjectPath
  37. err := nmObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager", "ActiveConnections").Store(&activeConnectionPaths)
  38. if err != nil || len(activeConnectionPaths) == 0 {
  39. return adapter.WIFIState{}
  40. }
  41. for _, connectionPath := range activeConnectionPaths {
  42. connObj := m.conn.Object("org.freedesktop.NetworkManager", connectionPath)
  43. var devicePaths []dbus.ObjectPath
  44. err = connObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Connection.Active", "Devices").Store(&devicePaths)
  45. if err != nil || len(devicePaths) == 0 {
  46. continue
  47. }
  48. for _, devicePath := range devicePaths {
  49. deviceObj := m.conn.Object("org.freedesktop.NetworkManager", devicePath)
  50. var deviceType uint32
  51. err = deviceObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Device", "DeviceType").Store(&deviceType)
  52. if err != nil || deviceType != 2 {
  53. continue
  54. }
  55. var accessPointPath dbus.ObjectPath
  56. err = deviceObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Device.Wireless", "ActiveAccessPoint").Store(&accessPointPath)
  57. if err != nil || accessPointPath == "/" {
  58. continue
  59. }
  60. apObj := m.conn.Object("org.freedesktop.NetworkManager", accessPointPath)
  61. var ssidBytes []byte
  62. err = apObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.AccessPoint", "Ssid").Store(&ssidBytes)
  63. if err != nil {
  64. continue
  65. }
  66. var hwAddress string
  67. err = apObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.AccessPoint", "HwAddress").Store(&hwAddress)
  68. if err != nil {
  69. continue
  70. }
  71. ssid := strings.TrimSpace(string(ssidBytes))
  72. if ssid == "" {
  73. continue
  74. }
  75. return adapter.WIFIState{
  76. SSID: ssid,
  77. BSSID: strings.ToUpper(strings.ReplaceAll(hwAddress, ":", "")),
  78. }
  79. }
  80. }
  81. return adapter.WIFIState{}
  82. }
  83. func (m *networkManagerMonitor) Start() error {
  84. if m.callback == nil {
  85. return nil
  86. }
  87. ctx, cancel := context.WithCancel(context.Background())
  88. m.cancel = cancel
  89. m.signalChan = make(chan *dbus.Signal, 10)
  90. m.conn.Signal(m.signalChan)
  91. err := m.conn.AddMatchSignal(
  92. dbus.WithMatchSender("org.freedesktop.NetworkManager"),
  93. dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
  94. )
  95. if err != nil {
  96. return err
  97. }
  98. state := m.ReadWIFIState()
  99. go m.monitorSignals(ctx, m.signalChan, state)
  100. m.callback(state)
  101. return nil
  102. }
  103. func (m *networkManagerMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) {
  104. for {
  105. select {
  106. case <-ctx.Done():
  107. return
  108. case signal, ok := <-signalChan:
  109. if !ok {
  110. return
  111. }
  112. if signal.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" {
  113. state := m.ReadWIFIState()
  114. if state != lastState {
  115. lastState = state
  116. m.callback(state)
  117. }
  118. }
  119. }
  120. }
  121. }
  122. func (m *networkManagerMonitor) Close() error {
  123. if m.cancel != nil {
  124. m.cancel()
  125. }
  126. if m.signalChan != nil {
  127. m.conn.RemoveSignal(m.signalChan)
  128. close(m.signalChan)
  129. }
  130. if m.conn != nil {
  131. m.conn.RemoveMatchSignal(
  132. dbus.WithMatchSender("org.freedesktop.NetworkManager"),
  133. dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
  134. )
  135. return m.conn.Close()
  136. }
  137. return nil
  138. }