command_server.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package libbox
  2. import (
  3. "encoding/binary"
  4. "net"
  5. "os"
  6. "path/filepath"
  7. "sync"
  8. "github.com/sagernet/sing-box/common/urltest"
  9. "github.com/sagernet/sing-box/experimental/clashapi"
  10. "github.com/sagernet/sing-box/log"
  11. "github.com/sagernet/sing/common"
  12. "github.com/sagernet/sing/common/debug"
  13. E "github.com/sagernet/sing/common/exceptions"
  14. "github.com/sagernet/sing/common/observable"
  15. "github.com/sagernet/sing/common/x/list"
  16. "github.com/sagernet/sing/service"
  17. )
  18. type CommandServer struct {
  19. listener net.Listener
  20. handler CommandServerHandler
  21. access sync.Mutex
  22. savedLines list.List[string]
  23. maxLines int
  24. subscriber *observable.Subscriber[string]
  25. observer *observable.Observer[string]
  26. service *BoxService
  27. // These channels only work with a single client. if multi-client support is needed, replace with Subscriber/Observer
  28. urlTestUpdate chan struct{}
  29. modeUpdate chan struct{}
  30. logReset chan struct{}
  31. events chan myEvent
  32. closedConnections []Connection
  33. }
  34. type CommandServerHandler interface {
  35. ServiceReload() error
  36. PostServiceClose()
  37. GetSystemProxyStatus() *SystemProxyStatus
  38. SetSystemProxyEnabled(isEnabled bool) error
  39. }
  40. func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer {
  41. server := &CommandServer{
  42. handler: handler,
  43. maxLines: int(maxLines),
  44. subscriber: observable.NewSubscriber[string](128),
  45. urlTestUpdate: make(chan struct{}, 1),
  46. modeUpdate: make(chan struct{}, 1),
  47. logReset: make(chan struct{}, 1),
  48. events: make(chan myEvent, 8),
  49. }
  50. server.observer = observable.NewObserver[string](server.subscriber, 64)
  51. return server
  52. }
  53. func (s *CommandServer) SetService(newService *BoxService) {
  54. if newService != nil {
  55. service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
  56. newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
  57. newService.platformInterface.openURLFunc = func(url string) {
  58. select {
  59. case s.events <- &eventOpenURL{URL: url}:
  60. default:
  61. }
  62. }
  63. }
  64. s.service = newService
  65. s.notifyURLTestUpdate()
  66. }
  67. func (s *CommandServer) notifyURLTestUpdate() {
  68. select {
  69. case s.urlTestUpdate <- struct{}{}:
  70. default:
  71. }
  72. }
  73. func (s *CommandServer) Start() error {
  74. if !sTVOS {
  75. return s.listenUNIX()
  76. } else {
  77. return s.listenTCP()
  78. }
  79. }
  80. func (s *CommandServer) listenUNIX() error {
  81. sockPath := filepath.Join(sBasePath, "command.sock")
  82. os.Remove(sockPath)
  83. listener, err := net.ListenUnix("unix", &net.UnixAddr{
  84. Name: sockPath,
  85. Net: "unix",
  86. })
  87. if err != nil {
  88. return E.Cause(err, "listen ", sockPath)
  89. }
  90. err = os.Chown(sockPath, sUserID, sGroupID)
  91. if err != nil {
  92. listener.Close()
  93. os.Remove(sockPath)
  94. return E.Cause(err, "chown")
  95. }
  96. s.listener = listener
  97. go s.loopConnection(listener)
  98. return nil
  99. }
  100. func (s *CommandServer) listenTCP() error {
  101. listener, err := net.Listen("tcp", "127.0.0.1:8964")
  102. if err != nil {
  103. return E.Cause(err, "listen")
  104. }
  105. s.listener = listener
  106. go s.loopConnection(listener)
  107. return nil
  108. }
  109. func (s *CommandServer) Close() error {
  110. return common.Close(
  111. s.listener,
  112. s.observer,
  113. )
  114. }
  115. func (s *CommandServer) loopConnection(listener net.Listener) {
  116. for {
  117. conn, err := listener.Accept()
  118. if err != nil {
  119. return
  120. }
  121. go func() {
  122. hErr := s.handleConnection(conn)
  123. if hErr != nil && !E.IsClosed(err) {
  124. if debug.Enabled {
  125. log.Warn("log-server: process connection: ", hErr)
  126. }
  127. }
  128. }()
  129. }
  130. }
  131. func (s *CommandServer) handleConnection(conn net.Conn) error {
  132. defer conn.Close()
  133. var command uint8
  134. err := binary.Read(conn, binary.BigEndian, &command)
  135. if err != nil {
  136. return E.Cause(err, "read command")
  137. }
  138. switch int32(command) {
  139. case CommandLog:
  140. return s.handleLogConn(conn)
  141. case CommandStatus:
  142. return s.handleStatusConn(conn)
  143. case CommandServiceReload:
  144. return s.handleServiceReload(conn)
  145. case CommandServiceClose:
  146. return s.handleServiceClose(conn)
  147. case CommandCloseConnections:
  148. return s.handleCloseConnections(conn)
  149. case CommandGroup:
  150. return s.handleGroupConn(conn)
  151. case CommandSelectOutbound:
  152. return s.handleSelectOutbound(conn)
  153. case CommandURLTest:
  154. return s.handleURLTest(conn)
  155. case CommandGroupExpand:
  156. return s.handleSetGroupExpand(conn)
  157. case CommandClashMode:
  158. return s.handleModeConn(conn)
  159. case CommandSetClashMode:
  160. return s.handleSetClashMode(conn)
  161. case CommandGetSystemProxyStatus:
  162. return s.handleGetSystemProxyStatus(conn)
  163. case CommandSetSystemProxyEnabled:
  164. return s.handleSetSystemProxyEnabled(conn)
  165. case CommandConnections:
  166. return s.handleConnectionsConn(conn)
  167. case CommandCloseConnection:
  168. return s.handleCloseConnection(conn)
  169. case CommandGetDeprecatedNotes:
  170. return s.handleGetDeprecatedNotes(conn)
  171. default:
  172. return E.New("unknown command: ", command)
  173. }
  174. }