sing-box/experimental/libbox/command_server.go

183 lines
4.5 KiB
Go
Raw Permalink Normal View History

2023-03-01 02:37:47 +00:00
package libbox
import (
"encoding/binary"
"net"
"os"
"path/filepath"
"sync"
2023-07-02 08:45:30 +00:00
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-box/experimental/clashapi"
2023-03-01 02:37:47 +00:00
"github.com/sagernet/sing-box/log"
2023-04-04 20:38:56 +00:00
"github.com/sagernet/sing/common"
2023-04-10 00:48:58 +00:00
"github.com/sagernet/sing/common/debug"
2023-03-01 02:37:47 +00:00
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/observable"
"github.com/sagernet/sing/common/x/list"
2023-07-02 08:45:30 +00:00
"github.com/sagernet/sing/service"
2023-03-01 02:37:47 +00:00
)
type CommandServer struct {
listener net.Listener
2023-03-03 11:26:54 +00:00
handler CommandServerHandler
2023-03-01 02:37:47 +00:00
access sync.Mutex
2023-10-02 14:27:48 +00:00
savedLines list.List[string]
2023-07-02 08:45:30 +00:00
maxLines int
2023-03-01 02:37:47 +00:00
subscriber *observable.Subscriber[string]
observer *observable.Observer[string]
2023-07-02 08:45:30 +00:00
service *BoxService
2023-10-02 14:27:48 +00:00
// These channels only work with a single client. if multi-client support is needed, replace with Subscriber/Observer
urlTestUpdate chan struct{}
modeUpdate chan struct{}
2023-10-02 14:27:48 +00:00
logReset chan struct{}
2024-06-11 13:16:33 +00:00
closedConnections []Connection
2023-03-01 02:37:47 +00:00
}
2023-03-03 11:26:54 +00:00
type CommandServerHandler interface {
ServiceReload() error
PostServiceClose()
2023-09-03 13:06:21 +00:00
GetSystemProxyStatus() *SystemProxyStatus
SetSystemProxyEnabled(isEnabled bool) error
2023-03-03 11:26:54 +00:00
}
2023-07-29 00:37:10 +00:00
func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer {
2023-03-01 02:37:47 +00:00
server := &CommandServer{
2023-07-02 08:45:30 +00:00
handler: handler,
maxLines: int(maxLines),
subscriber: observable.NewSubscriber[string](128),
urlTestUpdate: make(chan struct{}, 1),
modeUpdate: make(chan struct{}, 1),
2023-10-02 14:27:48 +00:00
logReset: make(chan struct{}, 1),
2023-03-01 02:37:47 +00:00
}
server.observer = observable.NewObserver[string](server.subscriber, 64)
return server
}
2023-07-02 08:45:30 +00:00
func (s *CommandServer) SetService(newService *BoxService) {
if newService != nil {
service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
2024-11-10 08:46:59 +00:00
newService.clashServer.(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
2023-07-02 08:45:30 +00:00
}
s.service = newService
2023-07-02 08:45:30 +00:00
s.notifyURLTestUpdate()
}
func (s *CommandServer) notifyURLTestUpdate() {
select {
case s.urlTestUpdate <- struct{}{}:
default:
}
}
2023-03-01 02:37:47 +00:00
func (s *CommandServer) Start() error {
2023-07-29 00:37:10 +00:00
if !sTVOS {
return s.listenUNIX()
} else {
return s.listenTCP()
}
}
func (s *CommandServer) listenUNIX() error {
sockPath := filepath.Join(sBasePath, "command.sock")
os.Remove(sockPath)
2023-03-01 02:37:47 +00:00
listener, err := net.ListenUnix("unix", &net.UnixAddr{
2023-07-29 00:37:10 +00:00
Name: sockPath,
2023-03-01 02:37:47 +00:00
Net: "unix",
})
if err != nil {
2023-07-30 12:26:09 +00:00
return E.Cause(err, "listen ", sockPath)
2023-03-01 02:37:47 +00:00
}
err = os.Chown(sockPath, sUserID, sGroupID)
if err != nil {
listener.Close()
os.Remove(sockPath)
return E.Cause(err, "chown")
2023-07-24 07:44:11 +00:00
}
2023-03-03 16:40:47 +00:00
s.listener = listener
2023-03-01 02:37:47 +00:00
go s.loopConnection(listener)
return nil
}
2023-07-29 00:37:10 +00:00
func (s *CommandServer) listenTCP() error {
listener, err := net.Listen("tcp", "127.0.0.1:8964")
if err != nil {
return E.Cause(err, "listen")
}
s.listener = listener
go s.loopConnection(listener)
return nil
}
2023-03-01 02:37:47 +00:00
func (s *CommandServer) Close() error {
2023-04-04 20:38:56 +00:00
return common.Close(
s.listener,
s.observer,
)
2023-03-01 02:37:47 +00:00
}
func (s *CommandServer) loopConnection(listener net.Listener) {
for {
conn, err := listener.Accept()
if err != nil {
return
}
go func() {
hErr := s.handleConnection(conn)
if hErr != nil && !E.IsClosed(err) {
2023-04-10 00:48:58 +00:00
if debug.Enabled {
log.Warn("log-server: process connection: ", hErr)
}
2023-03-01 02:37:47 +00:00
}
}()
}
}
func (s *CommandServer) handleConnection(conn net.Conn) error {
defer conn.Close()
var command uint8
err := binary.Read(conn, binary.BigEndian, &command)
if err != nil {
return E.Cause(err, "read command")
}
switch int32(command) {
case CommandLog:
return s.handleLogConn(conn)
case CommandStatus:
return s.handleStatusConn(conn)
2023-03-03 11:26:54 +00:00
case CommandServiceReload:
return s.handleServiceReload(conn)
case CommandServiceClose:
return s.handleServiceClose(conn)
2023-03-03 11:26:54 +00:00
case CommandCloseConnections:
return s.handleCloseConnections(conn)
2023-07-02 08:45:30 +00:00
case CommandGroup:
return s.handleGroupConn(conn)
case CommandSelectOutbound:
return s.handleSelectOutbound(conn)
case CommandURLTest:
return s.handleURLTest(conn)
2023-08-12 11:33:43 +00:00
case CommandGroupExpand:
return s.handleSetGroupExpand(conn)
case CommandClashMode:
return s.handleModeConn(conn)
case CommandSetClashMode:
return s.handleSetClashMode(conn)
2023-09-03 13:06:21 +00:00
case CommandGetSystemProxyStatus:
return s.handleGetSystemProxyStatus(conn)
case CommandSetSystemProxyEnabled:
return s.handleSetSystemProxyEnabled(conn)
2024-06-11 13:16:33 +00:00
case CommandConnections:
return s.handleConnectionsConn(conn)
case CommandCloseConnection:
return s.handleCloseConnection(conn)
2024-10-18 01:58:03 +00:00
case CommandGetDeprecatedNotes:
return s.handleGetDeprecatedNotes(conn)
2023-03-01 02:37:47 +00:00
default:
return E.New("unknown command: ", command)
}
}