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