github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/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/inazumav/sing-box/common/urltest"
    11  	"github.com/inazumav/sing-box/experimental/clashapi"
    12  	"github.com/inazumav/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  	urlTestUpdate chan struct{}
    33  	modeUpdate    chan struct{}
    34  }
    35  
    36  type CommandServerHandler interface {
    37  	ServiceReload() error
    38  	GetSystemProxyStatus() *SystemProxyStatus
    39  	SetSystemProxyEnabled(isEnabled bool) error
    40  }
    41  
    42  func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer {
    43  	server := &CommandServer{
    44  		handler:       handler,
    45  		savedLines:    new(list.List[string]),
    46  		maxLines:      int(maxLines),
    47  		subscriber:    observable.NewSubscriber[string](128),
    48  		urlTestUpdate: make(chan struct{}, 1),
    49  		modeUpdate:    make(chan struct{}, 1),
    50  	}
    51  	server.observer = observable.NewObserver[string](server.subscriber, 64)
    52  	return server
    53  }
    54  
    55  func (s *CommandServer) SetService(newService *BoxService) {
    56  	if newService != nil {
    57  		service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
    58  		newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
    59  	}
    60  	s.service = newService
    61  	s.notifyURLTestUpdate()
    62  }
    63  
    64  func (s *CommandServer) notifyURLTestUpdate() {
    65  	select {
    66  	case s.urlTestUpdate <- struct{}{}:
    67  	default:
    68  	}
    69  }
    70  
    71  func (s *CommandServer) Start() error {
    72  	if !sTVOS {
    73  		return s.listenUNIX()
    74  	} else {
    75  		return s.listenTCP()
    76  	}
    77  }
    78  
    79  func (s *CommandServer) listenUNIX() error {
    80  	sockPath := filepath.Join(sBasePath, "command.sock")
    81  	os.Remove(sockPath)
    82  	listener, err := net.ListenUnix("unix", &net.UnixAddr{
    83  		Name: sockPath,
    84  		Net:  "unix",
    85  	})
    86  	if err != nil {
    87  		return E.Cause(err, "listen ", sockPath)
    88  	}
    89  	if sUserID > 0 {
    90  		err = os.Chown(sockPath, sUserID, sGroupID)
    91  		if err != nil {
    92  			listener.Close()
    93  			os.Remove(sockPath)
    94  			return E.Cause(err, "chown")
    95  		}
    96  	}
    97  	s.listener = listener
    98  	go s.loopConnection(listener)
    99  	return nil
   100  }
   101  
   102  func (s *CommandServer) listenTCP() error {
   103  	listener, err := net.Listen("tcp", "127.0.0.1:8964")
   104  	if err != nil {
   105  		return E.Cause(err, "listen")
   106  	}
   107  	s.listener = listener
   108  	go s.loopConnection(listener)
   109  	return nil
   110  }
   111  
   112  func (s *CommandServer) Close() error {
   113  	return common.Close(
   114  		s.listener,
   115  		s.observer,
   116  	)
   117  }
   118  
   119  func (s *CommandServer) loopConnection(listener net.Listener) {
   120  	for {
   121  		conn, err := listener.Accept()
   122  		if err != nil {
   123  			return
   124  		}
   125  		go func() {
   126  			hErr := s.handleConnection(conn)
   127  			if hErr != nil && !E.IsClosed(err) {
   128  				if debug.Enabled {
   129  					log.Warn("log-server: process connection: ", hErr)
   130  				}
   131  			}
   132  		}()
   133  	}
   134  }
   135  
   136  func (s *CommandServer) handleConnection(conn net.Conn) error {
   137  	defer conn.Close()
   138  	var command uint8
   139  	err := binary.Read(conn, binary.BigEndian, &command)
   140  	if err != nil {
   141  		return E.Cause(err, "read command")
   142  	}
   143  	switch int32(command) {
   144  	case CommandLog:
   145  		return s.handleLogConn(conn)
   146  	case CommandStatus:
   147  		return s.handleStatusConn(conn)
   148  	case CommandServiceReload:
   149  		return s.handleServiceReload(conn)
   150  	case CommandCloseConnections:
   151  		return s.handleCloseConnections(conn)
   152  	case CommandGroup:
   153  		return s.handleGroupConn(conn)
   154  	case CommandSelectOutbound:
   155  		return s.handleSelectOutbound(conn)
   156  	case CommandURLTest:
   157  		return s.handleURLTest(conn)
   158  	case CommandGroupExpand:
   159  		return s.handleSetGroupExpand(conn)
   160  	case CommandClashMode:
   161  		return s.handleModeConn(conn)
   162  	case CommandSetClashMode:
   163  		return s.handleSetClashMode(conn)
   164  	case CommandGetSystemProxyStatus:
   165  		return s.handleGetSystemProxyStatus(conn)
   166  	case CommandSetSystemProxyEnabled:
   167  		return s.handleSetSystemProxyEnabled(conn)
   168  	default:
   169  		return E.New("unknown command: ", command)
   170  	}
   171  }