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"
|
2023-08-24 13:52:38 +00:00
|
|
|
"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
|
2023-08-24 13:52:38 +00:00
|
|
|
urlTestUpdate chan struct{}
|
|
|
|
modeUpdate chan struct{}
|
2023-10-02 14:27:48 +00:00
|
|
|
logReset chan struct{}
|
2024-10-30 14:48:10 +00:00
|
|
|
events chan myEvent
|
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
|
2024-03-14 05:40:17 +00:00
|
|
|
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),
|
2023-08-24 13:52:38 +00:00
|
|
|
modeUpdate: make(chan struct{}, 1),
|
2023-10-02 14:27:48 +00:00
|
|
|
logReset: make(chan struct{}, 1),
|
2024-10-30 14:48:10 +00:00
|
|
|
events: make(chan myEvent, 8),
|
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 {
|
2023-08-24 13:52:38 +00:00
|
|
|
service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
|
|
|
|
newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
|
2024-10-30 14:48:10 +00:00
|
|
|
newService.platformInterface.openURLFunc = func(url string) {
|
|
|
|
select {
|
|
|
|
case s.events <- &eventOpenURL{URL: url}:
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
2023-07-02 08:45:30 +00:00
|
|
|
}
|
2023-08-24 13:52:38 +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
|
|
|
}
|
2024-01-22 10:27:06 +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)
|
2024-03-14 05:40:17 +00:00
|
|
|
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)
|
2023-08-24 13:52:38 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|