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

     1  package libbox
     2  
     3  import (
     4  	"encoding/binary"
     5  	"net"
     6  	"os"
     7  	"path/filepath"
     8  	"time"
     9  
    10  	"github.com/sagernet/sing/common"
    11  	E "github.com/sagernet/sing/common/exceptions"
    12  )
    13  
    14  type CommandClient struct {
    15  	handler CommandClientHandler
    16  	conn    net.Conn
    17  	options CommandClientOptions
    18  }
    19  
    20  type CommandClientOptions struct {
    21  	Command        int32
    22  	StatusInterval int64
    23  }
    24  
    25  type CommandClientHandler interface {
    26  	Connected()
    27  	Disconnected(message string)
    28  	ClearLog()
    29  	WriteLog(message string)
    30  	WriteStatus(message *StatusMessage)
    31  	WriteGroups(message OutboundGroupIterator)
    32  	InitializeClashMode(modeList StringIterator, currentMode string)
    33  	UpdateClashMode(newMode string)
    34  }
    35  
    36  func NewStandaloneCommandClient() *CommandClient {
    37  	return new(CommandClient)
    38  }
    39  
    40  func NewCommandClient(handler CommandClientHandler, options *CommandClientOptions) *CommandClient {
    41  	return &CommandClient{
    42  		handler: handler,
    43  		options: common.PtrValueOrDefault(options),
    44  	}
    45  }
    46  
    47  func (c *CommandClient) directConnect() (net.Conn, error) {
    48  	if !sTVOS {
    49  		return net.DialUnix("unix", nil, &net.UnixAddr{
    50  			Name: filepath.Join(sBasePath, "command.sock"),
    51  			Net:  "unix",
    52  		})
    53  	} else {
    54  		return net.Dial("tcp", "127.0.0.1:8964")
    55  	}
    56  }
    57  
    58  func (c *CommandClient) directConnectWithRetry() (net.Conn, error) {
    59  	var (
    60  		conn net.Conn
    61  		err  error
    62  	)
    63  	for i := 0; i < 10; i++ {
    64  		conn, err = c.directConnect()
    65  		if err == nil {
    66  			return conn, nil
    67  		}
    68  		time.Sleep(time.Duration(100+i*50) * time.Millisecond)
    69  	}
    70  	return nil, err
    71  }
    72  
    73  func (c *CommandClient) Connect() error {
    74  	common.Close(c.conn)
    75  	conn, err := c.directConnectWithRetry()
    76  	if err != nil {
    77  		return err
    78  	}
    79  	c.conn = conn
    80  	err = binary.Write(conn, binary.BigEndian, uint8(c.options.Command))
    81  	if err != nil {
    82  		return err
    83  	}
    84  	switch c.options.Command {
    85  	case CommandLog:
    86  		c.handler.Connected()
    87  		go c.handleLogConn(conn)
    88  	case CommandStatus:
    89  		err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
    90  		if err != nil {
    91  			return E.Cause(err, "write interval")
    92  		}
    93  		c.handler.Connected()
    94  		go c.handleStatusConn(conn)
    95  	case CommandGroup:
    96  		err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
    97  		if err != nil {
    98  			return E.Cause(err, "write interval")
    99  		}
   100  		c.handler.Connected()
   101  		go c.handleGroupConn(conn)
   102  	case CommandClashMode:
   103  		var (
   104  			modeList    []string
   105  			currentMode string
   106  		)
   107  		modeList, currentMode, err = readClashModeList(conn)
   108  		if err != nil {
   109  			return err
   110  		}
   111  		c.handler.Connected()
   112  		c.handler.InitializeClashMode(newIterator(modeList), currentMode)
   113  		if len(modeList) == 0 {
   114  			conn.Close()
   115  			c.handler.Disconnected(os.ErrInvalid.Error())
   116  			return nil
   117  		}
   118  		go c.handleModeConn(conn)
   119  	}
   120  	return nil
   121  }
   122  
   123  func (c *CommandClient) Disconnect() error {
   124  	return common.Close(c.conn)
   125  }