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
|
|
|
|
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-08-24 13:52:38 +00:00
|
|
|
urlTestUpdate chan struct{}
|
|
|
|
modeUpdate chan struct{}
|
2023-03-01 02:37:47 +00:00
|
|
|
}
|
|
|
|
|
2023-03-03 11:26:54 +00:00
|
|
|
type CommandServerHandler interface {
|
|
|
|
ServiceReload() error
|
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
savedLines: new(list.List[string]),
|
|
|
|
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-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)
|
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
|
|
|
}
|
2023-07-24 07:44:11 +00:00
|
|
|
if sUserID > 0 {
|
2023-07-29 00:37:10 +00:00
|
|
|
err = os.Chown(sockPath, sUserID, sGroupID)
|
2023-07-24 07:44:11 +00:00
|
|
|
if err != nil {
|
|
|
|
listener.Close()
|
2023-07-29 00:37:10 +00:00
|
|
|
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 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-03-01 02:37:47 +00:00
|
|
|
default:
|
|
|
|
return E.New("unknown command: ", command)
|
|
|
|
}
|
|
|
|
}
|