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 }