diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index 9166f9d6..d49dc148 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -84,11 +84,12 @@ func buildiOS() { "-libname=box", } if !debugEnabled { - args = append(args, - "-trimpath", "-ldflags=-s -w -buildid=", "-tags", "with_gvisor,with_clash_api", + args = append( + args, "-trimpath", "-ldflags=-s -w -buildid=", + "-tags", "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api", ) } else { - args = append(args, "-tags", "with_gvisor,with_clash_api,debug") + args = append(args, "-tags", "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,debug") } args = append(args, "./experimental/libbox") diff --git a/experimental/libbox/command.go b/experimental/libbox/command.go new file mode 100644 index 00000000..0ca9b383 --- /dev/null +++ b/experimental/libbox/command.go @@ -0,0 +1,8 @@ +//go:build darwin + +package libbox + +const ( + CommandLog int32 = iota + CommandStatus +) diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go new file mode 100644 index 00000000..e37ce3f5 --- /dev/null +++ b/experimental/libbox/command_client.go @@ -0,0 +1,69 @@ +//go:build darwin + +package libbox + +import ( + "encoding/binary" + "net" + "path/filepath" + + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type CommandClient struct { + sockPath string + handler CommandClientHandler + conn net.Conn + options CommandClientOptions +} + +type CommandClientOptions struct { + Command int32 + StatusInterval int64 +} + +type CommandClientHandler interface { + Connected() + Disconnected(message string) + WriteLog(message string) + WriteStatus(message *StatusMessage) +} + +func NewCommandClient(sharedDirectory string, handler CommandClientHandler, options *CommandClientOptions) *CommandClient { + return &CommandClient{ + sockPath: filepath.Join(sharedDirectory, "command.sock"), + handler: handler, + options: common.PtrValueOrDefault(options), + } +} + +func (c *CommandClient) Connect() error { + conn, err := net.DialUnix("unix", nil, &net.UnixAddr{ + Name: c.sockPath, + Net: "unix", + }) + if err != nil { + return err + } + c.conn = conn + err = binary.Write(conn, binary.BigEndian, uint8(c.options.Command)) + if err != nil { + return err + } + switch c.options.Command { + case CommandLog: + go c.handleLogConn(conn) + case CommandStatus: + err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) + if err != nil { + return E.Cause(err, "write interval") + } + go c.handleStatusConn(conn) + } + return nil +} + +func (c *CommandClient) Disconnect() error { + return common.Close(c.conn) +} diff --git a/experimental/libbox/command_log.go b/experimental/libbox/command_log.go new file mode 100644 index 00000000..3555c616 --- /dev/null +++ b/experimental/libbox/command_log.go @@ -0,0 +1,104 @@ +//go:build darwin + +package libbox + +import ( + "context" + "encoding/binary" + "io" + "net" +) + +func (s *CommandServer) WriteMessage(message string) { + s.subscriber.Emit(message) + s.access.Lock() + s.savedLines.PushBack(message) + if s.savedLines.Len() > 100 { + s.savedLines.Remove(s.savedLines.Front()) + } + s.access.Unlock() +} + +func readLog(reader io.Reader) ([]byte, error) { + var messageLength uint16 + err := binary.Read(reader, binary.BigEndian, &messageLength) + if err != nil { + return nil, err + } + data := make([]byte, messageLength) + _, err = io.ReadFull(reader, data) + if err != nil { + return nil, err + } + return data, nil +} + +func writeLog(writer io.Writer, message []byte) error { + err := binary.Write(writer, binary.BigEndian, uint16(len(message))) + if err != nil { + return err + } + _, err = writer.Write(message) + return err +} + +func (s *CommandServer) handleLogConn(conn net.Conn) error { + var savedLines []string + s.access.Lock() + savedLines = make([]string, 0, s.savedLines.Len()) + for element := s.savedLines.Front(); element != nil; element = element.Next() { + savedLines = append(savedLines, element.Value) + } + s.access.Unlock() + subscription, done, err := s.observer.Subscribe() + if err != nil { + return err + } + defer s.observer.UnSubscribe(subscription) + for _, line := range savedLines { + err = writeLog(conn, []byte(line)) + if err != nil { + return err + } + } + ctx := connKeepAlive(conn) + for { + select { + case <-ctx.Done(): + return ctx.Err() + case message := <-subscription: + err = writeLog(conn, []byte(message)) + if err != nil { + return err + } + case <-done: + return nil + } + } +} + +func (c *CommandClient) handleLogConn(conn net.Conn) { + c.handler.Connected() + for { + message, err := readLog(conn) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + c.handler.WriteLog(string(message)) + } +} + +func connKeepAlive(reader io.Reader) context.Context { + ctx, cancel := context.WithCancelCause(context.Background()) + go func() { + for { + _, err := readLog(reader) + if err != nil { + cancel(err) + return + } + } + }() + return ctx +} diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go new file mode 100644 index 00000000..2606b9b1 --- /dev/null +++ b/experimental/libbox/command_server.go @@ -0,0 +1,85 @@ +//go:build darwin + +package libbox + +import ( + "encoding/binary" + "net" + "os" + "path/filepath" + "sync" + + "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/observable" + "github.com/sagernet/sing/common/x/list" +) + +type CommandServer struct { + sockPath string + listener net.Listener + + access sync.Mutex + savedLines *list.List[string] + subscriber *observable.Subscriber[string] + observer *observable.Observer[string] +} + +func NewCommandServer(sharedDirectory string) *CommandServer { + server := &CommandServer{ + sockPath: filepath.Join(sharedDirectory, "command.sock"), + savedLines: new(list.List[string]), + subscriber: observable.NewSubscriber[string](128), + } + server.observer = observable.NewObserver[string](server.subscriber, 64) + return server +} + +func (s *CommandServer) Start() error { + os.Remove(s.sockPath) + listener, err := net.ListenUnix("unix", &net.UnixAddr{ + Name: s.sockPath, + Net: "unix", + }) + if err != nil { + return err + } + go s.loopConnection(listener) + return nil +} + +func (s *CommandServer) Close() error { + return s.listener.Close() +} + +func (s *CommandServer) loopConnection(listener net.Listener) { + for { + conn, err := listener.Accept() + if err != nil { + return + } + go func() { + hErr := s.handleConnection(conn) + if hErr != nil && !E.IsClosed(err) { + log.Warn("log-server: process connection: ", hErr) + } + }() + } +} + +func (s *CommandServer) handleConnection(conn net.Conn) error { + defer conn.Close() + var command uint8 + err := binary.Read(conn, binary.BigEndian, &command) + if err != nil { + return E.Cause(err, "read command") + } + switch int32(command) { + case CommandLog: + return s.handleLogConn(conn) + case CommandStatus: + return s.handleStatusConn(conn) + default: + return E.New("unknown command: ", command) + } +} diff --git a/experimental/libbox/command_status.go b/experimental/libbox/command_status.go new file mode 100644 index 00000000..b9d3210f --- /dev/null +++ b/experimental/libbox/command_status.go @@ -0,0 +1,61 @@ +//go:build darwin + +package libbox + +import ( + "encoding/binary" + "net" + "runtime" + "time" + + E "github.com/sagernet/sing/common/exceptions" +) + +type StatusMessage struct { + Memory int64 + Goroutines int32 +} + +func readStatus() StatusMessage { + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + var message StatusMessage + message.Memory = int64(memStats.HeapInuse + memStats.StackInuse + memStats.MSpanInuse) + message.Goroutines = int32(runtime.NumGoroutine()) + return message +} + +func (s *CommandServer) handleStatusConn(conn net.Conn) error { + var interval int64 + err := binary.Read(conn, binary.BigEndian, &interval) + if err != nil { + return E.Cause(err, "read interval") + } + ticker := time.NewTicker(time.Duration(interval)) + defer ticker.Stop() + ctx := connKeepAlive(conn) + for { + err = binary.Write(conn, binary.BigEndian, readStatus()) + if err != nil { + return err + } + select { + case <-ctx.Done(): + return nil + case <-ticker.C: + } + } +} + +func (c *CommandClient) handleStatusConn(conn net.Conn) { + c.handler.Connected() + for { + var message StatusMessage + err := binary.Read(conn, binary.BigEndian, &message) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + c.handler.WriteStatus(&message) + } +} diff --git a/experimental/libbox/log_client.go b/experimental/libbox/log_client.go deleted file mode 100644 index 6efb21fb..00000000 --- a/experimental/libbox/log_client.go +++ /dev/null @@ -1,59 +0,0 @@ -//go:build darwin - -package libbox - -import ( - "net" - "path/filepath" - - "github.com/sagernet/sing/common" -) - -type LogClient struct { - sockPath string - handler LogClientHandler - conn net.Conn -} - -type LogClientHandler interface { - Connected() - Disconnected() - WriteLog(message string) -} - -func NewLogClient(sharedDirectory string, handler LogClientHandler) *LogClient { - return &LogClient{ - sockPath: filepath.Join(sharedDirectory, "log.sock"), - handler: handler, - } -} - -func (c *LogClient) Connect() error { - conn, err := net.DialUnix("unix", nil, &net.UnixAddr{ - Name: c.sockPath, - Net: "unix", - }) - if err != nil { - return err - } - c.conn = conn - go c.loopConnection(&messageConn{conn}) - return nil -} - -func (c *LogClient) Disconnect() error { - return common.Close(c.conn) -} - -func (c *LogClient) loopConnection(conn *messageConn) { - c.handler.Connected() - defer c.handler.Disconnected() - for { - message, err := conn.Read() - if err != nil { - c.handler.WriteLog("(log client error) " + err.Error()) - return - } - c.handler.WriteLog(string(message)) - } -} diff --git a/experimental/libbox/log_server.go b/experimental/libbox/log_server.go deleted file mode 100644 index 1e0eb0a1..00000000 --- a/experimental/libbox/log_server.go +++ /dev/null @@ -1,139 +0,0 @@ -//go:build darwin - -package libbox - -import ( - "encoding/binary" - "io" - "net" - "os" - "path/filepath" - "sync" - - "github.com/sagernet/sing-box/log" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/observable" - "github.com/sagernet/sing/common/x/list" -) - -type LogServer struct { - sockPath string - listener net.Listener - - access sync.Mutex - savedLines *list.List[string] - subscriber *observable.Subscriber[string] - observer *observable.Observer[string] -} - -func NewLogServer(sharedDirectory string) *LogServer { - server := &LogServer{ - sockPath: filepath.Join(sharedDirectory, "log.sock"), - savedLines: new(list.List[string]), - subscriber: observable.NewSubscriber[string](128), - } - server.observer = observable.NewObserver[string](server.subscriber, 64) - return server -} - -func (s *LogServer) Start() error { - os.Remove(s.sockPath) - listener, err := net.ListenUnix("unix", &net.UnixAddr{ - Name: s.sockPath, - Net: "unix", - }) - if err != nil { - return err - } - go s.loopConnection(listener) - return nil -} - -func (s *LogServer) Close() error { - return s.listener.Close() -} - -func (s *LogServer) WriteMessage(message string) { - s.subscriber.Emit(message) - s.access.Lock() - s.savedLines.PushBack(message) - if s.savedLines.Len() > 100 { - s.savedLines.Remove(s.savedLines.Front()) - } - s.access.Unlock() -} - -func (s *LogServer) loopConnection(listener net.Listener) { - for { - conn, err := listener.Accept() - if err != nil { - return - } - go func() { - hErr := s.handleConnection(&messageConn{conn}) - if hErr != nil && !E.IsClosed(err) { - log.Warn("log-server: process connection: ", hErr) - } - }() - } -} - -func (s *LogServer) handleConnection(conn *messageConn) error { - var savedLines []string - s.access.Lock() - savedLines = make([]string, 0, s.savedLines.Len()) - for element := s.savedLines.Front(); element != nil; element = element.Next() { - savedLines = append(savedLines, element.Value) - } - s.access.Unlock() - subscription, done, err := s.observer.Subscribe() - if err != nil { - return err - } - defer s.observer.UnSubscribe(subscription) - for _, line := range savedLines { - err = conn.Write([]byte(line)) - if err != nil { - return err - } - } - for { - select { - case message := <-subscription: - err = conn.Write([]byte(message)) - if err != nil { - return err - } - case <-done: - conn.Close() - return nil - } - } -} - -type messageConn struct { - net.Conn -} - -func (c *messageConn) Read() ([]byte, error) { - var messageLength uint16 - err := binary.Read(c.Conn, binary.BigEndian, &messageLength) - if err != nil { - return nil, err - } - data := make([]byte, messageLength) - _, err = io.ReadFull(c.Conn, data) - if err != nil { - return nil, err - } - return data, nil -} - -func (c *messageConn) Write(message []byte) error { - err := binary.Write(c.Conn, binary.BigEndian, uint16(len(message))) - if err != nil { - return err - } - _, err = c.Conn.Write(message) - return err -} diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index be38d3ab..97c7e581 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -1,6 +1,10 @@ package libbox -import C "github.com/sagernet/sing-box/constant" +import ( + C "github.com/sagernet/sing-box/constant" + + "github.com/dustin/go-humanize" +) func SetBasePath(path string) { C.SetBasePath(path) @@ -9,3 +13,7 @@ func SetBasePath(path string) { func Version() string { return C.Version } + +func FormatBytes(length int64) string { + return humanize.Bytes(uint64(length)) +} diff --git a/test/box_test.go b/test/box_test.go index d2f20c9a..db6075b9 100644 --- a/test/box_test.go +++ b/test/box_test.go @@ -37,7 +37,7 @@ func startInstance(t *testing.T, options option.Options) *box.Box { var instance *box.Box var err error for retry := 0; retry < 3; retry++ { - instance, err = box.New(ctx, options) + instance, err = box.New(ctx, options, nil) require.NoError(t, err) err = instance.Start() if err != nil { diff --git a/test/vless_test.go b/test/vless_test.go index 52005f7a..6dea7707 100644 --- a/test/vless_test.go +++ b/test/vless_test.go @@ -167,7 +167,7 @@ func testVLESSXray(t *testing.T, packetEncoding string, flow string) { }, UUID: userID.String(), Flow: flow, - PacketEncoding: packetEncoding, + PacketEncoding: &packetEncoding, TLS: &option.OutboundTLSOptions{ Enabled: true, ServerName: "example.org",