github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/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/inazumav/sing-box/common/urltest" 11 "github.com/inazumav/sing-box/experimental/clashapi" 12 "github.com/inazumav/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 urlTestUpdate chan struct{} 33 modeUpdate chan struct{} 34 } 35 36 type CommandServerHandler interface { 37 ServiceReload() error 38 GetSystemProxyStatus() *SystemProxyStatus 39 SetSystemProxyEnabled(isEnabled bool) error 40 } 41 42 func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer { 43 server := &CommandServer{ 44 handler: handler, 45 savedLines: new(list.List[string]), 46 maxLines: int(maxLines), 47 subscriber: observable.NewSubscriber[string](128), 48 urlTestUpdate: make(chan struct{}, 1), 49 modeUpdate: make(chan struct{}, 1), 50 } 51 server.observer = observable.NewObserver[string](server.subscriber, 64) 52 return server 53 } 54 55 func (s *CommandServer) SetService(newService *BoxService) { 56 if newService != nil { 57 service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate) 58 newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate) 59 } 60 s.service = newService 61 s.notifyURLTestUpdate() 62 } 63 64 func (s *CommandServer) notifyURLTestUpdate() { 65 select { 66 case s.urlTestUpdate <- struct{}{}: 67 default: 68 } 69 } 70 71 func (s *CommandServer) Start() error { 72 if !sTVOS { 73 return s.listenUNIX() 74 } else { 75 return s.listenTCP() 76 } 77 } 78 79 func (s *CommandServer) listenUNIX() error { 80 sockPath := filepath.Join(sBasePath, "command.sock") 81 os.Remove(sockPath) 82 listener, err := net.ListenUnix("unix", &net.UnixAddr{ 83 Name: sockPath, 84 Net: "unix", 85 }) 86 if err != nil { 87 return E.Cause(err, "listen ", sockPath) 88 } 89 if sUserID > 0 { 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 } 97 s.listener = listener 98 go s.loopConnection(listener) 99 return nil 100 } 101 102 func (s *CommandServer) listenTCP() error { 103 listener, err := net.Listen("tcp", "127.0.0.1:8964") 104 if err != nil { 105 return E.Cause(err, "listen") 106 } 107 s.listener = listener 108 go s.loopConnection(listener) 109 return nil 110 } 111 112 func (s *CommandServer) Close() error { 113 return common.Close( 114 s.listener, 115 s.observer, 116 ) 117 } 118 119 func (s *CommandServer) loopConnection(listener net.Listener) { 120 for { 121 conn, err := listener.Accept() 122 if err != nil { 123 return 124 } 125 go func() { 126 hErr := s.handleConnection(conn) 127 if hErr != nil && !E.IsClosed(err) { 128 if debug.Enabled { 129 log.Warn("log-server: process connection: ", hErr) 130 } 131 } 132 }() 133 } 134 } 135 136 func (s *CommandServer) handleConnection(conn net.Conn) error { 137 defer conn.Close() 138 var command uint8 139 err := binary.Read(conn, binary.BigEndian, &command) 140 if err != nil { 141 return E.Cause(err, "read command") 142 } 143 switch int32(command) { 144 case CommandLog: 145 return s.handleLogConn(conn) 146 case CommandStatus: 147 return s.handleStatusConn(conn) 148 case CommandServiceReload: 149 return s.handleServiceReload(conn) 150 case CommandCloseConnections: 151 return s.handleCloseConnections(conn) 152 case CommandGroup: 153 return s.handleGroupConn(conn) 154 case CommandSelectOutbound: 155 return s.handleSelectOutbound(conn) 156 case CommandURLTest: 157 return s.handleURLTest(conn) 158 case CommandGroupExpand: 159 return s.handleSetGroupExpand(conn) 160 case CommandClashMode: 161 return s.handleModeConn(conn) 162 case CommandSetClashMode: 163 return s.handleSetClashMode(conn) 164 case CommandGetSystemProxyStatus: 165 return s.handleGetSystemProxyStatus(conn) 166 case CommandSetSystemProxyEnabled: 167 return s.handleSetSystemProxyEnabled(conn) 168 default: 169 return E.New("unknown command: ", command) 170 } 171 }