sing-box/experimental/libbox/command_server.go

163 lines
3.7 KiB
Go
Raw 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"
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
urlTestListener *list.Element[func()]
urlTestUpdate 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-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 s.service != nil && s.listener != nil {
service.PtrFromContext[urltest.HistoryStorage](s.service.ctx).RemoveListener(s.urlTestListener)
s.urlTestListener = nil
}
s.service = newService
if newService != nil {
s.urlTestListener = service.PtrFromContext[urltest.HistoryStorage](newService.ctx).AddListener(s.notifyURLTestUpdate)
}
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-03-01 02:37:47 +00:00
default:
return E.New("unknown command: ", command)
}
}