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 }