diff --git a/Makefile b/Makefile index 25910d3f..d69247e0 100644 --- a/Makefile +++ b/Makefile @@ -89,8 +89,8 @@ lib: lib_install: go get -v -d - go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230701084532-493ee2e45182 - go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230701084532-493ee2e45182 + go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230728014906-3de089147f59 + go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230728014906-3de089147f59 clean: rm -rf bin dist sing-box diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index f65262ba..f6540c85 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -107,7 +107,7 @@ func buildiOS() { args := []string{ "bind", "-v", - "-target", "ios,iossimulator,macos", + "-target", "ios,iossimulator,tvos,tvossimulator,macos", "-libname=box", } if !debugEnabled { diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index 5c491777..11dccdd3 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -10,10 +10,9 @@ import ( ) type CommandClient struct { - sharedDirectory string - handler CommandClientHandler - conn net.Conn - options CommandClientOptions + handler CommandClientHandler + conn net.Conn + options CommandClientOptions } type CommandClientOptions struct { @@ -29,25 +28,26 @@ type CommandClientHandler interface { WriteGroups(message OutboundGroupIterator) } -func NewStandaloneCommandClient(sharedDirectory string) *CommandClient { - return &CommandClient{ - sharedDirectory: sharedDirectory, - } +func NewStandaloneCommandClient() *CommandClient { + return new(CommandClient) } func NewCommandClient(sharedDirectory string, handler CommandClientHandler, options *CommandClientOptions) *CommandClient { return &CommandClient{ - sharedDirectory: sharedDirectory, - handler: handler, - options: common.PtrValueOrDefault(options), + handler: handler, + options: common.PtrValueOrDefault(options), } } func (c *CommandClient) directConnect() (net.Conn, error) { - return net.DialUnix("unix", nil, &net.UnixAddr{ - Name: filepath.Join(c.sharedDirectory, "command.sock"), - Net: "unix", - }) + if !sTVOS { + return net.DialUnix("unix", nil, &net.UnixAddr{ + Name: filepath.Join(sBasePath, "command.sock"), + Net: "unix", + }) + } else { + return net.Dial("tcp", "127.0.0.1:8964") + } } func (c *CommandClient) Connect() error { diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index f8aa03e2..dabb6f87 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -18,7 +18,6 @@ import ( ) type CommandServer struct { - sockPath string listener net.Listener handler CommandServerHandler @@ -37,9 +36,8 @@ type CommandServerHandler interface { ServiceReload() error } -func NewCommandServer(sharedDirectory string, handler CommandServerHandler, maxLines int32) *CommandServer { +func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer { server := &CommandServer{ - sockPath: filepath.Join(sharedDirectory, "command.sock"), handler: handler, savedLines: new(list.List[string]), maxLines: int(maxLines), @@ -70,20 +68,29 @@ func (s *CommandServer) notifyURLTestUpdate() { } func (s *CommandServer) Start() error { - os.Remove(s.sockPath) + if !sTVOS { + return s.listenUNIX() + } else { + return s.listenTCP() + } +} + +func (s *CommandServer) listenUNIX() error { + sockPath := filepath.Join(sBasePath, "command.sock") + os.Remove(sockPath) listener, err := net.ListenUnix("unix", &net.UnixAddr{ - Name: s.sockPath, + Name: sockPath, Net: "unix", }) if err != nil { - return err + return E.Cause(err, "listen") } if sUserID > 0 { - err = os.Chown(s.sockPath, sUserID, sGroupID) + err = os.Chown(sockPath, sUserID, sGroupID) if err != nil { listener.Close() - os.Remove(s.sockPath) - return err + os.Remove(sockPath) + return E.Cause(err, "chown") } } s.listener = listener @@ -91,6 +98,16 @@ func (s *CommandServer) Start() error { return nil } +func (s *CommandServer) listenTCP() error { + listener, err := net.Listen("tcp", "127.0.0.1:8964") + if err != nil { + return E.Cause(err, "listen") + } + s.listener = listener + go s.loopConnection(listener) + return nil +} + func (s *CommandServer) Close() error { return common.Close( s.listener, diff --git a/experimental/libbox/profile_import.go b/experimental/libbox/profile_import.go new file mode 100644 index 00000000..7b2b7cb6 --- /dev/null +++ b/experimental/libbox/profile_import.go @@ -0,0 +1,219 @@ +package libbox + +import ( + "bytes" + "encoding/binary" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/rw" +) + +func EncodeChunkedMessage(data []byte) []byte { + var buffer bytes.Buffer + binary.Write(&buffer, binary.BigEndian, uint16(len(data))) + buffer.Write(data) + return buffer.Bytes() +} + +func DecodeLengthChunk(data []byte) int32 { + return int32(binary.BigEndian.Uint16(data)) +} + +const ( + MessageTypeError = iota + MessageTypeProfileList + MessageTypeProfileContentRequest + MessageTypeProfileContent +) + +type ErrorMessage struct { + Message string +} + +func (e *ErrorMessage) Encode() []byte { + var buffer bytes.Buffer + buffer.WriteByte(MessageTypeError) + rw.WriteVString(&buffer, e.Message) + return buffer.Bytes() +} + +func DecodeErrorMessage(data []byte) (*ErrorMessage, error) { + reader := bytes.NewReader(data) + messageType, err := rw.ReadByte(reader) + if err != nil { + return nil, err + } + if messageType != MessageTypeError { + return nil, E.New("invalid message") + } + var message ErrorMessage + message.Message, err = rw.ReadVString(reader) + if err != nil { + return nil, err + } + return &message, nil +} + +const ( + ProfileTypeLocal int32 = iota + ProfileTypeiCloud + ProfileTypeRemote +) + +type ProfilePreview struct { + ProfileID int64 + Name string + Type int32 +} + +type ProfilePreviewIterator interface { + Next() *ProfilePreview + HasNext() bool +} + +type ProfileEncoder struct { + profiles []ProfilePreview +} + +func (e *ProfileEncoder) Append(profile *ProfilePreview) { + e.profiles = append(e.profiles, *profile) +} + +func (e *ProfileEncoder) Encode() []byte { + var buffer bytes.Buffer + buffer.WriteByte(MessageTypeProfileList) + binary.Write(&buffer, binary.BigEndian, uint16(len(e.profiles))) + for _, preview := range e.profiles { + binary.Write(&buffer, binary.BigEndian, preview.ProfileID) + rw.WriteVString(&buffer, preview.Name) + binary.Write(&buffer, binary.BigEndian, preview.Type) + } + return buffer.Bytes() +} + +type ProfileDecoder struct { + profiles []*ProfilePreview +} + +func (d *ProfileDecoder) Decode(data []byte) error { + reader := bytes.NewReader(data) + messageType, err := reader.ReadByte() + if err != nil { + return err + } + if messageType != MessageTypeProfileList { + return E.New("invalid message") + } + var profileCount uint16 + err = binary.Read(reader, binary.BigEndian, &profileCount) + if err != nil { + return err + } + for i := 0; i < int(profileCount); i++ { + var profile ProfilePreview + err = binary.Read(reader, binary.BigEndian, &profile.ProfileID) + if err != nil { + return err + } + profile.Name, err = rw.ReadVString(reader) + if err != nil { + return err + } + err = binary.Read(reader, binary.BigEndian, &profile.Type) + if err != nil { + return err + } + d.profiles = append(d.profiles, &profile) + } + return nil +} + +func (d *ProfileDecoder) Iterator() ProfilePreviewIterator { + return newIterator(d.profiles) +} + +type ProfileContentRequest struct { + ProfileID int64 +} + +func (r *ProfileContentRequest) Encode() []byte { + var buffer bytes.Buffer + buffer.WriteByte(MessageTypeProfileContentRequest) + binary.Write(&buffer, binary.BigEndian, r.ProfileID) + return buffer.Bytes() +} + +func DecodeProfileContentRequest(data []byte) (*ProfileContentRequest, error) { + reader := bytes.NewReader(data) + messageType, err := rw.ReadByte(reader) + if err != nil { + return nil, err + } + if messageType != MessageTypeProfileContentRequest { + return nil, E.New("invalid message") + } + var request ProfileContentRequest + err = binary.Read(reader, binary.BigEndian, &request.ProfileID) + if err != nil { + return nil, err + } + return &request, nil +} + +type ProfileContent struct { + Name string + Type int32 + Config string + RemotePath string + AutoUpdate bool + LastUpdated int64 +} + +func (c *ProfileContent) Encode() []byte { + var buffer bytes.Buffer + buffer.WriteByte(MessageTypeProfileContent) + rw.WriteVString(&buffer, c.Name) + binary.Write(&buffer, binary.BigEndian, c.Type) + rw.WriteVString(&buffer, c.Config) + rw.WriteVString(&buffer, c.RemotePath) + binary.Write(&buffer, binary.BigEndian, c.AutoUpdate) + binary.Write(&buffer, binary.BigEndian, c.LastUpdated) + return buffer.Bytes() +} + +func DecodeProfileContent(data []byte) (*ProfileContent, error) { + reader := bytes.NewReader(data) + messageType, err := rw.ReadByte(reader) + if err != nil { + return nil, err + } + if messageType != MessageTypeProfileContent { + return nil, E.New("invalid message") + } + var content ProfileContent + content.Name, err = rw.ReadVString(reader) + if err != nil { + return nil, err + } + err = binary.Read(reader, binary.BigEndian, &content.Type) + if err != nil { + return nil, err + } + content.Config, err = rw.ReadVString(reader) + if err != nil { + return nil, err + } + content.RemotePath, err = rw.ReadVString(reader) + if err != nil { + return nil, err + } + err = binary.Read(reader, binary.BigEndian, &content.AutoUpdate) + if err != nil { + return nil, err + } + err = binary.Read(reader, binary.BigEndian, &content.LastUpdated) + if err != nil { + return nil, err + } + return &content, nil +} diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 1ed2d68e..f37f8408 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -33,7 +33,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box return nil, err } ctx, cancel := context.WithCancel(context.Background()) - ctx = filemanager.WithDefault(ctx, sBasePath, sTempPath, sUserID, sGroupID) + ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) ctx = service.ContextWithPtr(ctx, urltest.NewHistoryStorage()) instance, err := box.New(box.Options{ Context: ctx, diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index 83ae0424..2348a03a 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -11,21 +11,26 @@ import ( ) var ( - sBasePath string - sTempPath string - sUserID int - sGroupID int + sBasePath string + sWorkingPath string + sTempPath string + sUserID int + sGroupID int + sTVOS bool ) -func Setup(basePath string, tempPath string) { +func Setup(basePath string, workingPath string, tempPath string, isTVOS bool) { sBasePath = basePath + sWorkingPath = workingPath sTempPath = tempPath sUserID = os.Getuid() sGroupID = os.Getgid() + sTVOS = isTVOS } -func SetupWithUsername(basePath string, tempPath string, username string) error { +func SetupWithUsername(basePath string, workingPath string, tempPath string, username string) error { sBasePath = basePath + sWorkingPath = workingPath sTempPath = tempPath sUser, err := user.Lookup(username) if err != nil { diff --git a/go.mod b/go.mod index 09ea69d4..2ce33680 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/oschwald/maxminddb-golang v1.11.0 github.com/pires/go-proxyproto v0.7.0 github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 - github.com/sagernet/gomobile v0.0.0-20230701084532-493ee2e45182 + github.com/sagernet/gomobile v0.0.0-20230728014906-3de089147f59 github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 diff --git a/go.sum b/go.sum index 7bf8f1d0..265fe540 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,8 @@ github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms= -github.com/sagernet/gomobile v0.0.0-20230701084532-493ee2e45182 h1:sD5g92IO15RAX2DvA4Cq3Uc7tcgqNWVi8K3VTCI6sEo= -github.com/sagernet/gomobile v0.0.0-20230701084532-493ee2e45182/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU= +github.com/sagernet/gomobile v0.0.0-20230728014906-3de089147f59 h1:vN4divY6LYHcYmiTsCHNPmGZtEsEKJzh81LyvgAQfEQ= +github.com/sagernet/gomobile v0.0.0-20230728014906-3de089147f59/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU= github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTSWt6hdPrARORfoYvuUczynvRLrueo= github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=