wifi_linux_connman.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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 connmanMonitor struct {
  11. conn *dbus.Conn
  12. callback func(adapter.WIFIState)
  13. cancel context.CancelFunc
  14. signalChan chan *dbus.Signal
  15. }
  16. func newConnManMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
  17. conn, err := dbus.ConnectSystemBus()
  18. if err != nil {
  19. return nil, err
  20. }
  21. cmObj := conn.Object("net.connman", "/")
  22. ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
  23. defer cancel()
  24. call := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0)
  25. if call.Err != nil {
  26. conn.Close()
  27. return nil, call.Err
  28. }
  29. return &connmanMonitor{conn: conn, callback: callback}, nil
  30. }
  31. func (m *connmanMonitor) ReadWIFIState() adapter.WIFIState {
  32. ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
  33. defer cancel()
  34. cmObj := m.conn.Object("net.connman", "/")
  35. var services []interface{}
  36. err := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0).Store(&services)
  37. if err != nil {
  38. return adapter.WIFIState{}
  39. }
  40. for _, service := range services {
  41. servicePair, ok := service.([]interface{})
  42. if !ok || len(servicePair) != 2 {
  43. continue
  44. }
  45. serviceProps, ok := servicePair[1].(map[string]dbus.Variant)
  46. if !ok {
  47. continue
  48. }
  49. typeVariant, hasType := serviceProps["Type"]
  50. if !hasType {
  51. continue
  52. }
  53. serviceType, ok := typeVariant.Value().(string)
  54. if !ok || serviceType != "wifi" {
  55. continue
  56. }
  57. stateVariant, hasState := serviceProps["State"]
  58. if !hasState {
  59. continue
  60. }
  61. state, ok := stateVariant.Value().(string)
  62. if !ok || (state != "online" && state != "ready") {
  63. continue
  64. }
  65. nameVariant, hasName := serviceProps["Name"]
  66. if !hasName {
  67. continue
  68. }
  69. ssid, ok := nameVariant.Value().(string)
  70. if !ok || ssid == "" {
  71. continue
  72. }
  73. bssidVariant, hasBSSID := serviceProps["BSSID"]
  74. if !hasBSSID {
  75. return adapter.WIFIState{SSID: ssid}
  76. }
  77. bssid, ok := bssidVariant.Value().(string)
  78. if !ok {
  79. return adapter.WIFIState{SSID: ssid}
  80. }
  81. return adapter.WIFIState{
  82. SSID: ssid,
  83. BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")),
  84. }
  85. }
  86. return adapter.WIFIState{}
  87. }
  88. func (m *connmanMonitor) Start() error {
  89. if m.callback == nil {
  90. return nil
  91. }
  92. ctx, cancel := context.WithCancel(context.Background())
  93. m.cancel = cancel
  94. m.signalChan = make(chan *dbus.Signal, 10)
  95. m.conn.Signal(m.signalChan)
  96. err := m.conn.AddMatchSignal(
  97. dbus.WithMatchInterface("net.connman.Service"),
  98. dbus.WithMatchSender("net.connman"),
  99. )
  100. if err != nil {
  101. return err
  102. }
  103. state := m.ReadWIFIState()
  104. go m.monitorSignals(ctx, m.signalChan, state)
  105. m.callback(state)
  106. return nil
  107. }
  108. func (m *connmanMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) {
  109. for {
  110. select {
  111. case <-ctx.Done():
  112. return
  113. case signal, ok := <-signalChan:
  114. if !ok {
  115. return
  116. }
  117. // godbus Signal.Name uses "interface.member" format (e.g. "net.connman.Service.PropertyChanged"),
  118. // not just the member name. This differs from the D-Bus signal member in the match rule.
  119. if signal.Name == "net.connman.Service.PropertyChanged" {
  120. state := m.ReadWIFIState()
  121. if state != lastState {
  122. lastState = state
  123. m.callback(state)
  124. }
  125. }
  126. }
  127. }
  128. }
  129. func (m *connmanMonitor) Close() error {
  130. if m.cancel != nil {
  131. m.cancel()
  132. }
  133. if m.signalChan != nil {
  134. m.conn.RemoveSignal(m.signalChan)
  135. close(m.signalChan)
  136. }
  137. if m.conn != nil {
  138. m.conn.RemoveMatchSignal(
  139. dbus.WithMatchInterface("net.connman.Service"),
  140. dbus.WithMatchSender("net.connman"),
  141. )
  142. return m.conn.Close()
  143. }
  144. return nil
  145. }