github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/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  	WriteLog(message string)
    29  	WriteStatus(message *StatusMessage)
    30  	WriteGroups(message OutboundGroupIterator)
    31  	InitializeClashMode(modeList StringIterator, currentMode string)
    32  	UpdateClashMode(newMode string)
    33  }
    34  
    35  func NewStandaloneCommandClient() *CommandClient {
    36  	return new(CommandClient)
    37  }
    38  
    39  func NewCommandClient(handler CommandClientHandler, options *CommandClientOptions) *CommandClient {
    40  	return &CommandClient{
    41  		handler: handler,
    42  		options: common.PtrValueOrDefault(options),
    43  	}
    44  }
    45  
    46  func (c *CommandClient) directConnect() (net.Conn, error) {
    47  	if !sTVOS {
    48  		return net.DialUnix("unix", nil, &net.UnixAddr{
    49  			Name: filepath.Join(sBasePath, "command.sock"),
    50  			Net:  "unix",
    51  		})
    52  	} else {
    53  		return net.Dial("tcp", "127.0.0.1:8964")
    54  	}
    55  }
    56  
    57  func (c *CommandClient) directConnectWithRetry() (net.Conn, error) {
    58  	var (
    59  		conn net.Conn
    60  		err  error
    61  	)
    62  	for i := 0; i < 10; i++ {
    63  		conn, err = c.directConnect()
    64  		if err == nil {
    65  			return conn, nil
    66  		}
    67  		time.Sleep(time.Duration(100+i*50) * time.Millisecond)
    68  	}
    69  	return nil, err
    70  }
    71  
    72  func (c *CommandClient) Connect() error {
    73  	common.Close(c.conn)
    74  	conn, err := c.directConnectWithRetry()
    75  	if err != nil {
    76  		return err
    77  	}
    78  	c.conn = conn
    79  	err = binary.Write(conn, binary.BigEndian, uint8(c.options.Command))
    80  	if err != nil {
    81  		return err
    82  	}
    83  	switch c.options.Command {
    84  	case CommandLog:
    85  		c.handler.Connected()
    86  		go c.handleLogConn(conn)
    87  	case CommandStatus:
    88  		err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
    89  		if err != nil {
    90  			return E.Cause(err, "write interval")
    91  		}
    92  		c.handler.Connected()
    93  		go c.handleStatusConn(conn)
    94  	case CommandGroup:
    95  		err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
    96  		if err != nil {
    97  			return E.Cause(err, "write interval")
    98  		}
    99  		c.handler.Connected()
   100  		go c.handleGroupConn(conn)
   101  	case CommandClashMode:
   102  		var (
   103  			modeList    []string
   104  			currentMode string
   105  		)
   106  		modeList, currentMode, err = readClashModeList(conn)
   107  		if err != nil {
   108  			return err
   109  		}
   110  		c.handler.Connected()
   111  		c.handler.InitializeClashMode(newIterator(modeList), currentMode)
   112  		if len(modeList) == 0 {
   113  			conn.Close()
   114  			c.handler.Disconnected(os.ErrInvalid.Error())
   115  			return nil
   116  		}
   117  		go c.handleModeConn(conn)
   118  	}
   119  	return nil
   120  }
   121  
   122  func (c *CommandClient) Disconnect() error {
   123  	return common.Close(c.conn)
   124  }