wifi_linux_iwd.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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 iwdMonitor struct {
  11. conn *dbus.Conn
  12. callback func(adapter.WIFIState)
  13. cancel context.CancelFunc
  14. signalChan chan *dbus.Signal
  15. }
  16. func newIWDMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
  17. conn, err := dbus.ConnectSystemBus()
  18. if err != nil {
  19. return nil, err
  20. }
  21. iwdObj := conn.Object("net.connman.iwd", "/")
  22. ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
  23. defer cancel()
  24. call := iwdObj.CallWithContext(ctx, "org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0)
  25. if call.Err != nil {
  26. conn.Close()
  27. return nil, call.Err
  28. }
  29. return &iwdMonitor{conn: conn, callback: callback}, nil
  30. }
  31. func (m *iwdMonitor) ReadWIFIState() adapter.WIFIState {
  32. ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
  33. defer cancel()
  34. iwdObj := m.conn.Object("net.connman.iwd", "/")
  35. var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
  36. err := iwdObj.CallWithContext(ctx, "org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0).Store(&objects)
  37. if err != nil {
  38. return adapter.WIFIState{}
  39. }
  40. for _, interfaces := range objects {
  41. stationProps, hasStation := interfaces["net.connman.iwd.Station"]
  42. if !hasStation {
  43. continue
  44. }
  45. stateVariant, hasState := stationProps["State"]
  46. if !hasState {
  47. continue
  48. }
  49. state, ok := stateVariant.Value().(string)
  50. if !ok || state != "connected" {
  51. continue
  52. }
  53. connectedNetworkVariant, hasNetwork := stationProps["ConnectedNetwork"]
  54. if !hasNetwork {
  55. continue
  56. }
  57. networkPath, ok := connectedNetworkVariant.Value().(dbus.ObjectPath)
  58. if !ok || networkPath == "/" {
  59. continue
  60. }
  61. networkInterfaces, hasNetworkPath := objects[networkPath]
  62. if !hasNetworkPath {
  63. continue
  64. }
  65. networkProps, hasNetworkInterface := networkInterfaces["net.connman.iwd.Network"]
  66. if !hasNetworkInterface {
  67. continue
  68. }
  69. nameVariant, hasName := networkProps["Name"]
  70. if !hasName {
  71. continue
  72. }
  73. ssid, ok := nameVariant.Value().(string)
  74. if !ok {
  75. continue
  76. }
  77. connectedBSSVariant, hasBSS := stationProps["ConnectedAccessPoint"]
  78. if !hasBSS {
  79. return adapter.WIFIState{SSID: ssid}
  80. }
  81. bssPath, ok := connectedBSSVariant.Value().(dbus.ObjectPath)
  82. if !ok || bssPath == "/" {
  83. return adapter.WIFIState{SSID: ssid}
  84. }
  85. bssInterfaces, hasBSSPath := objects[bssPath]
  86. if !hasBSSPath {
  87. return adapter.WIFIState{SSID: ssid}
  88. }
  89. bssProps, hasBSSInterface := bssInterfaces["net.connman.iwd.BasicServiceSet"]
  90. if !hasBSSInterface {
  91. return adapter.WIFIState{SSID: ssid}
  92. }
  93. addressVariant, hasAddress := bssProps["Address"]
  94. if !hasAddress {
  95. return adapter.WIFIState{SSID: ssid}
  96. }
  97. bssid, ok := addressVariant.Value().(string)
  98. if !ok {
  99. return adapter.WIFIState{SSID: ssid}
  100. }
  101. return adapter.WIFIState{
  102. SSID: ssid,
  103. BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")),
  104. }
  105. }
  106. return adapter.WIFIState{}
  107. }
  108. func (m *iwdMonitor) Start() error {
  109. if m.callback == nil {
  110. return nil
  111. }
  112. ctx, cancel := context.WithCancel(context.Background())
  113. m.cancel = cancel
  114. m.signalChan = make(chan *dbus.Signal, 10)
  115. m.conn.Signal(m.signalChan)
  116. err := m.conn.AddMatchSignal(
  117. dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
  118. dbus.WithMatchSender("net.connman.iwd"),
  119. )
  120. if err != nil {
  121. return err
  122. }
  123. state := m.ReadWIFIState()
  124. go m.monitorSignals(ctx, m.signalChan, state)
  125. m.callback(state)
  126. return nil
  127. }
  128. func (m *iwdMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) {
  129. for {
  130. select {
  131. case <-ctx.Done():
  132. return
  133. case signal, ok := <-signalChan:
  134. if !ok {
  135. return
  136. }
  137. if signal.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" {
  138. state := m.ReadWIFIState()
  139. if state != lastState {
  140. lastState = state
  141. m.callback(state)
  142. }
  143. }
  144. }
  145. }
  146. }
  147. func (m *iwdMonitor) Close() error {
  148. if m.cancel != nil {
  149. m.cancel()
  150. }
  151. if m.signalChan != nil {
  152. m.conn.RemoveSignal(m.signalChan)
  153. close(m.signalChan)
  154. }
  155. if m.conn != nil {
  156. m.conn.RemoveMatchSignal(
  157. dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
  158. dbus.WithMatchSender("net.connman.iwd"),
  159. )
  160. return m.conn.Close()
  161. }
  162. return nil
  163. }