github.com/sagernet/sing-box@v1.9.0-rc.20/experimental/libbox/command_server.go (about)

     1  package libbox
     2  
     3  import (
     4  	"encoding/binary"
     5  	"net"
     6  	"os"
     7  	"path/filepath"
     8  	"sync"
     9  
    10  	"github.com/sagernet/sing-box/common/urltest"
    11  	"github.com/sagernet/sing-box/experimental/clashapi"
    12  	"github.com/sagernet/sing-box/log"
    13  	"github.com/sagernet/sing/common"
    14  	"github.com/sagernet/sing/common/debug"
    15  	E "github.com/sagernet/sing/common/exceptions"
    16  	"github.com/sagernet/sing/common/observable"
    17  	"github.com/sagernet/sing/common/x/list"
    18  	"github.com/sagernet/sing/service"
    19  )
    20  
    21  type CommandServer struct {
    22  	listener net.Listener
    23  	handler  CommandServerHandler
    24  
    25  	access     sync.Mutex
    26  	savedLines list.List[string]
    27  	maxLines   int
    28  	subscriber *observable.Subscriber[string]
    29  	observer   *observable.Observer[string]
    30  	service    *BoxService
    31  
    32  	// These channels only work with a single client. if multi-client support is needed, replace with Subscriber/Observer
    33  	urlTestUpdate chan struct{}
    34  	modeUpdate    chan struct{}
    35  	logReset      chan struct{}
    36  }
    37  
    38  type CommandServerHandler interface {
    39  	ServiceReload() error
    40  	PostServiceClose()
    41  	GetSystemProxyStatus() *SystemProxyStatus
    42  	SetSystemProxyEnabled(isEnabled bool) error
    43  }
    44  
    45  func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer {
    46  	server := &CommandServer{
    47  		handler:       handler,
    48  		maxLines:      int(maxLines),
    49  		subscriber:    observable.NewSubscriber[string](128),
    50  		urlTestUpdate: make(chan struct{}, 1),
    51  		modeUpdate:    make(chan struct{}, 1),
    52  		logReset:      make(chan struct{}, 1),
    53  	}
    54  	server.observer = observable.NewObserver[string](server.subscriber, 64)
    55  	return server
    56  }
    57  
    58  func (s *CommandServer) SetService(newService *BoxService) {
    59  	if newService != nil {
    60  		service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
    61  		newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
    62  	}
    63  	s.service = newService
    64  	s.notifyURLTestUpdate()
    65  }
    66  
    67  func (s *CommandServer) ResetLog() {
    68  	s.savedLines.Init()
    69  	select {
    70  	case s.logReset <- struct{}{}:
    71  	default:
    72  	}
    73  }
    74  
    75  func (s *CommandServer) notifyURLTestUpdate() {
    76  	select {
    77  	case s.urlTestUpdate <- struct{}{}:
    78  	default:
    79  	}
    80  }
    81  
    82  func (s *CommandServer) Start() error {
    83  	if !sTVOS {
    84  		return s.listenUNIX()
    85  	} else {
    86  		return s.listenTCP()
    87  	}
    88  }
    89  
    90  func (s *CommandServer) listenUNIX() error {
    91  	sockPath := filepath.Join(sBasePath, "command.sock")
    92  	os.Remove(sockPath)
    93  	listener, err := net.ListenUnix("unix", &net.UnixAddr{
    94  		Name: sockPath,
    95  		Net:  "unix",
    96  	})
    97  	if err != nil {
    98  		return E.Cause(err, "listen ", sockPath)
    99  	}
   100  	err = os.Chown(sockPath, sUserID, sGroupID)
   101  	if err != nil {
   102  		listener.Close()
   103  		os.Remove(sockPath)
   104  		return E.Cause(err, "chown")
   105  	}
   106  	s.listener = listener
   107  	go s.loopConnection(listener)
   108  	return nil
   109  }
   110  
   111  func (s *CommandServer) listenTCP() error {
   112  	listener, err := net.Listen("tcp", "127.0.0.1:8964")
   113  	if err != nil {
   114  		return E.Cause(err, "listen")
   115  	}
   116  	s.listener = listener
   117  	go s.loopConnection(listener)
   118  	return nil
   119  }
   120  
   121  func (s *CommandServer) Close() error {
   122  	return common.Close(
   123  		s.listener,
   124  		s.observer,
   125  	)
   126  }
   127  
   128  func (s *CommandServer) loopConnection(listener net.Listener) {
   129  	for {
   130  		conn, err := listener.Accept()
   131  		if err != nil {
   132  			return
   133  		}
   134  		go func() {
   135  			hErr := s.handleConnection(conn)
   136  			if hErr != nil && !E.IsClosed(err) {
   137  				if debug.Enabled {
   138  					log.Warn("log-server: process connection: ", hErr)
   139  				}
   140  			}
   141  		}()
   142  	}
   143  }
   144  
   145  func (s *CommandServer) handleConnection(conn net.Conn) error {
   146  	defer conn.Close()
   147  	var command uint8
   148  	err := binary.Read(conn, binary.BigEndian, &command)
   149  	if err != nil {
   150  		return E.Cause(err, "read command")
   151  	}
   152  	switch int32(command) {
   153  	case CommandLog:
   154  		return s.handleLogConn(conn)
   155  	case CommandStatus:
   156  		return s.handleStatusConn(conn)
   157  	case CommandServiceReload:
   158  		return s.handleServiceReload(conn)
   159  	case CommandServiceClose:
   160  		return s.handleServiceClose(conn)
   161  	case CommandCloseConnections:
   162  		return s.handleCloseConnections(conn)
   163  	case CommandGroup:
   164  		return s.handleGroupConn(conn)
   165  	case CommandSelectOutbound:
   166  		return s.handleSelectOutbound(conn)
   167  	case CommandURLTest:
   168  		return s.handleURLTest(conn)
   169  	case CommandGroupExpand:
   170  		return s.handleSetGroupExpand(conn)
   171  	case CommandClashMode:
   172  		return s.handleModeConn(conn)
   173  	case CommandSetClashMode:
   174  		return s.handleSetClashMode(conn)
   175  	case CommandGetSystemProxyStatus:
   176  		return s.handleGetSystemProxyStatus(conn)
   177  	case CommandSetSystemProxyEnabled:
   178  		return s.handleSetSystemProxyEnabled(conn)
   179  	default:
   180  		return E.New("unknown command: ", command)
   181  	}
   182  }