sing-box/experimental/libbox/command_server.go

144 lines
3.3 KiB
Go
Raw Normal View History

2023-03-01 10:37:47 +08:00
package libbox
import (
"encoding/binary"
"net"
"os"
"path/filepath"
"sync"
2023-07-02 16:45:30 +08:00
"github.com/sagernet/sing-box/common/urltest"
2023-03-01 10:37:47 +08:00
"github.com/sagernet/sing-box/log"
2023-04-05 04:38:56 +08:00
"github.com/sagernet/sing/common"
2023-04-10 08:48:58 +08:00
"github.com/sagernet/sing/common/debug"
2023-03-01 10:37:47 +08:00
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/observable"
"github.com/sagernet/sing/common/x/list"
2023-07-02 16:45:30 +08:00
"github.com/sagernet/sing/service"
2023-03-01 10:37:47 +08:00
)
type CommandServer struct {
sockPath string
listener net.Listener
2023-03-03 19:26:54 +08:00
handler CommandServerHandler
2023-03-01 10:37:47 +08:00
access sync.Mutex
savedLines *list.List[string]
2023-07-02 16:45:30 +08:00
maxLines int
2023-03-01 10:37:47 +08:00
subscriber *observable.Subscriber[string]
observer *observable.Observer[string]
2023-07-02 16:45:30 +08:00
service *BoxService
urlTestListener *list.Element[func()]
urlTestUpdate chan struct{}
2023-03-01 10:37:47 +08:00
}
2023-03-03 19:26:54 +08:00
type CommandServerHandler interface {
ServiceReload() error
}
2023-07-02 16:45:30 +08:00
func NewCommandServer(sharedDirectory string, handler CommandServerHandler, maxLines int32) *CommandServer {
2023-03-01 10:37:47 +08:00
server := &CommandServer{
2023-07-02 16:45:30 +08:00
sockPath: filepath.Join(sharedDirectory, "command.sock"),
handler: handler,
savedLines: new(list.List[string]),
maxLines: int(maxLines),
subscriber: observable.NewSubscriber[string](128),
urlTestUpdate: make(chan struct{}, 1),
2023-03-01 10:37:47 +08:00
}
server.observer = observable.NewObserver[string](server.subscriber, 64)
return server
}
2023-07-02 16:45:30 +08: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 10:37:47 +08:00
func (s *CommandServer) Start() error {
os.Remove(s.sockPath)
listener, err := net.ListenUnix("unix", &net.UnixAddr{
Name: s.sockPath,
Net: "unix",
})
if err != nil {
return err
}
2023-07-24 15:44:11 +08:00
if sUserID > 0 {
err = os.Chown(s.sockPath, sUserID, sGroupID)
if err != nil {
listener.Close()
os.Remove(s.sockPath)
return err
}
}
2023-03-04 00:40:47 +08:00
s.listener = listener
2023-03-01 10:37:47 +08:00
go s.loopConnection(listener)
return nil
}
func (s *CommandServer) Close() error {
2023-04-05 04:38:56 +08:00
return common.Close(
s.listener,
s.observer,
)
2023-03-01 10:37:47 +08: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 08:48:58 +08:00
if debug.Enabled {
log.Warn("log-server: process connection: ", hErr)
}
2023-03-01 10:37:47 +08: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 19:26:54 +08:00
case CommandServiceReload:
return s.handleServiceReload(conn)
case CommandCloseConnections:
return s.handleCloseConnections(conn)
2023-07-02 16:45:30 +08:00
case CommandGroup:
return s.handleGroupConn(conn)
case CommandSelectOutbound:
return s.handleSelectOutbound(conn)
case CommandURLTest:
return s.handleURLTest(conn)
2023-03-01 10:37:47 +08:00
default:
return E.New("unknown command: ", command)
}
}