Răsfoiți Sursa

Refactor platform command api

世界 2 ani în urmă
părinte
comite
5af8d001ae

+ 4 - 3
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")

+ 8 - 0
experimental/libbox/command.go

@@ -0,0 +1,8 @@
+//go:build darwin
+
+package libbox
+
+const (
+	CommandLog int32 = iota
+	CommandStatus
+)

+ 69 - 0
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)
+}

+ 104 - 0
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
+}

+ 85 - 0
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)
+	}
+}

+ 61 - 0
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)
+	}
+}

+ 0 - 59
experimental/libbox/log_client.go

@@ -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))
-	}
-}

+ 0 - 139
experimental/libbox/log_server.go

@@ -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
-}

+ 9 - 1
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))
+}

+ 1 - 1
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 {

+ 1 - 1
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",