diff --git a/adapter/platform.go b/adapter/platform.go index 6362a19c..b068b37b 100644 --- a/adapter/platform.go +++ b/adapter/platform.go @@ -38,6 +38,7 @@ type PlatformInterface interface { UsePlatformNotification() bool SendNotification(notification *Notification) error + CancelNotification(identifier string, typeID int32) error MyInterfaceAddress() []netip.Addr diff --git a/adapter/tailscale.go b/adapter/tailscale.go index b87e594f..a97430ab 100644 --- a/adapter/tailscale.go +++ b/adapter/tailscale.go @@ -1,32 +1,66 @@ package adapter -import "context" +import ( + "context" + "io" +) type TailscaleEndpoint interface { SubscribeTailscaleStatus(ctx context.Context, fn func(*TailscaleEndpointStatus)) error StartTailscalePing(ctx context.Context, peerIP string, fn func(*TailscalePingResult)) error SetTailscaleExitNode(ctx context.Context, stableID string) error Logout(ctx context.Context) error + SubscribeTaildropInbox(ctx context.Context, fn func(*TaildropInbox)) error + MarkTaildropInboxRead() error + SendTaildropFile(ctx context.Context, peerStableID string, fileName string, size int64, content io.Reader, progress func(sentBytes int64)) error + OpenTaildropFile(fileName string) (io.ReadCloser, int64, error) + DeleteTaildropFile(fileName string) error + CancelTaildropReceiving(senderID string, fileName string) error +} + +type TaildropInbox struct { + Files []*TaildropFile + Receiving []*TaildropReceivingFile +} + +type TaildropFile struct { + Name string + Size int64 + SenderName string + ModifiedAt int64 +} + +type TaildropReceivingFile struct { + Name string + Size int64 + ReceivedBytes int64 + SenderID string + SenderName string } type TailscalePingResult struct { LatencyMs float64 IsDirect bool Endpoint string + PeerRelay string DERPRegionID int32 DERPRegionCode string Error string } type TailscaleEndpointStatus struct { - BackendState string - AuthURL string - NetworkName string - MagicDNSSuffix string - Self *TailscalePeer - ExitNode *TailscalePeer - UserGroups []*TailscaleUserGroup - KeyAuth bool + BackendState string + AuthURL string + NetworkName string + MagicDNSSuffix string + Self *TailscalePeer + ExitNode *TailscalePeer + UserGroups []*TailscaleUserGroup + KeyAuth bool + CanShareFiles bool + WaitingFileCount int32 + ReceivingFileCount int32 + UnreadFileCount int32 } type TailscaleUserGroup struct { @@ -38,23 +72,24 @@ type TailscaleUserGroup struct { } type TailscalePeer struct { - StableID string - HostName string - DNSName string - OS string - TailscaleIPs []string - SSHHostKeys []string - Online bool - ExitNode bool - ExitNodeOption bool - ShareeNode bool - Expired bool - Active bool - RxBytes int64 - TxBytes int64 - UserID int64 - KeyExpiry int64 - LastSeen int64 + StableID string + HostName string + DNSName string + OS string + TailscaleIPs []string + SSHHostKeys []string + Online bool + ExitNode bool + ExitNodeOption bool + ShareeNode bool + Expired bool + Active bool + CanReceiveFiles bool + RxBytes int64 + TxBytes int64 + UserID int64 + KeyExpiry int64 + LastSeen int64 } type ShellSession interface { diff --git a/cmd/sing-box/cmd_api_tailscale_ping.go b/cmd/sing-box/cmd_api_tailscale_ping.go index 0269e48f..be717d7b 100644 --- a/cmd/sing-box/cmd_api_tailscale_ping.go +++ b/cmd/sing-box/cmd_api_tailscale_ping.go @@ -79,7 +79,10 @@ func runAPITailscalePing(selector string) error { }() timer := time.NewTimer(commandAPITailscalePingTimeout) defer timer.Stop() - var pongCount int + var ( + pongCount int + lastPeerRelay string + ) for pongCount < commandAPITailscalePingCount { var ( response *daemon.TailscalePingResponse @@ -109,16 +112,23 @@ func runAPITailscalePing(selector string) error { if response.GetEndpoint() != "" { return nil } + lastPeerRelay = response.GetPeerRelay() timer.Reset(commandAPITailscalePingTimeout) } - os.Stdout.WriteString("direct connection not established\n") + if lastPeerRelay != "" { + os.Stdout.WriteString(F.ToString("direct connection not established, relayed by peer relay ", lastPeerRelay, "\n")) + } else { + os.Stdout.WriteString("direct connection not established\n") + } return nil } func formatTailscalePong(peerName string, peerAddress string, response *daemon.TailscalePingResponse) string { via := response.GetEndpoint() if via == "" { - if response.GetDerpRegionCode() != "" { + if response.GetPeerRelay() != "" { + via = F.ToString("peer relay ", response.GetPeerRelay()) + } else if response.GetDerpRegionCode() != "" { via = F.ToString("DERP(", response.GetDerpRegionCode(), ")") } else { via = F.ToString("DERP(", response.GetDerpRegionID(), ")") diff --git a/cmd/sing-box/cmd_api_tailscale_taildrop.go b/cmd/sing-box/cmd_api_tailscale_taildrop.go new file mode 100644 index 00000000..a3f794b3 --- /dev/null +++ b/cmd/sing-box/cmd_api_tailscale_taildrop.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + "strconv" + "time" + + "github.com/sagernet/sing-box/daemon" + F "github.com/sagernet/sing/common/format" + + "github.com/spf13/cobra" +) + +var commandAPITailscaleTaildrop = &cobra.Command{ + Use: "taildrop", + Short: "Send and receive files over Tailscale", +} + +func init() { + commandAPITailscaleTaildrop.PersistentFlags().StringVar(&commandAPITailscaleFlagEndpoint, "endpoint", "", commandAPITailscaleEndpointUsage) + commandAPITailscale.AddCommand(commandAPITailscaleTaildrop) +} + +func fetchTaildropInbox(client daemon.StartedServiceClient, endpointTag string) (*daemon.TaildropInbox, error) { + ctx, cancel := context.WithCancel(globalCtx) + defer cancel() + stream, err := client.SubscribeTaildropInbox(ctx, &daemon.SubscribeTaildropInboxRequest{ + EndpointTag: endpointTag, + }) + if err != nil { + return nil, err + } + return stream.Recv() +} + +func formatTaildropSize(size int64) string { + switch { + case size < 0: + return "-" + case size < 1000: + return F.ToString(size, " B") + case size < 1000*1000: + return formatTaildropSizeUnit(size, 1000, "KB") + case size < 1000*1000*1000: + return formatTaildropSizeUnit(size, 1000*1000, "MB") + default: + return formatTaildropSizeUnit(size, 1000*1000*1000, "GB") + } +} + +func formatTaildropSizeUnit(size int64, unit int64, unitName string) string { + value := float64(size) / float64(unit) + var precision int + switch { + case value < 10: + precision = 2 + case value < 100: + precision = 1 + } + return strconv.FormatFloat(value, 'f', precision, 64) + " " + unitName +} + +func formatTaildropModifiedAt(timestamp int64) string { + if timestamp == 0 { + return "" + } + return time.Unix(timestamp, 0).Local().Format("2006-01-02 15:04") +} diff --git a/cmd/sing-box/cmd_api_tailscale_taildrop_delete.go b/cmd/sing-box/cmd_api_tailscale_taildrop_delete.go new file mode 100644 index 00000000..4c51c1dd --- /dev/null +++ b/cmd/sing-box/cmd_api_tailscale_taildrop_delete.go @@ -0,0 +1,42 @@ +package main + +import ( + "github.com/sagernet/sing-box/daemon" + + "github.com/spf13/cobra" +) + +var commandAPITailscaleTaildropDelete = &cobra.Command{ + Use: "delete ...", + Short: "Delete received files", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return runAPITaildropDelete(args) + }, +} + +func init() { + commandAPITailscaleTaildrop.AddCommand(commandAPITailscaleTaildropDelete) +} + +func runAPITaildropDelete(names []string) error { + clientConn, client, err := createAPIClient() + if err != nil { + return err + } + defer clientConn.Close() + endpointTag, err := resolveTailscaleEndpointTag(client) + if err != nil { + return err + } + for _, name := range names { + _, err = client.DeleteTaildropFile(globalCtx, &daemon.DeleteTaildropFileRequest{ + EndpointTag: endpointTag, + Name: name, + }) + if err != nil { + return err + } + } + return nil +} diff --git a/cmd/sing-box/cmd_api_tailscale_taildrop_get.go b/cmd/sing-box/cmd_api_tailscale_taildrop_get.go new file mode 100644 index 00000000..88aa2f04 --- /dev/null +++ b/cmd/sing-box/cmd_api_tailscale_taildrop_get.go @@ -0,0 +1,119 @@ +package main + +import ( + "io" + "os" + "os/signal" + "path/filepath" + "syscall" + + "github.com/sagernet/sing-box/daemon" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + + "github.com/spf13/cobra" +) + +var commandAPITailscaleTaildropGet = &cobra.Command{ + Use: "get [output]", + Short: "Save a received file", + Args: cobra.RangeArgs(1, 2), + RunE: func(cmd *cobra.Command, args []string) error { + var outputPath string + if len(args) > 1 { + outputPath = args[1] + } + return runAPITaildropGet(args[0], outputPath) + }, +} + +func init() { + commandAPITailscaleTaildrop.AddCommand(commandAPITailscaleTaildropGet) +} + +func runAPITaildropGet(name string, outputPath string) error { + clientConn, client, err := createAPIClient() + if err != nil { + return err + } + defer clientConn.Close() + endpointTag, err := resolveTailscaleEndpointTag(client) + if err != nil { + return err + } + if outputPath == "" { + outputPath = name + } else { + information, statErr := os.Stat(outputPath) + if statErr == nil && information.IsDir() { + outputPath = filepath.Join(outputPath, name) + } + } + ctx, cancel := signal.NotifyContext(globalCtx, os.Interrupt, syscall.SIGTERM) + defer cancel() + stream, err := client.DownloadTaildropFile(ctx, &daemon.DownloadTaildropFileRequest{ + EndpointTag: endpointTag, + Name: name, + }) + if err != nil { + return err + } + firstChunk, err := stream.Recv() + if err != nil { + return err + } + totalSize := firstChunk.GetSize() + var outputFile *os.File + if outputPath == "-" { + outputFile = os.Stdout + } else { + outputFile, err = os.OpenFile(outputPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0o644) + if err != nil { + return err + } + } + var downloaded int64 + writeChunk := func(data []byte) error { + if len(data) == 0 { + return nil + } + _, writeErr := outputFile.Write(data) + if writeErr != nil { + return writeErr + } + downloaded += int64(len(data)) + writeProgress(F.ToString(name, ": ", formatTaildropSize(downloaded), " / ", formatTaildropSize(totalSize), " ")) + return nil + } + err = writeChunk(firstChunk.GetData()) + for err == nil { + var chunk *daemon.DownloadTaildropFileChunk + chunk, err = stream.Recv() + if err == io.EOF { + err = nil + break + } + if err != nil { + break + } + err = writeChunk(chunk.GetData()) + } + if outputPath != "-" { + closeErr := outputFile.Close() + if err == nil { + err = closeErr + } + if err != nil { + os.Remove(outputPath) + } + } + if err != nil { + if ctx.Err() != nil { + return E.New("interrupted") + } + return err + } + writeProgress("") + writeStderrLine(F.ToString("saved ", name, " (", formatTaildropSize(downloaded), ")")) + return nil +} diff --git a/cmd/sing-box/cmd_api_tailscale_taildrop_list.go b/cmd/sing-box/cmd_api_tailscale_taildrop_list.go new file mode 100644 index 00000000..e51d66f4 --- /dev/null +++ b/cmd/sing-box/cmd_api_tailscale_taildrop_list.go @@ -0,0 +1,55 @@ +package main + +import ( + "github.com/spf13/cobra" +) + +var commandAPITailscaleTaildropList = &cobra.Command{ + Use: "list", + Short: "List received files", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + return runAPITaildropList() + }, +} + +func init() { + commandAPITailscaleTaildrop.AddCommand(commandAPITailscaleTaildropList) +} + +func runAPITaildropList() error { + clientConn, client, err := createAPIClient() + if err != nil { + return err + } + defer clientConn.Close() + endpointTag, err := resolveTailscaleEndpointTag(client) + if err != nil { + return err + } + inbox, err := fetchTaildropInbox(client, endpointTag) + if err != nil { + return err + } + for _, file := range inbox.GetReceiving() { + writeStderrLine("receiving " + file.GetName() + ": " + formatTaildropSize(file.GetReceivedBytes()) + " / " + formatTaildropSize(file.GetSize())) + } + table := tableWriter{ + header: []string{"NAME", "SIZE", "FROM", "RECEIVED"}, + emptyMessage: "no received files", + } + for _, file := range inbox.GetFiles() { + sender := file.GetSenderName() + if sender == "" { + sender = "-" + } + table.addRow( + file.GetName(), + formatTaildropSize(file.GetSize()), + sender, + formatTaildropModifiedAt(file.GetModifiedAt()), + ) + } + table.flush() + return nil +} diff --git a/cmd/sing-box/cmd_api_tailscale_taildrop_send.go b/cmd/sing-box/cmd_api_tailscale_taildrop_send.go new file mode 100644 index 00000000..022833d2 --- /dev/null +++ b/cmd/sing-box/cmd_api_tailscale_taildrop_send.go @@ -0,0 +1,182 @@ +package main + +import ( + "io" + "os" + "os/signal" + "path/filepath" + "syscall" + + "github.com/sagernet/sing-box/daemon" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + + "github.com/spf13/cobra" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var commandAPITailscaleTaildropSend = &cobra.Command{ + Use: "send ...", + Short: "Send files to a Tailscale peer", + Args: cobra.MinimumNArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + return runAPITaildropSend(args[0], args[1:]) + }, +} + +func init() { + commandAPITailscaleTaildrop.AddCommand(commandAPITailscaleTaildropSend) +} + +func runAPITaildropSend(selector string, filePaths []string) error { + clientConn, client, err := createAPIClient() + if err != nil { + return err + } + defer clientConn.Close() + endpoint, err := fetchTailscaleEndpoint(client) + if err != nil { + return err + } + if !endpoint.GetCanShareFiles() { + return E.New("file sharing not enabled by Tailscale admin") + } + entry, err := resolveTailscalePeer(tailscalePeerEntries(endpoint), selector) + if err != nil { + return err + } + if entry.self { + return E.New("cannot send files to this device") + } + if !entry.peer.GetCanReceiveFiles() { + return E.New("peer cannot receive files: ", tailscalePeerName(entry.peer)) + } + + type outgoingFile struct { + path string + name string + size int64 + } + files := make([]outgoingFile, 0, len(filePaths)) + manifest := make([]*daemon.TaildropOutgoingFile, 0, len(filePaths)) + for _, filePath := range filePaths { + information, statErr := os.Stat(filePath) + if statErr != nil { + return statErr + } + if information.IsDir() { + return E.New("directories are not supported: ", filePath) + } + file := outgoingFile{ + path: filePath, + name: filepath.Base(filePath), + size: information.Size(), + } + files = append(files, file) + manifest = append(manifest, &daemon.TaildropOutgoingFile{ + Name: file.name, + Size: file.size, + }) + } + + ctx, cancel := signal.NotifyContext(globalCtx, os.Interrupt, syscall.SIGTERM) + defer cancel() + stream, err := client.SendTaildropFiles(ctx) + if err != nil { + return err + } + err = stream.Send(&daemon.TaildropSendClientMessage{ + Message: &daemon.TaildropSendClientMessage_Start{Start: &daemon.TaildropSendStart{ + EndpointTag: endpoint.GetEndpointTag(), + PeerStableID: entry.peer.GetStableID(), + Files: manifest, + }}, + }) + if err != nil { + return err + } + + var uploadErr error + uploadDone := make(chan struct{}) + go func() { + defer close(uploadDone) + failUpload := func(cause error) { + uploadErr = cause + cancel() + } + buffer := make([]byte, daemon.TaildropChunkSize) + for _, file := range files { + sourceFile, openErr := os.Open(file.path) + if openErr != nil { + failUpload(openErr) + return + } + for { + n, readErr := sourceFile.Read(buffer) + if n > 0 { + sendErr := stream.Send(&daemon.TaildropSendClientMessage{ + Message: &daemon.TaildropSendClientMessage_Chunk{Chunk: &daemon.TaildropFileChunk{Data: buffer[:n]}}, + }) + if sendErr != nil { + sourceFile.Close() + failUpload(sendErr) + return + } + } + if readErr == io.EOF { + break + } + if readErr != nil { + sourceFile.Close() + failUpload(readErr) + return + } + } + sourceFile.Close() + sendErr := stream.Send(&daemon.TaildropSendClientMessage{ + Message: &daemon.TaildropSendClientMessage_FileDone{FileDone: &daemon.TaildropFileDone{}}, + }) + if sendErr != nil { + failUpload(sendErr) + return + } + } + }() + + peerName := tailscalePeerName(entry.peer) + for { + message, recvErr := stream.Recv() + if recvErr != nil { + cancel() + <-uploadDone + if uploadErr != nil && !E.IsCanceled(uploadErr) && status.Code(uploadErr) != codes.Canceled { + return uploadErr + } + if ctx.Err() != nil { + return E.New("interrupted") + } + if recvErr == io.EOF { + writeProgress("") + writeStderrLine(F.ToString("sent ", len(files), " file(s) to ", peerName)) + return nil + } + return recvErr + } + progress := message.GetProgress() + if progress == nil { + continue + } + fileIndex := progress.FileIndex + if fileIndex < 0 || int(fileIndex) >= len(files) { + return E.New("invalid file index: ", fileIndex) + } + file := files[fileIndex] + if progress.FileCompleted { + writeProgress("") + writeStderrLine(F.ToString(file.name, ": done")) + } else { + writeProgress(F.ToString(file.name, ": ", formatTaildropSize(progress.SentBytes), " / ", formatTaildropSize(file.size), " ")) + } + } +} diff --git a/cmd/sing-box/cmd_api_tailscale_taildrop_targets.go b/cmd/sing-box/cmd_api_tailscale_taildrop_targets.go new file mode 100644 index 00000000..4014470f --- /dev/null +++ b/cmd/sing-box/cmd_api_tailscale_taildrop_targets.go @@ -0,0 +1,56 @@ +package main + +import ( + E "github.com/sagernet/sing/common/exceptions" + + "github.com/spf13/cobra" +) + +var commandAPITailscaleTaildropTargets = &cobra.Command{ + Use: "targets", + Short: "List peers that can receive files", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + return runAPITaildropTargets() + }, +} + +func init() { + commandAPITailscaleTaildrop.AddCommand(commandAPITailscaleTaildropTargets) +} + +func runAPITaildropTargets() error { + clientConn, client, err := createAPIClient() + if err != nil { + return err + } + defer clientConn.Close() + endpoint, err := fetchTailscaleEndpoint(client) + if err != nil { + return err + } + if !endpoint.GetCanShareFiles() { + return E.New("file sharing not enabled by Tailscale admin") + } + entries := tailscalePeerEntries(endpoint) + if len(entries) > 0 && entries[0].self { + entries = entries[1:] + } + sortTailscalePeerEntries(entries) + table := tableWriter{ + header: []string{"DNS NAME", "IP", "ONLINE"}, + emptyMessage: "no file targets", + } + for _, entry := range entries { + if !entry.peer.GetCanReceiveFiles() { + continue + } + table.addRow( + tailscalePeerName(entry.peer), + tailscalePeerAddress(entry.peer), + formatYesNo(entry.peer.GetOnline()), + ) + } + table.flush() + return nil +} diff --git a/daemon/started_service.go b/daemon/started_service.go index 5e2d63fd..59e315c1 100644 --- a/daemon/started_service.go +++ b/daemon/started_service.go @@ -34,9 +34,12 @@ import ( "google.golang.org/protobuf/types/known/emptypb" ) -const APIVersion = 3 +const APIVersion = 4 -const urlTestPushMinInterval = 250 * time.Millisecond +const ( + urlTestPushMinInterval = 250 * time.Millisecond + notificationQueueSize = 16 +) var _ StartedServiceServer = (*StartedService)(nil) @@ -68,6 +71,8 @@ type StartedService struct { urlTestObserver *observable.Observer[struct{}] clashModeSubscriber *observable.Subscriber[struct{}] clashModeObserver *observable.Observer[struct{}] + notificationSubscriber *observable.Subscriber[*NotificationEvent] + notificationObserver *observable.Observer[*NotificationEvent] } type ServiceOptions struct { @@ -106,11 +111,13 @@ func NewStartedService(options ServiceOptions) *StartedService { logSubscriber: observable.NewSubscriber[*log.Entry](128), urlTestSubscriber: observable.NewSubscriber[struct{}](1), clashModeSubscriber: observable.NewSubscriber[struct{}](1), + notificationSubscriber: observable.NewSubscriber[*NotificationEvent](notificationQueueSize), } s.serviceStatusObserver = observable.NewObserver(s.serviceStatusSubscriber, 2) s.logObserver = observable.NewObserver(s.logSubscriber, 64) s.urlTestObserver = observable.NewObserver(s.urlTestSubscriber, 1) s.clashModeObserver = observable.NewObserver(s.clashModeSubscriber, 1) + s.notificationObserver = observable.NewObserver(s.notificationSubscriber, notificationQueueSize) return s } @@ -283,6 +290,7 @@ func (s *StartedService) Close() { s.logSubscriber.Close() s.urlTestSubscriber.Close() s.clashModeSubscriber.Close() + s.notificationSubscriber.Close() } func (s *StartedService) CloseService() error { @@ -1625,14 +1633,18 @@ func tailscaleEndpointStatusToProto(tag string, s *adapter.TailscaleEndpointStat } } result := &TailscaleEndpointStatus{ - EndpointTag: tag, - BackendState: s.BackendState, - StateText: selectedLocale.TailscaleStateText(s.BackendState), - AuthURL: s.AuthURL, - NetworkName: s.NetworkName, - MagicDNSSuffix: s.MagicDNSSuffix, - UserGroups: userGroups, - KeyAuth: s.KeyAuth, + EndpointTag: tag, + BackendState: s.BackendState, + StateText: selectedLocale.TailscaleStateText(s.BackendState), + AuthURL: s.AuthURL, + NetworkName: s.NetworkName, + MagicDNSSuffix: s.MagicDNSSuffix, + UserGroups: userGroups, + KeyAuth: s.KeyAuth, + CanShareFiles: s.CanShareFiles, + WaitingFileCount: s.WaitingFileCount, + ReceivingFileCount: s.ReceivingFileCount, + UnreadFileCount: s.UnreadFileCount, } if s.Self != nil { result.Self = tailscalePeerToProto(s.Self) @@ -1645,22 +1657,23 @@ func tailscaleEndpointStatusToProto(tag string, s *adapter.TailscaleEndpointStat func tailscalePeerToProto(peer *adapter.TailscalePeer) *TailscalePeer { return &TailscalePeer{ - StableID: peer.StableID, - HostName: peer.HostName, - DnsName: peer.DNSName, - Os: peer.OS, - TailscaleIPs: peer.TailscaleIPs, - SshHostKeys: peer.SSHHostKeys, - Online: peer.Online, - ExitNode: peer.ExitNode, - ExitNodeOption: peer.ExitNodeOption, - ShareeNode: peer.ShareeNode, - Expired: peer.Expired, - Active: peer.Active, - RxBytes: peer.RxBytes, - TxBytes: peer.TxBytes, - KeyExpiry: peer.KeyExpiry, - LastSeen: peer.LastSeen, + StableID: peer.StableID, + HostName: peer.HostName, + DnsName: peer.DNSName, + Os: peer.OS, + TailscaleIPs: peer.TailscaleIPs, + SshHostKeys: peer.SSHHostKeys, + Online: peer.Online, + ExitNode: peer.ExitNode, + ExitNodeOption: peer.ExitNodeOption, + ShareeNode: peer.ShareeNode, + Expired: peer.Expired, + Active: peer.Active, + CanReceiveFiles: peer.CanReceiveFiles, + RxBytes: peer.RxBytes, + TxBytes: peer.TxBytes, + KeyExpiry: peer.KeyExpiry, + LastSeen: peer.LastSeen, } } @@ -1676,35 +1689,9 @@ func (s *StartedService) StartTailscalePing( boxService := s.instance s.serviceAccess.RUnlock() - var provider adapter.TailscaleEndpoint - if request.EndpointTag != "" { - endpoint, err := resolveTailscaleEndpoint(boxService, request.EndpointTag) - if err != nil { - return err - } - pingProvider, loaded := endpoint.(adapter.TailscaleEndpoint) - if !loaded { - return status.Error(codes.FailedPrecondition, "endpoint does not support ping") - } - provider = pingProvider - } else { - endpointManager := service.FromContext[adapter.EndpointManager](boxService.ctx) - if endpointManager == nil { - return status.Error(codes.FailedPrecondition, "endpoint manager not available") - } - for _, endpoint := range endpointManager.Endpoints() { - if endpoint.Type() != C.TypeTailscale { - continue - } - pingProvider, loaded := endpoint.(adapter.TailscaleEndpoint) - if loaded { - provider = pingProvider - break - } - } - if provider == nil { - return status.Error(codes.NotFound, "no Tailscale endpoint found") - } + provider, _, err := resolveTailscaleProvider(boxService, request.EndpointTag) + if err != nil { + return err } return provider.StartTailscalePing(server.Context(), request.PeerIP, func(result *adapter.TailscalePingResult) { @@ -1712,6 +1699,7 @@ func (s *StartedService) StartTailscalePing( LatencyMs: result.LatencyMs, IsDirect: result.IsDirect, Endpoint: result.Endpoint, + PeerRelay: result.PeerRelay, DerpRegionID: result.DERPRegionID, DerpRegionCode: result.DERPRegionCode, Error: result.Error, @@ -2019,6 +2007,58 @@ func (s *StartedService) CancelOpenVPNChallenge(ctx context.Context, request *Op return &emptypb.Empty{}, nil } +func (s *StartedService) SendNotification(notification *adapter.Notification) error { + s.notificationSubscriber.Emit(&NotificationEvent{ + Event: &NotificationEvent_Send{ + Send: &Notification{ + Identifier: notification.Identifier, + TypeName: notification.TypeName, + TypeID: notification.TypeID, + Title: notification.Title, + Subtitle: notification.Subtitle, + Body: notification.Body, + OpenURL: notification.OpenURL, + }, + }, + }) + return nil +} + +func (s *StartedService) CancelNotification(identifier string, typeID int32) error { + s.notificationSubscriber.Emit(&NotificationEvent{ + Event: &NotificationEvent_Cancel{ + Cancel: &NotificationCancel{ + Identifier: identifier, + TypeID: typeID, + }, + }, + }) + return nil +} + +func (s *StartedService) SubscribeNotifications(empty *emptypb.Empty, server grpc.ServerStreamingServer[NotificationEvent]) error { + subscription, done, err := s.notificationObserver.Subscribe() + if err != nil { + return err + } + defer s.notificationObserver.UnSubscribe(subscription) + for { + select { + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-done: + return nil + case event := <-subscription: + err = server.Send(event) + if err != nil { + return err + } + } + } +} + func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() { } diff --git a/daemon/started_service.pb.go b/daemon/started_service.pb.go index f3fb9192..da2056c8 100644 --- a/daemon/started_service.pb.go +++ b/daemon/started_service.pb.go @@ -2219,19 +2219,23 @@ func (x *TailscaleStatusUpdate) GetEndpoints() []*TailscaleEndpointStatus { } type TailscaleEndpointStatus struct { - state protoimpl.MessageState `protogen:"open.v1"` - EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` - BackendState string `protobuf:"bytes,2,opt,name=backendState,proto3" json:"backendState,omitempty"` - StateText string `protobuf:"bytes,3,opt,name=stateText,proto3" json:"stateText,omitempty"` - AuthURL string `protobuf:"bytes,4,opt,name=authURL,proto3" json:"authURL,omitempty"` - NetworkName string `protobuf:"bytes,5,opt,name=networkName,proto3" json:"networkName,omitempty"` - MagicDNSSuffix string `protobuf:"bytes,6,opt,name=magicDNSSuffix,proto3" json:"magicDNSSuffix,omitempty"` - Self *TailscalePeer `protobuf:"bytes,7,opt,name=self,proto3" json:"self,omitempty"` - UserGroups []*TailscaleUserGroup `protobuf:"bytes,8,rep,name=userGroups,proto3" json:"userGroups,omitempty"` - ExitNode *TailscalePeer `protobuf:"bytes,9,opt,name=exitNode,proto3" json:"exitNode,omitempty"` - KeyAuth bool `protobuf:"varint,10,opt,name=keyAuth,proto3" json:"keyAuth,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` + BackendState string `protobuf:"bytes,2,opt,name=backendState,proto3" json:"backendState,omitempty"` + StateText string `protobuf:"bytes,3,opt,name=stateText,proto3" json:"stateText,omitempty"` + AuthURL string `protobuf:"bytes,4,opt,name=authURL,proto3" json:"authURL,omitempty"` + NetworkName string `protobuf:"bytes,5,opt,name=networkName,proto3" json:"networkName,omitempty"` + MagicDNSSuffix string `protobuf:"bytes,6,opt,name=magicDNSSuffix,proto3" json:"magicDNSSuffix,omitempty"` + Self *TailscalePeer `protobuf:"bytes,7,opt,name=self,proto3" json:"self,omitempty"` + UserGroups []*TailscaleUserGroup `protobuf:"bytes,8,rep,name=userGroups,proto3" json:"userGroups,omitempty"` + ExitNode *TailscalePeer `protobuf:"bytes,9,opt,name=exitNode,proto3" json:"exitNode,omitempty"` + KeyAuth bool `protobuf:"varint,10,opt,name=keyAuth,proto3" json:"keyAuth,omitempty"` + CanShareFiles bool `protobuf:"varint,11,opt,name=canShareFiles,proto3" json:"canShareFiles,omitempty"` + WaitingFileCount int32 `protobuf:"varint,12,opt,name=waitingFileCount,proto3" json:"waitingFileCount,omitempty"` + ReceivingFileCount int32 `protobuf:"varint,13,opt,name=receivingFileCount,proto3" json:"receivingFileCount,omitempty"` + UnreadFileCount int32 `protobuf:"varint,14,opt,name=unreadFileCount,proto3" json:"unreadFileCount,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *TailscaleEndpointStatus) Reset() { @@ -2334,6 +2338,34 @@ func (x *TailscaleEndpointStatus) GetKeyAuth() bool { return false } +func (x *TailscaleEndpointStatus) GetCanShareFiles() bool { + if x != nil { + return x.CanShareFiles + } + return false +} + +func (x *TailscaleEndpointStatus) GetWaitingFileCount() int32 { + if x != nil { + return x.WaitingFileCount + } + return 0 +} + +func (x *TailscaleEndpointStatus) GetReceivingFileCount() int32 { + if x != nil { + return x.ReceivingFileCount + } + return 0 +} + +func (x *TailscaleEndpointStatus) GetUnreadFileCount() int32 { + if x != nil { + return x.UnreadFileCount + } + return 0 +} + type TailscaleUserGroup struct { state protoimpl.MessageState `protogen:"open.v1"` UserID int64 `protobuf:"varint,1,opt,name=userID,proto3" json:"userID,omitempty"` @@ -2411,25 +2443,26 @@ func (x *TailscaleUserGroup) GetPeers() []*TailscalePeer { } type TailscalePeer struct { - state protoimpl.MessageState `protogen:"open.v1"` - HostName string `protobuf:"bytes,1,opt,name=hostName,proto3" json:"hostName,omitempty"` - DnsName string `protobuf:"bytes,2,opt,name=dnsName,proto3" json:"dnsName,omitempty"` - Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` - TailscaleIPs []string `protobuf:"bytes,4,rep,name=tailscaleIPs,proto3" json:"tailscaleIPs,omitempty"` - Online bool `protobuf:"varint,5,opt,name=online,proto3" json:"online,omitempty"` - ExitNode bool `protobuf:"varint,6,opt,name=exitNode,proto3" json:"exitNode,omitempty"` - ExitNodeOption bool `protobuf:"varint,7,opt,name=exitNodeOption,proto3" json:"exitNodeOption,omitempty"` - Active bool `protobuf:"varint,8,opt,name=active,proto3" json:"active,omitempty"` - RxBytes int64 `protobuf:"varint,9,opt,name=rxBytes,proto3" json:"rxBytes,omitempty"` - TxBytes int64 `protobuf:"varint,10,opt,name=txBytes,proto3" json:"txBytes,omitempty"` - KeyExpiry int64 `protobuf:"varint,11,opt,name=keyExpiry,proto3" json:"keyExpiry,omitempty"` - StableID string `protobuf:"bytes,12,opt,name=stableID,proto3" json:"stableID,omitempty"` - Expired bool `protobuf:"varint,13,opt,name=expired,proto3" json:"expired,omitempty"` - SshHostKeys []string `protobuf:"bytes,14,rep,name=sshHostKeys,proto3" json:"sshHostKeys,omitempty"` - ShareeNode bool `protobuf:"varint,15,opt,name=shareeNode,proto3" json:"shareeNode,omitempty"` - LastSeen int64 `protobuf:"varint,16,opt,name=lastSeen,proto3" json:"lastSeen,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + HostName string `protobuf:"bytes,1,opt,name=hostName,proto3" json:"hostName,omitempty"` + DnsName string `protobuf:"bytes,2,opt,name=dnsName,proto3" json:"dnsName,omitempty"` + Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` + TailscaleIPs []string `protobuf:"bytes,4,rep,name=tailscaleIPs,proto3" json:"tailscaleIPs,omitempty"` + Online bool `protobuf:"varint,5,opt,name=online,proto3" json:"online,omitempty"` + ExitNode bool `protobuf:"varint,6,opt,name=exitNode,proto3" json:"exitNode,omitempty"` + ExitNodeOption bool `protobuf:"varint,7,opt,name=exitNodeOption,proto3" json:"exitNodeOption,omitempty"` + Active bool `protobuf:"varint,8,opt,name=active,proto3" json:"active,omitempty"` + RxBytes int64 `protobuf:"varint,9,opt,name=rxBytes,proto3" json:"rxBytes,omitempty"` + TxBytes int64 `protobuf:"varint,10,opt,name=txBytes,proto3" json:"txBytes,omitempty"` + KeyExpiry int64 `protobuf:"varint,11,opt,name=keyExpiry,proto3" json:"keyExpiry,omitempty"` + StableID string `protobuf:"bytes,12,opt,name=stableID,proto3" json:"stableID,omitempty"` + Expired bool `protobuf:"varint,13,opt,name=expired,proto3" json:"expired,omitempty"` + SshHostKeys []string `protobuf:"bytes,14,rep,name=sshHostKeys,proto3" json:"sshHostKeys,omitempty"` + ShareeNode bool `protobuf:"varint,15,opt,name=shareeNode,proto3" json:"shareeNode,omitempty"` + LastSeen int64 `protobuf:"varint,16,opt,name=lastSeen,proto3" json:"lastSeen,omitempty"` + CanReceiveFiles bool `protobuf:"varint,17,opt,name=canReceiveFiles,proto3" json:"canReceiveFiles,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *TailscalePeer) Reset() { @@ -2574,6 +2607,13 @@ func (x *TailscalePeer) GetLastSeen() int64 { return 0 } +func (x *TailscalePeer) GetCanReceiveFiles() bool { + if x != nil { + return x.CanReceiveFiles + } + return false +} + type TailscalePingRequest struct { state protoimpl.MessageState `protogen:"open.v1"` EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` @@ -2634,6 +2674,7 @@ type TailscalePingResponse struct { DerpRegionID int32 `protobuf:"varint,4,opt,name=derpRegionID,proto3" json:"derpRegionID,omitempty"` DerpRegionCode string `protobuf:"bytes,5,opt,name=derpRegionCode,proto3" json:"derpRegionCode,omitempty"` Error string `protobuf:"bytes,6,opt,name=error,proto3" json:"error,omitempty"` + PeerRelay string `protobuf:"bytes,7,opt,name=peerRelay,proto3" json:"peerRelay,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2710,6 +2751,13 @@ func (x *TailscalePingResponse) GetError() string { return "" } +func (x *TailscalePingResponse) GetPeerRelay() string { + if x != nil { + return x.PeerRelay + } + return "" +} + type SetTailscaleExitNodeRequest struct { state protoimpl.MessageState `protogen:"open.v1"` EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` @@ -3490,6 +3538,946 @@ func (x *TailscaleSSHError) GetMessage() string { return "" } +type SubscribeTaildropInboxRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscribeTaildropInboxRequest) Reset() { + *x = SubscribeTaildropInboxRequest{} + mi := &file_daemon_started_service_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscribeTaildropInboxRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscribeTaildropInboxRequest) ProtoMessage() {} + +func (x *SubscribeTaildropInboxRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[46] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscribeTaildropInboxRequest.ProtoReflect.Descriptor instead. +func (*SubscribeTaildropInboxRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{46} +} + +func (x *SubscribeTaildropInboxRequest) GetEndpointTag() string { + if x != nil { + return x.EndpointTag + } + return "" +} + +type MarkTaildropInboxReadRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MarkTaildropInboxReadRequest) Reset() { + *x = MarkTaildropInboxReadRequest{} + mi := &file_daemon_started_service_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MarkTaildropInboxReadRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MarkTaildropInboxReadRequest) ProtoMessage() {} + +func (x *MarkTaildropInboxReadRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[47] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MarkTaildropInboxReadRequest.ProtoReflect.Descriptor instead. +func (*MarkTaildropInboxReadRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{47} +} + +func (x *MarkTaildropInboxReadRequest) GetEndpointTag() string { + if x != nil { + return x.EndpointTag + } + return "" +} + +type TaildropInbox struct { + state protoimpl.MessageState `protogen:"open.v1"` + EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` + Files []*TaildropFile `protobuf:"bytes,2,rep,name=files,proto3" json:"files,omitempty"` + Receiving []*TaildropReceivingFile `protobuf:"bytes,3,rep,name=receiving,proto3" json:"receiving,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaildropInbox) Reset() { + *x = TaildropInbox{} + mi := &file_daemon_started_service_proto_msgTypes[48] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaildropInbox) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaildropInbox) ProtoMessage() {} + +func (x *TaildropInbox) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[48] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaildropInbox.ProtoReflect.Descriptor instead. +func (*TaildropInbox) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{48} +} + +func (x *TaildropInbox) GetEndpointTag() string { + if x != nil { + return x.EndpointTag + } + return "" +} + +func (x *TaildropInbox) GetFiles() []*TaildropFile { + if x != nil { + return x.Files + } + return nil +} + +func (x *TaildropInbox) GetReceiving() []*TaildropReceivingFile { + if x != nil { + return x.Receiving + } + return nil +} + +type TaildropFile struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Size int64 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` + SenderName string `protobuf:"bytes,3,opt,name=senderName,proto3" json:"senderName,omitempty"` + ModifiedAt int64 `protobuf:"varint,4,opt,name=modifiedAt,proto3" json:"modifiedAt,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaildropFile) Reset() { + *x = TaildropFile{} + mi := &file_daemon_started_service_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaildropFile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaildropFile) ProtoMessage() {} + +func (x *TaildropFile) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[49] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaildropFile.ProtoReflect.Descriptor instead. +func (*TaildropFile) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{49} +} + +func (x *TaildropFile) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TaildropFile) GetSize() int64 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *TaildropFile) GetSenderName() string { + if x != nil { + return x.SenderName + } + return "" +} + +func (x *TaildropFile) GetModifiedAt() int64 { + if x != nil { + return x.ModifiedAt + } + return 0 +} + +type TaildropReceivingFile struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Size int64 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` + ReceivedBytes int64 `protobuf:"varint,3,opt,name=receivedBytes,proto3" json:"receivedBytes,omitempty"` + SenderID string `protobuf:"bytes,4,opt,name=senderID,proto3" json:"senderID,omitempty"` + SenderName string `protobuf:"bytes,5,opt,name=senderName,proto3" json:"senderName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaildropReceivingFile) Reset() { + *x = TaildropReceivingFile{} + mi := &file_daemon_started_service_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaildropReceivingFile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaildropReceivingFile) ProtoMessage() {} + +func (x *TaildropReceivingFile) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[50] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaildropReceivingFile.ProtoReflect.Descriptor instead. +func (*TaildropReceivingFile) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{50} +} + +func (x *TaildropReceivingFile) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TaildropReceivingFile) GetSize() int64 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *TaildropReceivingFile) GetReceivedBytes() int64 { + if x != nil { + return x.ReceivedBytes + } + return 0 +} + +func (x *TaildropReceivingFile) GetSenderID() string { + if x != nil { + return x.SenderID + } + return "" +} + +func (x *TaildropReceivingFile) GetSenderName() string { + if x != nil { + return x.SenderName + } + return "" +} + +type TaildropSendClientMessage struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Message: + // + // *TaildropSendClientMessage_Start + // *TaildropSendClientMessage_Chunk + // *TaildropSendClientMessage_FileDone + Message isTaildropSendClientMessage_Message `protobuf_oneof:"message"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaildropSendClientMessage) Reset() { + *x = TaildropSendClientMessage{} + mi := &file_daemon_started_service_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaildropSendClientMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaildropSendClientMessage) ProtoMessage() {} + +func (x *TaildropSendClientMessage) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[51] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaildropSendClientMessage.ProtoReflect.Descriptor instead. +func (*TaildropSendClientMessage) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{51} +} + +func (x *TaildropSendClientMessage) GetMessage() isTaildropSendClientMessage_Message { + if x != nil { + return x.Message + } + return nil +} + +func (x *TaildropSendClientMessage) GetStart() *TaildropSendStart { + if x != nil { + if x, ok := x.Message.(*TaildropSendClientMessage_Start); ok { + return x.Start + } + } + return nil +} + +func (x *TaildropSendClientMessage) GetChunk() *TaildropFileChunk { + if x != nil { + if x, ok := x.Message.(*TaildropSendClientMessage_Chunk); ok { + return x.Chunk + } + } + return nil +} + +func (x *TaildropSendClientMessage) GetFileDone() *TaildropFileDone { + if x != nil { + if x, ok := x.Message.(*TaildropSendClientMessage_FileDone); ok { + return x.FileDone + } + } + return nil +} + +type isTaildropSendClientMessage_Message interface { + isTaildropSendClientMessage_Message() +} + +type TaildropSendClientMessage_Start struct { + Start *TaildropSendStart `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +} + +type TaildropSendClientMessage_Chunk struct { + Chunk *TaildropFileChunk `protobuf:"bytes,2,opt,name=chunk,proto3,oneof"` +} + +type TaildropSendClientMessage_FileDone struct { + FileDone *TaildropFileDone `protobuf:"bytes,3,opt,name=fileDone,proto3,oneof"` +} + +func (*TaildropSendClientMessage_Start) isTaildropSendClientMessage_Message() {} + +func (*TaildropSendClientMessage_Chunk) isTaildropSendClientMessage_Message() {} + +func (*TaildropSendClientMessage_FileDone) isTaildropSendClientMessage_Message() {} + +type TaildropSendStart struct { + state protoimpl.MessageState `protogen:"open.v1"` + EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` + PeerStableID string `protobuf:"bytes,2,opt,name=peerStableID,proto3" json:"peerStableID,omitempty"` + Files []*TaildropOutgoingFile `protobuf:"bytes,3,rep,name=files,proto3" json:"files,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaildropSendStart) Reset() { + *x = TaildropSendStart{} + mi := &file_daemon_started_service_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaildropSendStart) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaildropSendStart) ProtoMessage() {} + +func (x *TaildropSendStart) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[52] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaildropSendStart.ProtoReflect.Descriptor instead. +func (*TaildropSendStart) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{52} +} + +func (x *TaildropSendStart) GetEndpointTag() string { + if x != nil { + return x.EndpointTag + } + return "" +} + +func (x *TaildropSendStart) GetPeerStableID() string { + if x != nil { + return x.PeerStableID + } + return "" +} + +func (x *TaildropSendStart) GetFiles() []*TaildropOutgoingFile { + if x != nil { + return x.Files + } + return nil +} + +type TaildropOutgoingFile struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Size int64 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaildropOutgoingFile) Reset() { + *x = TaildropOutgoingFile{} + mi := &file_daemon_started_service_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaildropOutgoingFile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaildropOutgoingFile) ProtoMessage() {} + +func (x *TaildropOutgoingFile) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[53] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaildropOutgoingFile.ProtoReflect.Descriptor instead. +func (*TaildropOutgoingFile) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{53} +} + +func (x *TaildropOutgoingFile) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TaildropOutgoingFile) GetSize() int64 { + if x != nil { + return x.Size + } + return 0 +} + +type TaildropFileChunk struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaildropFileChunk) Reset() { + *x = TaildropFileChunk{} + mi := &file_daemon_started_service_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaildropFileChunk) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaildropFileChunk) ProtoMessage() {} + +func (x *TaildropFileChunk) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[54] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaildropFileChunk.ProtoReflect.Descriptor instead. +func (*TaildropFileChunk) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{54} +} + +func (x *TaildropFileChunk) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type TaildropFileDone struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaildropFileDone) Reset() { + *x = TaildropFileDone{} + mi := &file_daemon_started_service_proto_msgTypes[55] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaildropFileDone) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaildropFileDone) ProtoMessage() {} + +func (x *TaildropFileDone) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[55] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaildropFileDone.ProtoReflect.Descriptor instead. +func (*TaildropFileDone) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{55} +} + +type TaildropSendServerMessage struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Message: + // + // *TaildropSendServerMessage_Progress + // *TaildropSendServerMessage_ReceivedBytes + Message isTaildropSendServerMessage_Message `protobuf_oneof:"message"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaildropSendServerMessage) Reset() { + *x = TaildropSendServerMessage{} + mi := &file_daemon_started_service_proto_msgTypes[56] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaildropSendServerMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaildropSendServerMessage) ProtoMessage() {} + +func (x *TaildropSendServerMessage) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[56] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaildropSendServerMessage.ProtoReflect.Descriptor instead. +func (*TaildropSendServerMessage) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{56} +} + +func (x *TaildropSendServerMessage) GetMessage() isTaildropSendServerMessage_Message { + if x != nil { + return x.Message + } + return nil +} + +func (x *TaildropSendServerMessage) GetProgress() *TaildropSendProgress { + if x != nil { + if x, ok := x.Message.(*TaildropSendServerMessage_Progress); ok { + return x.Progress + } + } + return nil +} + +func (x *TaildropSendServerMessage) GetReceivedBytes() int64 { + if x != nil { + if x, ok := x.Message.(*TaildropSendServerMessage_ReceivedBytes); ok { + return x.ReceivedBytes + } + } + return 0 +} + +type isTaildropSendServerMessage_Message interface { + isTaildropSendServerMessage_Message() +} + +type TaildropSendServerMessage_Progress struct { + Progress *TaildropSendProgress `protobuf:"bytes,1,opt,name=progress,proto3,oneof"` +} + +type TaildropSendServerMessage_ReceivedBytes struct { + ReceivedBytes int64 `protobuf:"varint,2,opt,name=receivedBytes,proto3,oneof"` +} + +func (*TaildropSendServerMessage_Progress) isTaildropSendServerMessage_Message() {} + +func (*TaildropSendServerMessage_ReceivedBytes) isTaildropSendServerMessage_Message() {} + +type TaildropSendProgress struct { + state protoimpl.MessageState `protogen:"open.v1"` + FileIndex int32 `protobuf:"varint,1,opt,name=fileIndex,proto3" json:"fileIndex,omitempty"` + SentBytes int64 `protobuf:"varint,2,opt,name=sentBytes,proto3" json:"sentBytes,omitempty"` + FileCompleted bool `protobuf:"varint,3,opt,name=fileCompleted,proto3" json:"fileCompleted,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TaildropSendProgress) Reset() { + *x = TaildropSendProgress{} + mi := &file_daemon_started_service_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TaildropSendProgress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaildropSendProgress) ProtoMessage() {} + +func (x *TaildropSendProgress) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[57] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaildropSendProgress.ProtoReflect.Descriptor instead. +func (*TaildropSendProgress) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{57} +} + +func (x *TaildropSendProgress) GetFileIndex() int32 { + if x != nil { + return x.FileIndex + } + return 0 +} + +func (x *TaildropSendProgress) GetSentBytes() int64 { + if x != nil { + return x.SentBytes + } + return 0 +} + +func (x *TaildropSendProgress) GetFileCompleted() bool { + if x != nil { + return x.FileCompleted + } + return false +} + +type DownloadTaildropFileRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DownloadTaildropFileRequest) Reset() { + *x = DownloadTaildropFileRequest{} + mi := &file_daemon_started_service_proto_msgTypes[58] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DownloadTaildropFileRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DownloadTaildropFileRequest) ProtoMessage() {} + +func (x *DownloadTaildropFileRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[58] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DownloadTaildropFileRequest.ProtoReflect.Descriptor instead. +func (*DownloadTaildropFileRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{58} +} + +func (x *DownloadTaildropFileRequest) GetEndpointTag() string { + if x != nil { + return x.EndpointTag + } + return "" +} + +func (x *DownloadTaildropFileRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type DownloadTaildropFileChunk struct { + state protoimpl.MessageState `protogen:"open.v1"` + Size int64 `protobuf:"varint,1,opt,name=size,proto3" json:"size,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DownloadTaildropFileChunk) Reset() { + *x = DownloadTaildropFileChunk{} + mi := &file_daemon_started_service_proto_msgTypes[59] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DownloadTaildropFileChunk) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DownloadTaildropFileChunk) ProtoMessage() {} + +func (x *DownloadTaildropFileChunk) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[59] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DownloadTaildropFileChunk.ProtoReflect.Descriptor instead. +func (*DownloadTaildropFileChunk) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{59} +} + +func (x *DownloadTaildropFileChunk) GetSize() int64 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *DownloadTaildropFileChunk) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type DeleteTaildropFileRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteTaildropFileRequest) Reset() { + *x = DeleteTaildropFileRequest{} + mi := &file_daemon_started_service_proto_msgTypes[60] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteTaildropFileRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTaildropFileRequest) ProtoMessage() {} + +func (x *DeleteTaildropFileRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[60] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTaildropFileRequest.ProtoReflect.Descriptor instead. +func (*DeleteTaildropFileRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{60} +} + +func (x *DeleteTaildropFileRequest) GetEndpointTag() string { + if x != nil { + return x.EndpointTag + } + return "" +} + +func (x *DeleteTaildropFileRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type CancelTaildropReceivingRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + EndpointTag string `protobuf:"bytes,1,opt,name=endpointTag,proto3" json:"endpointTag,omitempty"` + SenderID string `protobuf:"bytes,2,opt,name=senderID,proto3" json:"senderID,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CancelTaildropReceivingRequest) Reset() { + *x = CancelTaildropReceivingRequest{} + mi := &file_daemon_started_service_proto_msgTypes[61] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CancelTaildropReceivingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CancelTaildropReceivingRequest) ProtoMessage() {} + +func (x *CancelTaildropReceivingRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[61] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CancelTaildropReceivingRequest.ProtoReflect.Descriptor instead. +func (*CancelTaildropReceivingRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{61} +} + +func (x *CancelTaildropReceivingRequest) GetEndpointTag() string { + if x != nil { + return x.EndpointTag + } + return "" +} + +func (x *CancelTaildropReceivingRequest) GetSenderID() string { + if x != nil { + return x.SenderID + } + return "" +} + +func (x *CancelTaildropReceivingRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + type USBProviderMessage struct { state protoimpl.MessageState `protogen:"open.v1"` // Types that are valid to be assigned to Message: @@ -3504,7 +4492,7 @@ type USBProviderMessage struct { func (x *USBProviderMessage) Reset() { *x = USBProviderMessage{} - mi := &file_daemon_started_service_proto_msgTypes[46] + mi := &file_daemon_started_service_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3516,7 +4504,7 @@ func (x *USBProviderMessage) String() string { func (*USBProviderMessage) ProtoMessage() {} func (x *USBProviderMessage) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[46] + mi := &file_daemon_started_service_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3529,7 +4517,7 @@ func (x *USBProviderMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use USBProviderMessage.ProtoReflect.Descriptor instead. func (*USBProviderMessage) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{46} + return file_daemon_started_service_proto_rawDescGZIP(), []int{62} } func (x *USBProviderMessage) GetMessage() isUSBProviderMessage_Message { @@ -3603,7 +4591,7 @@ type USBServerMessage struct { func (x *USBServerMessage) Reset() { *x = USBServerMessage{} - mi := &file_daemon_started_service_proto_msgTypes[47] + mi := &file_daemon_started_service_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3615,7 +4603,7 @@ func (x *USBServerMessage) String() string { func (*USBServerMessage) ProtoMessage() {} func (x *USBServerMessage) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[47] + mi := &file_daemon_started_service_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3628,7 +4616,7 @@ func (x *USBServerMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use USBServerMessage.ProtoReflect.Descriptor instead. func (*USBServerMessage) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{47} + return file_daemon_started_service_proto_rawDescGZIP(), []int{63} } func (x *USBServerMessage) GetMessage() isUSBServerMessage_Message { @@ -3725,7 +4713,7 @@ type USBDeviceDescriptor struct { func (x *USBDeviceDescriptor) Reset() { *x = USBDeviceDescriptor{} - mi := &file_daemon_started_service_proto_msgTypes[48] + mi := &file_daemon_started_service_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3737,7 +4725,7 @@ func (x *USBDeviceDescriptor) String() string { func (*USBDeviceDescriptor) ProtoMessage() {} func (x *USBDeviceDescriptor) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[48] + mi := &file_daemon_started_service_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3750,7 +4738,7 @@ func (x *USBDeviceDescriptor) ProtoReflect() protoreflect.Message { // Deprecated: Use USBDeviceDescriptor.ProtoReflect.Descriptor instead. func (*USBDeviceDescriptor) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{48} + return file_daemon_started_service_proto_rawDescGZIP(), []int{64} } func (x *USBDeviceDescriptor) GetDeviceId() string { @@ -3868,7 +4856,7 @@ type USBDeviceAttach struct { func (x *USBDeviceAttach) Reset() { *x = USBDeviceAttach{} - mi := &file_daemon_started_service_proto_msgTypes[49] + mi := &file_daemon_started_service_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3880,7 +4868,7 @@ func (x *USBDeviceAttach) String() string { func (*USBDeviceAttach) ProtoMessage() {} func (x *USBDeviceAttach) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[49] + mi := &file_daemon_started_service_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3893,7 +4881,7 @@ func (x *USBDeviceAttach) ProtoReflect() protoreflect.Message { // Deprecated: Use USBDeviceAttach.ProtoReflect.Descriptor instead. func (*USBDeviceAttach) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{49} + return file_daemon_started_service_proto_rawDescGZIP(), []int{65} } func (x *USBDeviceAttach) GetServerTag() string { @@ -3921,7 +4909,7 @@ type USBInterface struct { func (x *USBInterface) Reset() { *x = USBInterface{} - mi := &file_daemon_started_service_proto_msgTypes[50] + mi := &file_daemon_started_service_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3933,7 +4921,7 @@ func (x *USBInterface) String() string { func (*USBInterface) ProtoMessage() {} func (x *USBInterface) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[50] + mi := &file_daemon_started_service_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3946,7 +4934,7 @@ func (x *USBInterface) ProtoReflect() protoreflect.Message { // Deprecated: Use USBInterface.ProtoReflect.Descriptor instead. func (*USBInterface) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{50} + return file_daemon_started_service_proto_rawDescGZIP(), []int{66} } func (x *USBInterface) GetInterfaceClass() uint32 { @@ -3979,7 +4967,7 @@ type USBDeviceDetach struct { func (x *USBDeviceDetach) Reset() { *x = USBDeviceDetach{} - mi := &file_daemon_started_service_proto_msgTypes[51] + mi := &file_daemon_started_service_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3991,7 +4979,7 @@ func (x *USBDeviceDetach) String() string { func (*USBDeviceDetach) ProtoMessage() {} func (x *USBDeviceDetach) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[51] + mi := &file_daemon_started_service_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4004,7 +4992,7 @@ func (x *USBDeviceDetach) ProtoReflect() protoreflect.Message { // Deprecated: Use USBDeviceDetach.ProtoReflect.Descriptor instead. func (*USBDeviceDetach) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{51} + return file_daemon_started_service_proto_rawDescGZIP(), []int{67} } func (x *USBDeviceDetach) GetDeviceId() string { @@ -4024,7 +5012,7 @@ type USBDeviceReady struct { func (x *USBDeviceReady) Reset() { *x = USBDeviceReady{} - mi := &file_daemon_started_service_proto_msgTypes[52] + mi := &file_daemon_started_service_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4036,7 +5024,7 @@ func (x *USBDeviceReady) String() string { func (*USBDeviceReady) ProtoMessage() {} func (x *USBDeviceReady) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[52] + mi := &file_daemon_started_service_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4049,7 +5037,7 @@ func (x *USBDeviceReady) ProtoReflect() protoreflect.Message { // Deprecated: Use USBDeviceReady.ProtoReflect.Descriptor instead. func (*USBDeviceReady) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{52} + return file_daemon_started_service_proto_rawDescGZIP(), []int{68} } func (x *USBDeviceReady) GetDeviceId() string { @@ -4086,7 +5074,7 @@ type USBURBRequest struct { func (x *USBURBRequest) Reset() { *x = USBURBRequest{} - mi := &file_daemon_started_service_proto_msgTypes[53] + mi := &file_daemon_started_service_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4098,7 +5086,7 @@ func (x *USBURBRequest) String() string { func (*USBURBRequest) ProtoMessage() {} func (x *USBURBRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[53] + mi := &file_daemon_started_service_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4111,7 +5099,7 @@ func (x *USBURBRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use USBURBRequest.ProtoReflect.Descriptor instead. func (*USBURBRequest) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{53} + return file_daemon_started_service_proto_rawDescGZIP(), []int{69} } func (x *USBURBRequest) GetDeviceId() string { @@ -4212,7 +5200,7 @@ type USBURBResponse struct { func (x *USBURBResponse) Reset() { *x = USBURBResponse{} - mi := &file_daemon_started_service_proto_msgTypes[54] + mi := &file_daemon_started_service_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4224,7 +5212,7 @@ func (x *USBURBResponse) String() string { func (*USBURBResponse) ProtoMessage() {} func (x *USBURBResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[54] + mi := &file_daemon_started_service_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4237,7 +5225,7 @@ func (x *USBURBResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use USBURBResponse.ProtoReflect.Descriptor instead. func (*USBURBResponse) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{54} + return file_daemon_started_service_proto_rawDescGZIP(), []int{70} } func (x *USBURBResponse) GetDeviceId() string { @@ -4294,7 +5282,7 @@ type USBIsoPacket struct { func (x *USBIsoPacket) Reset() { *x = USBIsoPacket{} - mi := &file_daemon_started_service_proto_msgTypes[55] + mi := &file_daemon_started_service_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4306,7 +5294,7 @@ func (x *USBIsoPacket) String() string { func (*USBIsoPacket) ProtoMessage() {} func (x *USBIsoPacket) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[55] + mi := &file_daemon_started_service_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4319,7 +5307,7 @@ func (x *USBIsoPacket) ProtoReflect() protoreflect.Message { // Deprecated: Use USBIsoPacket.ProtoReflect.Descriptor instead. func (*USBIsoPacket) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{55} + return file_daemon_started_service_proto_rawDescGZIP(), []int{71} } func (x *USBIsoPacket) GetOffset() int32 { @@ -4360,7 +5348,7 @@ type USBEndpointAbort struct { func (x *USBEndpointAbort) Reset() { *x = USBEndpointAbort{} - mi := &file_daemon_started_service_proto_msgTypes[56] + mi := &file_daemon_started_service_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4372,7 +5360,7 @@ func (x *USBEndpointAbort) String() string { func (*USBEndpointAbort) ProtoMessage() {} func (x *USBEndpointAbort) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[56] + mi := &file_daemon_started_service_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4385,7 +5373,7 @@ func (x *USBEndpointAbort) ProtoReflect() protoreflect.Message { // Deprecated: Use USBEndpointAbort.ProtoReflect.Descriptor instead. func (*USBEndpointAbort) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{56} + return file_daemon_started_service_proto_rawDescGZIP(), []int{72} } func (x *USBEndpointAbort) GetDeviceId() string { @@ -4412,7 +5400,7 @@ type USBError struct { func (x *USBError) Reset() { *x = USBError{} - mi := &file_daemon_started_service_proto_msgTypes[57] + mi := &file_daemon_started_service_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4424,7 +5412,7 @@ func (x *USBError) String() string { func (*USBError) ProtoMessage() {} func (x *USBError) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[57] + mi := &file_daemon_started_service_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4437,7 +5425,7 @@ func (x *USBError) ProtoReflect() protoreflect.Message { // Deprecated: Use USBError.ProtoReflect.Descriptor instead. func (*USBError) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{57} + return file_daemon_started_service_proto_rawDescGZIP(), []int{73} } func (x *USBError) GetDeviceId() string { @@ -4463,7 +5451,7 @@ type USBIPServerStatusUpdate struct { func (x *USBIPServerStatusUpdate) Reset() { *x = USBIPServerStatusUpdate{} - mi := &file_daemon_started_service_proto_msgTypes[58] + mi := &file_daemon_started_service_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4475,7 +5463,7 @@ func (x *USBIPServerStatusUpdate) String() string { func (*USBIPServerStatusUpdate) ProtoMessage() {} func (x *USBIPServerStatusUpdate) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[58] + mi := &file_daemon_started_service_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4488,7 +5476,7 @@ func (x *USBIPServerStatusUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use USBIPServerStatusUpdate.ProtoReflect.Descriptor instead. func (*USBIPServerStatusUpdate) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{58} + return file_daemon_started_service_proto_rawDescGZIP(), []int{74} } func (x *USBIPServerStatusUpdate) GetServers() []*USBIPServerStatus { @@ -4508,7 +5496,7 @@ type USBIPServerStatus struct { func (x *USBIPServerStatus) Reset() { *x = USBIPServerStatus{} - mi := &file_daemon_started_service_proto_msgTypes[59] + mi := &file_daemon_started_service_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4520,7 +5508,7 @@ func (x *USBIPServerStatus) String() string { func (*USBIPServerStatus) ProtoMessage() {} func (x *USBIPServerStatus) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[59] + mi := &file_daemon_started_service_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4533,7 +5521,7 @@ func (x *USBIPServerStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use USBIPServerStatus.ProtoReflect.Descriptor instead. func (*USBIPServerStatus) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{59} + return file_daemon_started_service_proto_rawDescGZIP(), []int{75} } func (x *USBIPServerStatus) GetServerTag() string { @@ -4563,7 +5551,7 @@ type USBSharedDevice struct { func (x *USBSharedDevice) Reset() { *x = USBSharedDevice{} - mi := &file_daemon_started_service_proto_msgTypes[60] + mi := &file_daemon_started_service_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4575,7 +5563,7 @@ func (x *USBSharedDevice) String() string { func (*USBSharedDevice) ProtoMessage() {} func (x *USBSharedDevice) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[60] + mi := &file_daemon_started_service_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4588,7 +5576,7 @@ func (x *USBSharedDevice) ProtoReflect() protoreflect.Message { // Deprecated: Use USBSharedDevice.ProtoReflect.Descriptor instead. func (*USBSharedDevice) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{60} + return file_daemon_started_service_proto_rawDescGZIP(), []int{76} } func (x *USBSharedDevice) GetDescriptor_() *USBDeviceDescriptor { @@ -4635,7 +5623,7 @@ type OpenConnectStatusUpdate struct { func (x *OpenConnectStatusUpdate) Reset() { *x = OpenConnectStatusUpdate{} - mi := &file_daemon_started_service_proto_msgTypes[61] + mi := &file_daemon_started_service_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4647,7 +5635,7 @@ func (x *OpenConnectStatusUpdate) String() string { func (*OpenConnectStatusUpdate) ProtoMessage() {} func (x *OpenConnectStatusUpdate) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[61] + mi := &file_daemon_started_service_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4660,7 +5648,7 @@ func (x *OpenConnectStatusUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenConnectStatusUpdate.ProtoReflect.Descriptor instead. func (*OpenConnectStatusUpdate) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{61} + return file_daemon_started_service_proto_rawDescGZIP(), []int{77} } func (x *OpenConnectStatusUpdate) GetEndpoints() []*OpenConnectEndpointStatus { @@ -4684,7 +5672,7 @@ type OpenConnectEndpointStatus struct { func (x *OpenConnectEndpointStatus) Reset() { *x = OpenConnectEndpointStatus{} - mi := &file_daemon_started_service_proto_msgTypes[62] + mi := &file_daemon_started_service_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4696,7 +5684,7 @@ func (x *OpenConnectEndpointStatus) String() string { func (*OpenConnectEndpointStatus) ProtoMessage() {} func (x *OpenConnectEndpointStatus) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[62] + mi := &file_daemon_started_service_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4709,7 +5697,7 @@ func (x *OpenConnectEndpointStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenConnectEndpointStatus.ProtoReflect.Descriptor instead. func (*OpenConnectEndpointStatus) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{62} + return file_daemon_started_service_proto_rawDescGZIP(), []int{78} } func (x *OpenConnectEndpointStatus) GetEndpointTag() string { @@ -4770,7 +5758,7 @@ type OpenConnectTunnelInfo struct { func (x *OpenConnectTunnelInfo) Reset() { *x = OpenConnectTunnelInfo{} - mi := &file_daemon_started_service_proto_msgTypes[63] + mi := &file_daemon_started_service_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4782,7 +5770,7 @@ func (x *OpenConnectTunnelInfo) String() string { func (*OpenConnectTunnelInfo) ProtoMessage() {} func (x *OpenConnectTunnelInfo) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[63] + mi := &file_daemon_started_service_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4795,7 +5783,7 @@ func (x *OpenConnectTunnelInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenConnectTunnelInfo.ProtoReflect.Descriptor instead. func (*OpenConnectTunnelInfo) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{63} + return file_daemon_started_service_proto_rawDescGZIP(), []int{79} } func (x *OpenConnectTunnelInfo) GetServer() string { @@ -4871,7 +5859,7 @@ type OpenConnectAuthChallenge struct { func (x *OpenConnectAuthChallenge) Reset() { *x = OpenConnectAuthChallenge{} - mi := &file_daemon_started_service_proto_msgTypes[64] + mi := &file_daemon_started_service_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4883,7 +5871,7 @@ func (x *OpenConnectAuthChallenge) String() string { func (*OpenConnectAuthChallenge) ProtoMessage() {} func (x *OpenConnectAuthChallenge) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[64] + mi := &file_daemon_started_service_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4896,7 +5884,7 @@ func (x *OpenConnectAuthChallenge) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenConnectAuthChallenge.ProtoReflect.Descriptor instead. func (*OpenConnectAuthChallenge) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{64} + return file_daemon_started_service_proto_rawDescGZIP(), []int{80} } func (x *OpenConnectAuthChallenge) GetId() string { @@ -4977,7 +5965,7 @@ type OpenConnectAuthForm struct { func (x *OpenConnectAuthForm) Reset() { *x = OpenConnectAuthForm{} - mi := &file_daemon_started_service_proto_msgTypes[65] + mi := &file_daemon_started_service_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4989,7 +5977,7 @@ func (x *OpenConnectAuthForm) String() string { func (*OpenConnectAuthForm) ProtoMessage() {} func (x *OpenConnectAuthForm) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[65] + mi := &file_daemon_started_service_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5002,7 +5990,7 @@ func (x *OpenConnectAuthForm) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenConnectAuthForm.ProtoReflect.Descriptor instead. func (*OpenConnectAuthForm) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{65} + return file_daemon_started_service_proto_rawDescGZIP(), []int{81} } func (x *OpenConnectAuthForm) GetFields() []*OpenConnectAuthFormField { @@ -5026,7 +6014,7 @@ type OpenConnectAuthFormField struct { func (x *OpenConnectAuthFormField) Reset() { *x = OpenConnectAuthFormField{} - mi := &file_daemon_started_service_proto_msgTypes[66] + mi := &file_daemon_started_service_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5038,7 +6026,7 @@ func (x *OpenConnectAuthFormField) String() string { func (*OpenConnectAuthFormField) ProtoMessage() {} func (x *OpenConnectAuthFormField) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[66] + mi := &file_daemon_started_service_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5051,7 +6039,7 @@ func (x *OpenConnectAuthFormField) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenConnectAuthFormField.ProtoReflect.Descriptor instead. func (*OpenConnectAuthFormField) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{66} + return file_daemon_started_service_proto_rawDescGZIP(), []int{82} } func (x *OpenConnectAuthFormField) GetSubmissionKey() string { @@ -5106,7 +6094,7 @@ type OpenConnectAuthFormChoice struct { func (x *OpenConnectAuthFormChoice) Reset() { *x = OpenConnectAuthFormChoice{} - mi := &file_daemon_started_service_proto_msgTypes[67] + mi := &file_daemon_started_service_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5118,7 +6106,7 @@ func (x *OpenConnectAuthFormChoice) String() string { func (*OpenConnectAuthFormChoice) ProtoMessage() {} func (x *OpenConnectAuthFormChoice) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[67] + mi := &file_daemon_started_service_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5131,7 +6119,7 @@ func (x *OpenConnectAuthFormChoice) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenConnectAuthFormChoice.ProtoReflect.Descriptor instead. func (*OpenConnectAuthFormChoice) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{67} + return file_daemon_started_service_proto_rawDescGZIP(), []int{83} } func (x *OpenConnectAuthFormChoice) GetValue() string { @@ -5163,7 +6151,7 @@ type OpenConnectBrowserRequest struct { func (x *OpenConnectBrowserRequest) Reset() { *x = OpenConnectBrowserRequest{} - mi := &file_daemon_started_service_proto_msgTypes[68] + mi := &file_daemon_started_service_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5175,7 +6163,7 @@ func (x *OpenConnectBrowserRequest) String() string { func (*OpenConnectBrowserRequest) ProtoMessage() {} func (x *OpenConnectBrowserRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[68] + mi := &file_daemon_started_service_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5188,7 +6176,7 @@ func (x *OpenConnectBrowserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenConnectBrowserRequest.ProtoReflect.Descriptor instead. func (*OpenConnectBrowserRequest) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{68} + return file_daemon_started_service_proto_rawDescGZIP(), []int{84} } func (x *OpenConnectBrowserRequest) GetUrl() string { @@ -5250,7 +6238,7 @@ type OpenConnectBrowserCookie struct { func (x *OpenConnectBrowserCookie) Reset() { *x = OpenConnectBrowserCookie{} - mi := &file_daemon_started_service_proto_msgTypes[69] + mi := &file_daemon_started_service_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5262,7 +6250,7 @@ func (x *OpenConnectBrowserCookie) String() string { func (*OpenConnectBrowserCookie) ProtoMessage() {} func (x *OpenConnectBrowserCookie) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[69] + mi := &file_daemon_started_service_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5275,7 +6263,7 @@ func (x *OpenConnectBrowserCookie) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenConnectBrowserCookie.ProtoReflect.Descriptor instead. func (*OpenConnectBrowserCookie) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{69} + return file_daemon_started_service_proto_rawDescGZIP(), []int{85} } func (x *OpenConnectBrowserCookie) GetName() string { @@ -5302,7 +6290,7 @@ type OpenConnectBrowserHeader struct { func (x *OpenConnectBrowserHeader) Reset() { *x = OpenConnectBrowserHeader{} - mi := &file_daemon_started_service_proto_msgTypes[70] + mi := &file_daemon_started_service_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5314,7 +6302,7 @@ func (x *OpenConnectBrowserHeader) String() string { func (*OpenConnectBrowserHeader) ProtoMessage() {} func (x *OpenConnectBrowserHeader) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[70] + mi := &file_daemon_started_service_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5327,7 +6315,7 @@ func (x *OpenConnectBrowserHeader) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenConnectBrowserHeader.ProtoReflect.Descriptor instead. func (*OpenConnectBrowserHeader) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{70} + return file_daemon_started_service_proto_rawDescGZIP(), []int{86} } func (x *OpenConnectBrowserHeader) GetName() string { @@ -5353,7 +6341,7 @@ type OpenConnectAuthFormResponse struct { func (x *OpenConnectAuthFormResponse) Reset() { *x = OpenConnectAuthFormResponse{} - mi := &file_daemon_started_service_proto_msgTypes[71] + mi := &file_daemon_started_service_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5365,7 +6353,7 @@ func (x *OpenConnectAuthFormResponse) String() string { func (*OpenConnectAuthFormResponse) ProtoMessage() {} func (x *OpenConnectAuthFormResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[71] + mi := &file_daemon_started_service_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5378,7 +6366,7 @@ func (x *OpenConnectAuthFormResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenConnectAuthFormResponse.ProtoReflect.Descriptor instead. func (*OpenConnectAuthFormResponse) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{71} + return file_daemon_started_service_proto_rawDescGZIP(), []int{87} } func (x *OpenConnectAuthFormResponse) GetValues() map[string]string { @@ -5399,7 +6387,7 @@ type OpenConnectBrowserResult struct { func (x *OpenConnectBrowserResult) Reset() { *x = OpenConnectBrowserResult{} - mi := &file_daemon_started_service_proto_msgTypes[72] + mi := &file_daemon_started_service_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5411,7 +6399,7 @@ func (x *OpenConnectBrowserResult) String() string { func (*OpenConnectBrowserResult) ProtoMessage() {} func (x *OpenConnectBrowserResult) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[72] + mi := &file_daemon_started_service_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5424,7 +6412,7 @@ func (x *OpenConnectBrowserResult) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenConnectBrowserResult.ProtoReflect.Descriptor instead. func (*OpenConnectBrowserResult) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{72} + return file_daemon_started_service_proto_rawDescGZIP(), []int{88} } func (x *OpenConnectBrowserResult) GetFinalURL() string { @@ -5463,7 +6451,7 @@ type OpenConnectAuthResponseSubmission struct { func (x *OpenConnectAuthResponseSubmission) Reset() { *x = OpenConnectAuthResponseSubmission{} - mi := &file_daemon_started_service_proto_msgTypes[73] + mi := &file_daemon_started_service_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5475,7 +6463,7 @@ func (x *OpenConnectAuthResponseSubmission) String() string { func (*OpenConnectAuthResponseSubmission) ProtoMessage() {} func (x *OpenConnectAuthResponseSubmission) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[73] + mi := &file_daemon_started_service_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5488,7 +6476,7 @@ func (x *OpenConnectAuthResponseSubmission) ProtoReflect() protoreflect.Message // Deprecated: Use OpenConnectAuthResponseSubmission.ProtoReflect.Descriptor instead. func (*OpenConnectAuthResponseSubmission) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{73} + return file_daemon_started_service_proto_rawDescGZIP(), []int{89} } func (x *OpenConnectAuthResponseSubmission) GetEndpointTag() string { @@ -5556,7 +6544,7 @@ type OpenConnectAuthChallengeCancel struct { func (x *OpenConnectAuthChallengeCancel) Reset() { *x = OpenConnectAuthChallengeCancel{} - mi := &file_daemon_started_service_proto_msgTypes[74] + mi := &file_daemon_started_service_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5568,7 +6556,7 @@ func (x *OpenConnectAuthChallengeCancel) String() string { func (*OpenConnectAuthChallengeCancel) ProtoMessage() {} func (x *OpenConnectAuthChallengeCancel) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[74] + mi := &file_daemon_started_service_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5581,7 +6569,7 @@ func (x *OpenConnectAuthChallengeCancel) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenConnectAuthChallengeCancel.ProtoReflect.Descriptor instead. func (*OpenConnectAuthChallengeCancel) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{74} + return file_daemon_started_service_proto_rawDescGZIP(), []int{90} } func (x *OpenConnectAuthChallengeCancel) GetEndpointTag() string { @@ -5607,7 +6595,7 @@ type OpenVPNStatusUpdate struct { func (x *OpenVPNStatusUpdate) Reset() { *x = OpenVPNStatusUpdate{} - mi := &file_daemon_started_service_proto_msgTypes[75] + mi := &file_daemon_started_service_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5619,7 +6607,7 @@ func (x *OpenVPNStatusUpdate) String() string { func (*OpenVPNStatusUpdate) ProtoMessage() {} func (x *OpenVPNStatusUpdate) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[75] + mi := &file_daemon_started_service_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5632,7 +6620,7 @@ func (x *OpenVPNStatusUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenVPNStatusUpdate.ProtoReflect.Descriptor instead. func (*OpenVPNStatusUpdate) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{75} + return file_daemon_started_service_proto_rawDescGZIP(), []int{91} } func (x *OpenVPNStatusUpdate) GetEndpoints() []*OpenVPNEndpointStatus { @@ -5656,7 +6644,7 @@ type OpenVPNEndpointStatus struct { func (x *OpenVPNEndpointStatus) Reset() { *x = OpenVPNEndpointStatus{} - mi := &file_daemon_started_service_proto_msgTypes[76] + mi := &file_daemon_started_service_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5668,7 +6656,7 @@ func (x *OpenVPNEndpointStatus) String() string { func (*OpenVPNEndpointStatus) ProtoMessage() {} func (x *OpenVPNEndpointStatus) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[76] + mi := &file_daemon_started_service_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5681,7 +6669,7 @@ func (x *OpenVPNEndpointStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenVPNEndpointStatus.ProtoReflect.Descriptor instead. func (*OpenVPNEndpointStatus) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{76} + return file_daemon_started_service_proto_rawDescGZIP(), []int{92} } func (x *OpenVPNEndpointStatus) GetEndpointTag() string { @@ -5742,7 +6730,7 @@ type OpenVPNTunnelInfo struct { func (x *OpenVPNTunnelInfo) Reset() { *x = OpenVPNTunnelInfo{} - mi := &file_daemon_started_service_proto_msgTypes[77] + mi := &file_daemon_started_service_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5754,7 +6742,7 @@ func (x *OpenVPNTunnelInfo) String() string { func (*OpenVPNTunnelInfo) ProtoMessage() {} func (x *OpenVPNTunnelInfo) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[77] + mi := &file_daemon_started_service_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5767,7 +6755,7 @@ func (x *OpenVPNTunnelInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenVPNTunnelInfo.ProtoReflect.Descriptor instead. func (*OpenVPNTunnelInfo) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{77} + return file_daemon_started_service_proto_rawDescGZIP(), []int{93} } func (x *OpenVPNTunnelInfo) GetServer() string { @@ -5843,7 +6831,7 @@ type OpenVPNChallenge struct { func (x *OpenVPNChallenge) Reset() { *x = OpenVPNChallenge{} - mi := &file_daemon_started_service_proto_msgTypes[78] + mi := &file_daemon_started_service_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5855,7 +6843,7 @@ func (x *OpenVPNChallenge) String() string { func (*OpenVPNChallenge) ProtoMessage() {} func (x *OpenVPNChallenge) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[78] + mi := &file_daemon_started_service_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5868,7 +6856,7 @@ func (x *OpenVPNChallenge) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenVPNChallenge.ProtoReflect.Descriptor instead. func (*OpenVPNChallenge) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{78} + return file_daemon_started_service_proto_rawDescGZIP(), []int{94} } func (x *OpenVPNChallenge) GetId() string { @@ -5947,7 +6935,7 @@ type OpenVPNChallengeSubmission struct { func (x *OpenVPNChallengeSubmission) Reset() { *x = OpenVPNChallengeSubmission{} - mi := &file_daemon_started_service_proto_msgTypes[79] + mi := &file_daemon_started_service_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5959,7 +6947,7 @@ func (x *OpenVPNChallengeSubmission) String() string { func (*OpenVPNChallengeSubmission) ProtoMessage() {} func (x *OpenVPNChallengeSubmission) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[79] + mi := &file_daemon_started_service_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5972,7 +6960,7 @@ func (x *OpenVPNChallengeSubmission) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenVPNChallengeSubmission.ProtoReflect.Descriptor instead. func (*OpenVPNChallengeSubmission) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{79} + return file_daemon_started_service_proto_rawDescGZIP(), []int{95} } func (x *OpenVPNChallengeSubmission) GetEndpointTag() string { @@ -6020,7 +7008,7 @@ type OpenVPNChallengeCancel struct { func (x *OpenVPNChallengeCancel) Reset() { *x = OpenVPNChallengeCancel{} - mi := &file_daemon_started_service_proto_msgTypes[80] + mi := &file_daemon_started_service_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6032,7 +7020,7 @@ func (x *OpenVPNChallengeCancel) String() string { func (*OpenVPNChallengeCancel) ProtoMessage() {} func (x *OpenVPNChallengeCancel) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[80] + mi := &file_daemon_started_service_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6045,7 +7033,7 @@ func (x *OpenVPNChallengeCancel) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenVPNChallengeCancel.ProtoReflect.Descriptor instead. func (*OpenVPNChallengeCancel) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{80} + return file_daemon_started_service_proto_rawDescGZIP(), []int{96} } func (x *OpenVPNChallengeCancel) GetEndpointTag() string { @@ -6062,6 +7050,232 @@ func (x *OpenVPNChallengeCancel) GetChallengeID() string { return "" } +type NotificationEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Event: + // + // *NotificationEvent_Send + // *NotificationEvent_Cancel + Event isNotificationEvent_Event `protobuf_oneof:"event"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NotificationEvent) Reset() { + *x = NotificationEvent{} + mi := &file_daemon_started_service_proto_msgTypes[97] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NotificationEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NotificationEvent) ProtoMessage() {} + +func (x *NotificationEvent) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[97] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NotificationEvent.ProtoReflect.Descriptor instead. +func (*NotificationEvent) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{97} +} + +func (x *NotificationEvent) GetEvent() isNotificationEvent_Event { + if x != nil { + return x.Event + } + return nil +} + +func (x *NotificationEvent) GetSend() *Notification { + if x != nil { + if x, ok := x.Event.(*NotificationEvent_Send); ok { + return x.Send + } + } + return nil +} + +func (x *NotificationEvent) GetCancel() *NotificationCancel { + if x != nil { + if x, ok := x.Event.(*NotificationEvent_Cancel); ok { + return x.Cancel + } + } + return nil +} + +type isNotificationEvent_Event interface { + isNotificationEvent_Event() +} + +type NotificationEvent_Send struct { + Send *Notification `protobuf:"bytes,1,opt,name=send,proto3,oneof"` +} + +type NotificationEvent_Cancel struct { + Cancel *NotificationCancel `protobuf:"bytes,2,opt,name=cancel,proto3,oneof"` +} + +func (*NotificationEvent_Send) isNotificationEvent_Event() {} + +func (*NotificationEvent_Cancel) isNotificationEvent_Event() {} + +type Notification struct { + state protoimpl.MessageState `protogen:"open.v1"` + Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` + TypeName string `protobuf:"bytes,2,opt,name=typeName,proto3" json:"typeName,omitempty"` + TypeID int32 `protobuf:"varint,3,opt,name=typeID,proto3" json:"typeID,omitempty"` + Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"` + Subtitle string `protobuf:"bytes,5,opt,name=subtitle,proto3" json:"subtitle,omitempty"` + Body string `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` + OpenURL string `protobuf:"bytes,7,opt,name=openURL,proto3" json:"openURL,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Notification) Reset() { + *x = Notification{} + mi := &file_daemon_started_service_proto_msgTypes[98] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Notification) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Notification) ProtoMessage() {} + +func (x *Notification) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[98] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Notification.ProtoReflect.Descriptor instead. +func (*Notification) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{98} +} + +func (x *Notification) GetIdentifier() string { + if x != nil { + return x.Identifier + } + return "" +} + +func (x *Notification) GetTypeName() string { + if x != nil { + return x.TypeName + } + return "" +} + +func (x *Notification) GetTypeID() int32 { + if x != nil { + return x.TypeID + } + return 0 +} + +func (x *Notification) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *Notification) GetSubtitle() string { + if x != nil { + return x.Subtitle + } + return "" +} + +func (x *Notification) GetBody() string { + if x != nil { + return x.Body + } + return "" +} + +func (x *Notification) GetOpenURL() string { + if x != nil { + return x.OpenURL + } + return "" +} + +type NotificationCancel struct { + state protoimpl.MessageState `protogen:"open.v1"` + Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` + TypeID int32 `protobuf:"varint,2,opt,name=typeID,proto3" json:"typeID,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NotificationCancel) Reset() { + *x = NotificationCancel{} + mi := &file_daemon_started_service_proto_msgTypes[99] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NotificationCancel) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NotificationCancel) ProtoMessage() {} + +func (x *NotificationCancel) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[99] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NotificationCancel.ProtoReflect.Descriptor instead. +func (*NotificationCancel) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{99} +} + +func (x *NotificationCancel) GetIdentifier() string { + if x != nil { + return x.Identifier + } + return "" +} + +func (x *NotificationCancel) GetTypeID() int32 { + if x != nil { + return x.TypeID + } + return 0 +} + type Log_Message struct { state protoimpl.MessageState `protogen:"open.v1"` Level LogLevel `protobuf:"varint,1,opt,name=level,proto3,enum=daemon.LogLevel" json:"level,omitempty"` @@ -6072,7 +7286,7 @@ type Log_Message struct { func (x *Log_Message) Reset() { *x = Log_Message{} - mi := &file_daemon_started_service_proto_msgTypes[81] + mi := &file_daemon_started_service_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6084,7 +7298,7 @@ func (x *Log_Message) String() string { func (*Log_Message) ProtoMessage() {} func (x *Log_Message) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[81] + mi := &file_daemon_started_service_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6280,7 +7494,7 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x05error\x18\a \x01(\tR\x05error\x12*\n" + "\x10natTypeSupported\x18\b \x01(\bR\x10natTypeSupported\"V\n" + "\x15TailscaleStatusUpdate\x12=\n" + - "\tendpoints\x18\x01 \x03(\v2\x1f.daemon.TailscaleEndpointStatusR\tendpoints\"\x95\x03\n" + + "\tendpoints\x18\x01 \x03(\v2\x1f.daemon.TailscaleEndpointStatusR\tendpoints\"\xc1\x04\n" + "\x17TailscaleEndpointStatus\x12 \n" + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12\"\n" + "\fbackendState\x18\x02 \x01(\tR\fbackendState\x12\x1c\n" + @@ -6294,13 +7508,17 @@ const file_daemon_started_service_proto_rawDesc = "" + "userGroups\x121\n" + "\bexitNode\x18\t \x01(\v2\x15.daemon.TailscalePeerR\bexitNode\x12\x18\n" + "\akeyAuth\x18\n" + - " \x01(\bR\akeyAuth\"\xbf\x01\n" + + " \x01(\bR\akeyAuth\x12$\n" + + "\rcanShareFiles\x18\v \x01(\bR\rcanShareFiles\x12*\n" + + "\x10waitingFileCount\x18\f \x01(\x05R\x10waitingFileCount\x12.\n" + + "\x12receivingFileCount\x18\r \x01(\x05R\x12receivingFileCount\x12(\n" + + "\x0funreadFileCount\x18\x0e \x01(\x05R\x0funreadFileCount\"\xbf\x01\n" + "\x12TailscaleUserGroup\x12\x16\n" + "\x06userID\x18\x01 \x01(\x03R\x06userID\x12\x1c\n" + "\tloginName\x18\x02 \x01(\tR\tloginName\x12 \n" + "\vdisplayName\x18\x03 \x01(\tR\vdisplayName\x12$\n" + "\rprofilePicURL\x18\x04 \x01(\tR\rprofilePicURL\x12+\n" + - "\x05peers\x18\x05 \x03(\v2\x15.daemon.TailscalePeerR\x05peers\"\xd3\x03\n" + + "\x05peers\x18\x05 \x03(\v2\x15.daemon.TailscalePeerR\x05peers\"\xfd\x03\n" + "\rTailscalePeer\x12\x1a\n" + "\bhostName\x18\x01 \x01(\tR\bhostName\x12\x18\n" + "\adnsName\x18\x02 \x01(\tR\adnsName\x12\x0e\n" + @@ -6320,17 +7538,19 @@ const file_daemon_started_service_proto_rawDesc = "" + "\n" + "shareeNode\x18\x0f \x01(\bR\n" + "shareeNode\x12\x1a\n" + - "\blastSeen\x18\x10 \x01(\x03R\blastSeen\"P\n" + + "\blastSeen\x18\x10 \x01(\x03R\blastSeen\x12(\n" + + "\x0fcanReceiveFiles\x18\x11 \x01(\bR\x0fcanReceiveFiles\"P\n" + "\x14TailscalePingRequest\x12 \n" + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12\x16\n" + - "\x06peerIP\x18\x02 \x01(\tR\x06peerIP\"\xcf\x01\n" + + "\x06peerIP\x18\x02 \x01(\tR\x06peerIP\"\xed\x01\n" + "\x15TailscalePingResponse\x12\x1c\n" + "\tlatencyMs\x18\x01 \x01(\x01R\tlatencyMs\x12\x1a\n" + "\bisDirect\x18\x02 \x01(\bR\bisDirect\x12\x1a\n" + "\bendpoint\x18\x03 \x01(\tR\bendpoint\x12\"\n" + "\fderpRegionID\x18\x04 \x01(\x05R\fderpRegionID\x12&\n" + "\x0ederpRegionCode\x18\x05 \x01(\tR\x0ederpRegionCode\x12\x14\n" + - "\x05error\x18\x06 \x01(\tR\x05error\"[\n" + + "\x05error\x18\x06 \x01(\tR\x05error\x12\x1c\n" + + "\tpeerRelay\x18\a \x01(\tR\tpeerRelay\"[\n" + "\x1bSetTailscaleExitNodeRequest\x12 \n" + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12\x1a\n" + "\bstableID\x18\x02 \x01(\tR\bstableID\":\n" + @@ -6379,7 +7599,68 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x06signal\x18\x02 \x01(\tR\x06signal\x12\"\n" + "\ferrorMessage\x18\x03 \x01(\tR\ferrorMessage\"-\n" + "\x11TailscaleSSHError\x12\x18\n" + - "\amessage\x18\x01 \x01(\tR\amessage\"\xc1\x01\n" + + "\amessage\x18\x01 \x01(\tR\amessage\"A\n" + + "\x1dSubscribeTaildropInboxRequest\x12 \n" + + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\"@\n" + + "\x1cMarkTaildropInboxReadRequest\x12 \n" + + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\"\x9a\x01\n" + + "\rTaildropInbox\x12 \n" + + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12*\n" + + "\x05files\x18\x02 \x03(\v2\x14.daemon.TaildropFileR\x05files\x12;\n" + + "\treceiving\x18\x03 \x03(\v2\x1d.daemon.TaildropReceivingFileR\treceiving\"v\n" + + "\fTaildropFile\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + + "\x04size\x18\x02 \x01(\x03R\x04size\x12\x1e\n" + + "\n" + + "senderName\x18\x03 \x01(\tR\n" + + "senderName\x12\x1e\n" + + "\n" + + "modifiedAt\x18\x04 \x01(\x03R\n" + + "modifiedAt\"\xa1\x01\n" + + "\x15TaildropReceivingFile\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + + "\x04size\x18\x02 \x01(\x03R\x04size\x12$\n" + + "\rreceivedBytes\x18\x03 \x01(\x03R\rreceivedBytes\x12\x1a\n" + + "\bsenderID\x18\x04 \x01(\tR\bsenderID\x12\x1e\n" + + "\n" + + "senderName\x18\x05 \x01(\tR\n" + + "senderName\"\xc4\x01\n" + + "\x19TaildropSendClientMessage\x121\n" + + "\x05start\x18\x01 \x01(\v2\x19.daemon.TaildropSendStartH\x00R\x05start\x121\n" + + "\x05chunk\x18\x02 \x01(\v2\x19.daemon.TaildropFileChunkH\x00R\x05chunk\x126\n" + + "\bfileDone\x18\x03 \x01(\v2\x18.daemon.TaildropFileDoneH\x00R\bfileDoneB\t\n" + + "\amessage\"\x8d\x01\n" + + "\x11TaildropSendStart\x12 \n" + + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12\"\n" + + "\fpeerStableID\x18\x02 \x01(\tR\fpeerStableID\x122\n" + + "\x05files\x18\x03 \x03(\v2\x1c.daemon.TaildropOutgoingFileR\x05files\">\n" + + "\x14TaildropOutgoingFile\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + + "\x04size\x18\x02 \x01(\x03R\x04size\"'\n" + + "\x11TaildropFileChunk\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\"\x12\n" + + "\x10TaildropFileDone\"\x8a\x01\n" + + "\x19TaildropSendServerMessage\x12:\n" + + "\bprogress\x18\x01 \x01(\v2\x1c.daemon.TaildropSendProgressH\x00R\bprogress\x12&\n" + + "\rreceivedBytes\x18\x02 \x01(\x03H\x00R\rreceivedBytesB\t\n" + + "\amessage\"x\n" + + "\x14TaildropSendProgress\x12\x1c\n" + + "\tfileIndex\x18\x01 \x01(\x05R\tfileIndex\x12\x1c\n" + + "\tsentBytes\x18\x02 \x01(\x03R\tsentBytes\x12$\n" + + "\rfileCompleted\x18\x03 \x01(\bR\rfileCompleted\"S\n" + + "\x1bDownloadTaildropFileRequest\x12 \n" + + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\"C\n" + + "\x19DownloadTaildropFileChunk\x12\x12\n" + + "\x04size\x18\x01 \x01(\x03R\x04size\x12\x12\n" + + "\x04data\x18\x02 \x01(\fR\x04data\"Q\n" + + "\x19DeleteTaildropFileRequest\x12 \n" + + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\"r\n" + + "\x1eCancelTaildropReceivingRequest\x12 \n" + + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12\x1a\n" + + "\bsenderID\x18\x02 \x01(\tR\bsenderID\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\"\xc1\x01\n" + "\x12USBProviderMessage\x121\n" + "\x06attach\x18\x01 \x01(\v2\x17.daemon.USBDeviceAttachH\x00R\x06attach\x121\n" + "\x06detach\x18\x02 \x01(\v2\x17.daemon.USBDeviceDetachH\x00R\x06detach\x12:\n" + @@ -6588,7 +7869,26 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x06secret\x18\x05 \x01(\tR\x06secret\"\\\n" + "\x16OpenVPNChallengeCancel\x12 \n" + "\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12 \n" + - "\vchallengeID\x18\x02 \x01(\tR\vchallengeID*U\n" + + "\vchallengeID\x18\x02 \x01(\tR\vchallengeID\"~\n" + + "\x11NotificationEvent\x12*\n" + + "\x04send\x18\x01 \x01(\v2\x14.daemon.NotificationH\x00R\x04send\x124\n" + + "\x06cancel\x18\x02 \x01(\v2\x1a.daemon.NotificationCancelH\x00R\x06cancelB\a\n" + + "\x05event\"\xc2\x01\n" + + "\fNotification\x12\x1e\n" + + "\n" + + "identifier\x18\x01 \x01(\tR\n" + + "identifier\x12\x1a\n" + + "\btypeName\x18\x02 \x01(\tR\btypeName\x12\x16\n" + + "\x06typeID\x18\x03 \x01(\x05R\x06typeID\x12\x14\n" + + "\x05title\x18\x04 \x01(\tR\x05title\x12\x1a\n" + + "\bsubtitle\x18\x05 \x01(\tR\bsubtitle\x12\x12\n" + + "\x04body\x18\x06 \x01(\tR\x04body\x12\x18\n" + + "\aopenURL\x18\a \x01(\tR\aopenURL\"L\n" + + "\x12NotificationCancel\x12\x1e\n" + + "\n" + + "identifier\x18\x01 \x01(\tR\n" + + "identifier\x12\x16\n" + + "\x06typeID\x18\x02 \x01(\x05R\x06typeID*U\n" + "\bLogLevel\x12\t\n" + "\x05PANIC\x10\x00\x12\t\n" + "\x05FATAL\x10\x01\x12\t\n" + @@ -6611,7 +7911,7 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x17USB_BACKEND_LINUX_SYSFS\x10\x01\x12\x17\n" + "\x13USB_BACKEND_DYNAMIC\x10\x02\x12\x1c\n" + "\x18USB_BACKEND_DARWIN_IOKIT\x10\x03\x12\x1f\n" + - "\x1bUSB_BACKEND_WINDOWS_VBOXUSB\x10\x042\x80\x15\n" + + "\x1bUSB_BACKEND_WINDOWS_VBOXUSB\x10\x042\xfb\x19\n" + "\x0eStartedService\x127\n" + "\n" + "GetVersion\x12\x16.google.protobuf.Empty\x1a\x0f.daemon.Version\"\x00\x12K\n" + @@ -6639,7 +7939,13 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x12StartTailscalePing\x12\x1c.daemon.TailscalePingRequest\x1a\x1d.daemon.TailscalePingResponse\"\x000\x01\x12U\n" + "\x14SetTailscaleExitNode\x12#.daemon.SetTailscaleExitNodeRequest\x1a\x16.google.protobuf.Empty\"\x00\x12K\n" + "\x0fTailscaleLogout\x12\x1e.daemon.TailscaleLogoutRequest\x1a\x16.google.protobuf.Empty\"\x00\x12f\n" + - "\x18StartTailscaleSSHSession\x12!.daemon.TailscaleSSHClientMessage\x1a!.daemon.TailscaleSSHServerMessage\"\x00(\x010\x01\x12O\n" + + "\x18StartTailscaleSSHSession\x12!.daemon.TailscaleSSHClientMessage\x1a!.daemon.TailscaleSSHServerMessage\"\x00(\x010\x01\x12Z\n" + + "\x16SubscribeTaildropInbox\x12%.daemon.SubscribeTaildropInboxRequest\x1a\x15.daemon.TaildropInbox\"\x000\x01\x12W\n" + + "\x15MarkTaildropInboxRead\x12$.daemon.MarkTaildropInboxReadRequest\x1a\x16.google.protobuf.Empty\"\x00\x12_\n" + + "\x11SendTaildropFiles\x12!.daemon.TaildropSendClientMessage\x1a!.daemon.TaildropSendServerMessage\"\x00(\x010\x01\x12b\n" + + "\x14DownloadTaildropFile\x12#.daemon.DownloadTaildropFileRequest\x1a!.daemon.DownloadTaildropFileChunk\"\x000\x01\x12Q\n" + + "\x12DeleteTaildropFile\x12!.daemon.DeleteTaildropFileRequest\x1a\x16.google.protobuf.Empty\"\x00\x12[\n" + + "\x17CancelTaildropReceiving\x12&.daemon.CancelTaildropReceivingRequest\x1a\x16.google.protobuf.Empty\"\x00\x12O\n" + "\x11ProvideUSBDevices\x12\x1a.daemon.USBProviderMessage\x1a\x18.daemon.USBServerMessage\"\x00(\x010\x01\x12Y\n" + "\x1aSubscribeUSBIPServerStatus\x12\x16.google.protobuf.Empty\x1a\x1f.daemon.USBIPServerStatusUpdate\"\x000\x01\x12Y\n" + "\x1aSubscribeOpenConnectStatus\x12\x16.google.protobuf.Empty\x1a\x1f.daemon.OpenConnectStatusUpdate\"\x000\x01\x12d\n" + @@ -6647,7 +7953,8 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x1eCancelOpenConnectAuthChallenge\x12&.daemon.OpenConnectAuthChallengeCancel\x1a\x16.google.protobuf.Empty\"\x00\x12Q\n" + "\x16SubscribeOpenVPNStatus\x12\x16.google.protobuf.Empty\x1a\x1b.daemon.OpenVPNStatusUpdate\"\x000\x01\x12^\n" + "\x1eSubmitOpenVPNChallengeResponse\x12\".daemon.OpenVPNChallengeSubmission\x1a\x16.google.protobuf.Empty\"\x00\x12R\n" + - "\x16CancelOpenVPNChallenge\x12\x1e.daemon.OpenVPNChallengeCancel\x1a\x16.google.protobuf.Empty\"\x00B%Z#github.com/sagernet/sing-box/daemonb\x06proto3" + "\x16CancelOpenVPNChallenge\x12\x1e.daemon.OpenVPNChallengeCancel\x1a\x16.google.protobuf.Empty\"\x00\x12O\n" + + "\x16SubscribeNotifications\x12\x16.google.protobuf.Empty\x1a\x19.daemon.NotificationEvent\"\x000\x01B%Z#github.com/sagernet/sing-box/daemonb\x06proto3" var ( file_daemon_started_service_proto_rawDescOnce sync.Once @@ -6663,7 +7970,7 @@ func file_daemon_started_service_proto_rawDescGZIP() []byte { var ( file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 5) - file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 83) + file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 102) file_daemon_started_service_proto_goTypes = []any{ (LogLevel)(0), // 0: daemon.LogLevel (ConnectionEventType)(0), // 1: daemon.ConnectionEventType @@ -6716,177 +8023,219 @@ var ( (*TailscaleSSHOutput)(nil), // 48: daemon.TailscaleSSHOutput (*TailscaleSSHExit)(nil), // 49: daemon.TailscaleSSHExit (*TailscaleSSHError)(nil), // 50: daemon.TailscaleSSHError - (*USBProviderMessage)(nil), // 51: daemon.USBProviderMessage - (*USBServerMessage)(nil), // 52: daemon.USBServerMessage - (*USBDeviceDescriptor)(nil), // 53: daemon.USBDeviceDescriptor - (*USBDeviceAttach)(nil), // 54: daemon.USBDeviceAttach - (*USBInterface)(nil), // 55: daemon.USBInterface - (*USBDeviceDetach)(nil), // 56: daemon.USBDeviceDetach - (*USBDeviceReady)(nil), // 57: daemon.USBDeviceReady - (*USBURBRequest)(nil), // 58: daemon.USBURBRequest - (*USBURBResponse)(nil), // 59: daemon.USBURBResponse - (*USBIsoPacket)(nil), // 60: daemon.USBIsoPacket - (*USBEndpointAbort)(nil), // 61: daemon.USBEndpointAbort - (*USBError)(nil), // 62: daemon.USBError - (*USBIPServerStatusUpdate)(nil), // 63: daemon.USBIPServerStatusUpdate - (*USBIPServerStatus)(nil), // 64: daemon.USBIPServerStatus - (*USBSharedDevice)(nil), // 65: daemon.USBSharedDevice - (*OpenConnectStatusUpdate)(nil), // 66: daemon.OpenConnectStatusUpdate - (*OpenConnectEndpointStatus)(nil), // 67: daemon.OpenConnectEndpointStatus - (*OpenConnectTunnelInfo)(nil), // 68: daemon.OpenConnectTunnelInfo - (*OpenConnectAuthChallenge)(nil), // 69: daemon.OpenConnectAuthChallenge - (*OpenConnectAuthForm)(nil), // 70: daemon.OpenConnectAuthForm - (*OpenConnectAuthFormField)(nil), // 71: daemon.OpenConnectAuthFormField - (*OpenConnectAuthFormChoice)(nil), // 72: daemon.OpenConnectAuthFormChoice - (*OpenConnectBrowserRequest)(nil), // 73: daemon.OpenConnectBrowserRequest - (*OpenConnectBrowserCookie)(nil), // 74: daemon.OpenConnectBrowserCookie - (*OpenConnectBrowserHeader)(nil), // 75: daemon.OpenConnectBrowserHeader - (*OpenConnectAuthFormResponse)(nil), // 76: daemon.OpenConnectAuthFormResponse - (*OpenConnectBrowserResult)(nil), // 77: daemon.OpenConnectBrowserResult - (*OpenConnectAuthResponseSubmission)(nil), // 78: daemon.OpenConnectAuthResponseSubmission - (*OpenConnectAuthChallengeCancel)(nil), // 79: daemon.OpenConnectAuthChallengeCancel - (*OpenVPNStatusUpdate)(nil), // 80: daemon.OpenVPNStatusUpdate - (*OpenVPNEndpointStatus)(nil), // 81: daemon.OpenVPNEndpointStatus - (*OpenVPNTunnelInfo)(nil), // 82: daemon.OpenVPNTunnelInfo - (*OpenVPNChallenge)(nil), // 83: daemon.OpenVPNChallenge - (*OpenVPNChallengeSubmission)(nil), // 84: daemon.OpenVPNChallengeSubmission - (*OpenVPNChallengeCancel)(nil), // 85: daemon.OpenVPNChallengeCancel - (*Log_Message)(nil), // 86: daemon.Log.Message - nil, // 87: daemon.OpenConnectAuthFormResponse.ValuesEntry - (*emptypb.Empty)(nil), // 88: google.protobuf.Empty + (*SubscribeTaildropInboxRequest)(nil), // 51: daemon.SubscribeTaildropInboxRequest + (*MarkTaildropInboxReadRequest)(nil), // 52: daemon.MarkTaildropInboxReadRequest + (*TaildropInbox)(nil), // 53: daemon.TaildropInbox + (*TaildropFile)(nil), // 54: daemon.TaildropFile + (*TaildropReceivingFile)(nil), // 55: daemon.TaildropReceivingFile + (*TaildropSendClientMessage)(nil), // 56: daemon.TaildropSendClientMessage + (*TaildropSendStart)(nil), // 57: daemon.TaildropSendStart + (*TaildropOutgoingFile)(nil), // 58: daemon.TaildropOutgoingFile + (*TaildropFileChunk)(nil), // 59: daemon.TaildropFileChunk + (*TaildropFileDone)(nil), // 60: daemon.TaildropFileDone + (*TaildropSendServerMessage)(nil), // 61: daemon.TaildropSendServerMessage + (*TaildropSendProgress)(nil), // 62: daemon.TaildropSendProgress + (*DownloadTaildropFileRequest)(nil), // 63: daemon.DownloadTaildropFileRequest + (*DownloadTaildropFileChunk)(nil), // 64: daemon.DownloadTaildropFileChunk + (*DeleteTaildropFileRequest)(nil), // 65: daemon.DeleteTaildropFileRequest + (*CancelTaildropReceivingRequest)(nil), // 66: daemon.CancelTaildropReceivingRequest + (*USBProviderMessage)(nil), // 67: daemon.USBProviderMessage + (*USBServerMessage)(nil), // 68: daemon.USBServerMessage + (*USBDeviceDescriptor)(nil), // 69: daemon.USBDeviceDescriptor + (*USBDeviceAttach)(nil), // 70: daemon.USBDeviceAttach + (*USBInterface)(nil), // 71: daemon.USBInterface + (*USBDeviceDetach)(nil), // 72: daemon.USBDeviceDetach + (*USBDeviceReady)(nil), // 73: daemon.USBDeviceReady + (*USBURBRequest)(nil), // 74: daemon.USBURBRequest + (*USBURBResponse)(nil), // 75: daemon.USBURBResponse + (*USBIsoPacket)(nil), // 76: daemon.USBIsoPacket + (*USBEndpointAbort)(nil), // 77: daemon.USBEndpointAbort + (*USBError)(nil), // 78: daemon.USBError + (*USBIPServerStatusUpdate)(nil), // 79: daemon.USBIPServerStatusUpdate + (*USBIPServerStatus)(nil), // 80: daemon.USBIPServerStatus + (*USBSharedDevice)(nil), // 81: daemon.USBSharedDevice + (*OpenConnectStatusUpdate)(nil), // 82: daemon.OpenConnectStatusUpdate + (*OpenConnectEndpointStatus)(nil), // 83: daemon.OpenConnectEndpointStatus + (*OpenConnectTunnelInfo)(nil), // 84: daemon.OpenConnectTunnelInfo + (*OpenConnectAuthChallenge)(nil), // 85: daemon.OpenConnectAuthChallenge + (*OpenConnectAuthForm)(nil), // 86: daemon.OpenConnectAuthForm + (*OpenConnectAuthFormField)(nil), // 87: daemon.OpenConnectAuthFormField + (*OpenConnectAuthFormChoice)(nil), // 88: daemon.OpenConnectAuthFormChoice + (*OpenConnectBrowserRequest)(nil), // 89: daemon.OpenConnectBrowserRequest + (*OpenConnectBrowserCookie)(nil), // 90: daemon.OpenConnectBrowserCookie + (*OpenConnectBrowserHeader)(nil), // 91: daemon.OpenConnectBrowserHeader + (*OpenConnectAuthFormResponse)(nil), // 92: daemon.OpenConnectAuthFormResponse + (*OpenConnectBrowserResult)(nil), // 93: daemon.OpenConnectBrowserResult + (*OpenConnectAuthResponseSubmission)(nil), // 94: daemon.OpenConnectAuthResponseSubmission + (*OpenConnectAuthChallengeCancel)(nil), // 95: daemon.OpenConnectAuthChallengeCancel + (*OpenVPNStatusUpdate)(nil), // 96: daemon.OpenVPNStatusUpdate + (*OpenVPNEndpointStatus)(nil), // 97: daemon.OpenVPNEndpointStatus + (*OpenVPNTunnelInfo)(nil), // 98: daemon.OpenVPNTunnelInfo + (*OpenVPNChallenge)(nil), // 99: daemon.OpenVPNChallenge + (*OpenVPNChallengeSubmission)(nil), // 100: daemon.OpenVPNChallengeSubmission + (*OpenVPNChallengeCancel)(nil), // 101: daemon.OpenVPNChallengeCancel + (*NotificationEvent)(nil), // 102: daemon.NotificationEvent + (*Notification)(nil), // 103: daemon.Notification + (*NotificationCancel)(nil), // 104: daemon.NotificationCancel + (*Log_Message)(nil), // 105: daemon.Log.Message + nil, // 106: daemon.OpenConnectAuthFormResponse.ValuesEntry + (*emptypb.Empty)(nil), // 107: google.protobuf.Empty } ) var file_daemon_started_service_proto_depIdxs = []int32{ - 4, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type - 86, // 1: daemon.Log.messages:type_name -> daemon.Log.Message - 0, // 2: daemon.DefaultLogLevel.level:type_name -> daemon.LogLevel - 12, // 3: daemon.Groups.group:type_name -> daemon.Group - 13, // 4: daemon.Group.items:type_name -> daemon.GroupItem - 1, // 5: daemon.ConnectionEvent.type:type_name -> daemon.ConnectionEventType - 22, // 6: daemon.ConnectionEvent.connection:type_name -> daemon.Connection - 20, // 7: daemon.ConnectionEvents.events:type_name -> daemon.ConnectionEvent - 23, // 8: daemon.Connection.processInfo:type_name -> daemon.ProcessInfo - 26, // 9: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning - 13, // 10: daemon.OutboundList.outbounds:type_name -> daemon.GroupItem - 34, // 11: daemon.TailscaleStatusUpdate.endpoints:type_name -> daemon.TailscaleEndpointStatus - 36, // 12: daemon.TailscaleEndpointStatus.self:type_name -> daemon.TailscalePeer - 35, // 13: daemon.TailscaleEndpointStatus.userGroups:type_name -> daemon.TailscaleUserGroup - 36, // 14: daemon.TailscaleEndpointStatus.exitNode:type_name -> daemon.TailscalePeer - 36, // 15: daemon.TailscaleUserGroup.peers:type_name -> daemon.TailscalePeer - 42, // 16: daemon.TailscaleSSHClientMessage.start:type_name -> daemon.TailscaleSSHStart - 43, // 17: daemon.TailscaleSSHClientMessage.input:type_name -> daemon.TailscaleSSHInput - 44, // 18: daemon.TailscaleSSHClientMessage.resize:type_name -> daemon.TailscaleSSHResize - 46, // 19: daemon.TailscaleSSHServerMessage.authBanner:type_name -> daemon.TailscaleSSHAuthBanner - 47, // 20: daemon.TailscaleSSHServerMessage.ready:type_name -> daemon.TailscaleSSHReady - 48, // 21: daemon.TailscaleSSHServerMessage.output:type_name -> daemon.TailscaleSSHOutput - 49, // 22: daemon.TailscaleSSHServerMessage.exit:type_name -> daemon.TailscaleSSHExit - 50, // 23: daemon.TailscaleSSHServerMessage.error:type_name -> daemon.TailscaleSSHError - 54, // 24: daemon.USBProviderMessage.attach:type_name -> daemon.USBDeviceAttach - 56, // 25: daemon.USBProviderMessage.detach:type_name -> daemon.USBDeviceDetach - 59, // 26: daemon.USBProviderMessage.urbResponse:type_name -> daemon.USBURBResponse - 57, // 27: daemon.USBServerMessage.ready:type_name -> daemon.USBDeviceReady - 58, // 28: daemon.USBServerMessage.urbRequest:type_name -> daemon.USBURBRequest - 61, // 29: daemon.USBServerMessage.abort:type_name -> daemon.USBEndpointAbort - 62, // 30: daemon.USBServerMessage.error:type_name -> daemon.USBError - 55, // 31: daemon.USBDeviceDescriptor.interfaces:type_name -> daemon.USBInterface - 53, // 32: daemon.USBDeviceAttach.descriptor:type_name -> daemon.USBDeviceDescriptor - 60, // 33: daemon.USBURBRequest.isoPackets:type_name -> daemon.USBIsoPacket - 60, // 34: daemon.USBURBResponse.isoPackets:type_name -> daemon.USBIsoPacket - 64, // 35: daemon.USBIPServerStatusUpdate.servers:type_name -> daemon.USBIPServerStatus - 65, // 36: daemon.USBIPServerStatus.devices:type_name -> daemon.USBSharedDevice - 53, // 37: daemon.USBSharedDevice.descriptor:type_name -> daemon.USBDeviceDescriptor - 3, // 38: daemon.USBSharedDevice.backend:type_name -> daemon.USBBackend - 2, // 39: daemon.USBSharedDevice.state:type_name -> daemon.USBDeviceState - 67, // 40: daemon.OpenConnectStatusUpdate.endpoints:type_name -> daemon.OpenConnectEndpointStatus - 69, // 41: daemon.OpenConnectEndpointStatus.authChallenge:type_name -> daemon.OpenConnectAuthChallenge - 68, // 42: daemon.OpenConnectEndpointStatus.tunnelInfo:type_name -> daemon.OpenConnectTunnelInfo - 70, // 43: daemon.OpenConnectAuthChallenge.form:type_name -> daemon.OpenConnectAuthForm - 73, // 44: daemon.OpenConnectAuthChallenge.browser:type_name -> daemon.OpenConnectBrowserRequest - 71, // 45: daemon.OpenConnectAuthForm.fields:type_name -> daemon.OpenConnectAuthFormField - 72, // 46: daemon.OpenConnectAuthFormField.options:type_name -> daemon.OpenConnectAuthFormChoice - 87, // 47: daemon.OpenConnectAuthFormResponse.values:type_name -> daemon.OpenConnectAuthFormResponse.ValuesEntry - 74, // 48: daemon.OpenConnectBrowserResult.cookies:type_name -> daemon.OpenConnectBrowserCookie - 75, // 49: daemon.OpenConnectBrowserResult.headers:type_name -> daemon.OpenConnectBrowserHeader - 76, // 50: daemon.OpenConnectAuthResponseSubmission.form:type_name -> daemon.OpenConnectAuthFormResponse - 77, // 51: daemon.OpenConnectAuthResponseSubmission.browser:type_name -> daemon.OpenConnectBrowserResult - 81, // 52: daemon.OpenVPNStatusUpdate.endpoints:type_name -> daemon.OpenVPNEndpointStatus - 83, // 53: daemon.OpenVPNEndpointStatus.challenge:type_name -> daemon.OpenVPNChallenge - 82, // 54: daemon.OpenVPNEndpointStatus.tunnelInfo:type_name -> daemon.OpenVPNTunnelInfo - 0, // 55: daemon.Log.Message.level:type_name -> daemon.LogLevel - 88, // 56: daemon.StartedService.GetVersion:input_type -> google.protobuf.Empty - 88, // 57: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty - 88, // 58: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty - 88, // 59: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty - 88, // 60: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty - 7, // 61: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest - 88, // 62: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty - 88, // 63: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty - 88, // 64: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty - 17, // 65: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode - 14, // 66: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest - 15, // 67: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest - 16, // 68: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest - 19, // 69: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest - 24, // 70: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest - 88, // 71: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty - 88, // 72: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty - 88, // 73: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty - 88, // 74: daemon.StartedService.SubscribeOutbounds:input_type -> google.protobuf.Empty - 29, // 75: daemon.StartedService.StartNetworkQualityTest:input_type -> daemon.NetworkQualityTestRequest - 31, // 76: daemon.StartedService.StartSTUNTest:input_type -> daemon.STUNTestRequest - 88, // 77: daemon.StartedService.SubscribeTailscaleStatus:input_type -> google.protobuf.Empty - 37, // 78: daemon.StartedService.StartTailscalePing:input_type -> daemon.TailscalePingRequest - 39, // 79: daemon.StartedService.SetTailscaleExitNode:input_type -> daemon.SetTailscaleExitNodeRequest - 40, // 80: daemon.StartedService.TailscaleLogout:input_type -> daemon.TailscaleLogoutRequest - 41, // 81: daemon.StartedService.StartTailscaleSSHSession:input_type -> daemon.TailscaleSSHClientMessage - 51, // 82: daemon.StartedService.ProvideUSBDevices:input_type -> daemon.USBProviderMessage - 88, // 83: daemon.StartedService.SubscribeUSBIPServerStatus:input_type -> google.protobuf.Empty - 88, // 84: daemon.StartedService.SubscribeOpenConnectStatus:input_type -> google.protobuf.Empty - 78, // 85: daemon.StartedService.SubmitOpenConnectAuthResponse:input_type -> daemon.OpenConnectAuthResponseSubmission - 79, // 86: daemon.StartedService.CancelOpenConnectAuthChallenge:input_type -> daemon.OpenConnectAuthChallengeCancel - 88, // 87: daemon.StartedService.SubscribeOpenVPNStatus:input_type -> google.protobuf.Empty - 84, // 88: daemon.StartedService.SubmitOpenVPNChallengeResponse:input_type -> daemon.OpenVPNChallengeSubmission - 85, // 89: daemon.StartedService.CancelOpenVPNChallenge:input_type -> daemon.OpenVPNChallengeCancel - 5, // 90: daemon.StartedService.GetVersion:output_type -> daemon.Version - 6, // 91: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus - 8, // 92: daemon.StartedService.SubscribeLog:output_type -> daemon.Log - 9, // 93: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel - 88, // 94: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty - 10, // 95: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status - 11, // 96: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups - 18, // 97: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus - 17, // 98: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode - 88, // 99: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty - 88, // 100: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty - 88, // 101: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty - 88, // 102: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty - 21, // 103: daemon.StartedService.SubscribeConnections:output_type -> daemon.ConnectionEvents - 88, // 104: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty - 88, // 105: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty - 25, // 106: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings - 27, // 107: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt - 28, // 108: daemon.StartedService.SubscribeOutbounds:output_type -> daemon.OutboundList - 30, // 109: daemon.StartedService.StartNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress - 32, // 110: daemon.StartedService.StartSTUNTest:output_type -> daemon.STUNTestProgress - 33, // 111: daemon.StartedService.SubscribeTailscaleStatus:output_type -> daemon.TailscaleStatusUpdate - 38, // 112: daemon.StartedService.StartTailscalePing:output_type -> daemon.TailscalePingResponse - 88, // 113: daemon.StartedService.SetTailscaleExitNode:output_type -> google.protobuf.Empty - 88, // 114: daemon.StartedService.TailscaleLogout:output_type -> google.protobuf.Empty - 45, // 115: daemon.StartedService.StartTailscaleSSHSession:output_type -> daemon.TailscaleSSHServerMessage - 52, // 116: daemon.StartedService.ProvideUSBDevices:output_type -> daemon.USBServerMessage - 63, // 117: daemon.StartedService.SubscribeUSBIPServerStatus:output_type -> daemon.USBIPServerStatusUpdate - 66, // 118: daemon.StartedService.SubscribeOpenConnectStatus:output_type -> daemon.OpenConnectStatusUpdate - 88, // 119: daemon.StartedService.SubmitOpenConnectAuthResponse:output_type -> google.protobuf.Empty - 88, // 120: daemon.StartedService.CancelOpenConnectAuthChallenge:output_type -> google.protobuf.Empty - 80, // 121: daemon.StartedService.SubscribeOpenVPNStatus:output_type -> daemon.OpenVPNStatusUpdate - 88, // 122: daemon.StartedService.SubmitOpenVPNChallengeResponse:output_type -> google.protobuf.Empty - 88, // 123: daemon.StartedService.CancelOpenVPNChallenge:output_type -> google.protobuf.Empty - 90, // [90:124] is the sub-list for method output_type - 56, // [56:90] is the sub-list for method input_type - 56, // [56:56] is the sub-list for extension type_name - 56, // [56:56] is the sub-list for extension extendee - 0, // [0:56] is the sub-list for field type_name + 4, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type + 105, // 1: daemon.Log.messages:type_name -> daemon.Log.Message + 0, // 2: daemon.DefaultLogLevel.level:type_name -> daemon.LogLevel + 12, // 3: daemon.Groups.group:type_name -> daemon.Group + 13, // 4: daemon.Group.items:type_name -> daemon.GroupItem + 1, // 5: daemon.ConnectionEvent.type:type_name -> daemon.ConnectionEventType + 22, // 6: daemon.ConnectionEvent.connection:type_name -> daemon.Connection + 20, // 7: daemon.ConnectionEvents.events:type_name -> daemon.ConnectionEvent + 23, // 8: daemon.Connection.processInfo:type_name -> daemon.ProcessInfo + 26, // 9: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning + 13, // 10: daemon.OutboundList.outbounds:type_name -> daemon.GroupItem + 34, // 11: daemon.TailscaleStatusUpdate.endpoints:type_name -> daemon.TailscaleEndpointStatus + 36, // 12: daemon.TailscaleEndpointStatus.self:type_name -> daemon.TailscalePeer + 35, // 13: daemon.TailscaleEndpointStatus.userGroups:type_name -> daemon.TailscaleUserGroup + 36, // 14: daemon.TailscaleEndpointStatus.exitNode:type_name -> daemon.TailscalePeer + 36, // 15: daemon.TailscaleUserGroup.peers:type_name -> daemon.TailscalePeer + 42, // 16: daemon.TailscaleSSHClientMessage.start:type_name -> daemon.TailscaleSSHStart + 43, // 17: daemon.TailscaleSSHClientMessage.input:type_name -> daemon.TailscaleSSHInput + 44, // 18: daemon.TailscaleSSHClientMessage.resize:type_name -> daemon.TailscaleSSHResize + 46, // 19: daemon.TailscaleSSHServerMessage.authBanner:type_name -> daemon.TailscaleSSHAuthBanner + 47, // 20: daemon.TailscaleSSHServerMessage.ready:type_name -> daemon.TailscaleSSHReady + 48, // 21: daemon.TailscaleSSHServerMessage.output:type_name -> daemon.TailscaleSSHOutput + 49, // 22: daemon.TailscaleSSHServerMessage.exit:type_name -> daemon.TailscaleSSHExit + 50, // 23: daemon.TailscaleSSHServerMessage.error:type_name -> daemon.TailscaleSSHError + 54, // 24: daemon.TaildropInbox.files:type_name -> daemon.TaildropFile + 55, // 25: daemon.TaildropInbox.receiving:type_name -> daemon.TaildropReceivingFile + 57, // 26: daemon.TaildropSendClientMessage.start:type_name -> daemon.TaildropSendStart + 59, // 27: daemon.TaildropSendClientMessage.chunk:type_name -> daemon.TaildropFileChunk + 60, // 28: daemon.TaildropSendClientMessage.fileDone:type_name -> daemon.TaildropFileDone + 58, // 29: daemon.TaildropSendStart.files:type_name -> daemon.TaildropOutgoingFile + 62, // 30: daemon.TaildropSendServerMessage.progress:type_name -> daemon.TaildropSendProgress + 70, // 31: daemon.USBProviderMessage.attach:type_name -> daemon.USBDeviceAttach + 72, // 32: daemon.USBProviderMessage.detach:type_name -> daemon.USBDeviceDetach + 75, // 33: daemon.USBProviderMessage.urbResponse:type_name -> daemon.USBURBResponse + 73, // 34: daemon.USBServerMessage.ready:type_name -> daemon.USBDeviceReady + 74, // 35: daemon.USBServerMessage.urbRequest:type_name -> daemon.USBURBRequest + 77, // 36: daemon.USBServerMessage.abort:type_name -> daemon.USBEndpointAbort + 78, // 37: daemon.USBServerMessage.error:type_name -> daemon.USBError + 71, // 38: daemon.USBDeviceDescriptor.interfaces:type_name -> daemon.USBInterface + 69, // 39: daemon.USBDeviceAttach.descriptor:type_name -> daemon.USBDeviceDescriptor + 76, // 40: daemon.USBURBRequest.isoPackets:type_name -> daemon.USBIsoPacket + 76, // 41: daemon.USBURBResponse.isoPackets:type_name -> daemon.USBIsoPacket + 80, // 42: daemon.USBIPServerStatusUpdate.servers:type_name -> daemon.USBIPServerStatus + 81, // 43: daemon.USBIPServerStatus.devices:type_name -> daemon.USBSharedDevice + 69, // 44: daemon.USBSharedDevice.descriptor:type_name -> daemon.USBDeviceDescriptor + 3, // 45: daemon.USBSharedDevice.backend:type_name -> daemon.USBBackend + 2, // 46: daemon.USBSharedDevice.state:type_name -> daemon.USBDeviceState + 83, // 47: daemon.OpenConnectStatusUpdate.endpoints:type_name -> daemon.OpenConnectEndpointStatus + 85, // 48: daemon.OpenConnectEndpointStatus.authChallenge:type_name -> daemon.OpenConnectAuthChallenge + 84, // 49: daemon.OpenConnectEndpointStatus.tunnelInfo:type_name -> daemon.OpenConnectTunnelInfo + 86, // 50: daemon.OpenConnectAuthChallenge.form:type_name -> daemon.OpenConnectAuthForm + 89, // 51: daemon.OpenConnectAuthChallenge.browser:type_name -> daemon.OpenConnectBrowserRequest + 87, // 52: daemon.OpenConnectAuthForm.fields:type_name -> daemon.OpenConnectAuthFormField + 88, // 53: daemon.OpenConnectAuthFormField.options:type_name -> daemon.OpenConnectAuthFormChoice + 106, // 54: daemon.OpenConnectAuthFormResponse.values:type_name -> daemon.OpenConnectAuthFormResponse.ValuesEntry + 90, // 55: daemon.OpenConnectBrowserResult.cookies:type_name -> daemon.OpenConnectBrowserCookie + 91, // 56: daemon.OpenConnectBrowserResult.headers:type_name -> daemon.OpenConnectBrowserHeader + 92, // 57: daemon.OpenConnectAuthResponseSubmission.form:type_name -> daemon.OpenConnectAuthFormResponse + 93, // 58: daemon.OpenConnectAuthResponseSubmission.browser:type_name -> daemon.OpenConnectBrowserResult + 97, // 59: daemon.OpenVPNStatusUpdate.endpoints:type_name -> daemon.OpenVPNEndpointStatus + 99, // 60: daemon.OpenVPNEndpointStatus.challenge:type_name -> daemon.OpenVPNChallenge + 98, // 61: daemon.OpenVPNEndpointStatus.tunnelInfo:type_name -> daemon.OpenVPNTunnelInfo + 103, // 62: daemon.NotificationEvent.send:type_name -> daemon.Notification + 104, // 63: daemon.NotificationEvent.cancel:type_name -> daemon.NotificationCancel + 0, // 64: daemon.Log.Message.level:type_name -> daemon.LogLevel + 107, // 65: daemon.StartedService.GetVersion:input_type -> google.protobuf.Empty + 107, // 66: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty + 107, // 67: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty + 107, // 68: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty + 107, // 69: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty + 7, // 70: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest + 107, // 71: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty + 107, // 72: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty + 107, // 73: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty + 17, // 74: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode + 14, // 75: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest + 15, // 76: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest + 16, // 77: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest + 19, // 78: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest + 24, // 79: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest + 107, // 80: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty + 107, // 81: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty + 107, // 82: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty + 107, // 83: daemon.StartedService.SubscribeOutbounds:input_type -> google.protobuf.Empty + 29, // 84: daemon.StartedService.StartNetworkQualityTest:input_type -> daemon.NetworkQualityTestRequest + 31, // 85: daemon.StartedService.StartSTUNTest:input_type -> daemon.STUNTestRequest + 107, // 86: daemon.StartedService.SubscribeTailscaleStatus:input_type -> google.protobuf.Empty + 37, // 87: daemon.StartedService.StartTailscalePing:input_type -> daemon.TailscalePingRequest + 39, // 88: daemon.StartedService.SetTailscaleExitNode:input_type -> daemon.SetTailscaleExitNodeRequest + 40, // 89: daemon.StartedService.TailscaleLogout:input_type -> daemon.TailscaleLogoutRequest + 41, // 90: daemon.StartedService.StartTailscaleSSHSession:input_type -> daemon.TailscaleSSHClientMessage + 51, // 91: daemon.StartedService.SubscribeTaildropInbox:input_type -> daemon.SubscribeTaildropInboxRequest + 52, // 92: daemon.StartedService.MarkTaildropInboxRead:input_type -> daemon.MarkTaildropInboxReadRequest + 56, // 93: daemon.StartedService.SendTaildropFiles:input_type -> daemon.TaildropSendClientMessage + 63, // 94: daemon.StartedService.DownloadTaildropFile:input_type -> daemon.DownloadTaildropFileRequest + 65, // 95: daemon.StartedService.DeleteTaildropFile:input_type -> daemon.DeleteTaildropFileRequest + 66, // 96: daemon.StartedService.CancelTaildropReceiving:input_type -> daemon.CancelTaildropReceivingRequest + 67, // 97: daemon.StartedService.ProvideUSBDevices:input_type -> daemon.USBProviderMessage + 107, // 98: daemon.StartedService.SubscribeUSBIPServerStatus:input_type -> google.protobuf.Empty + 107, // 99: daemon.StartedService.SubscribeOpenConnectStatus:input_type -> google.protobuf.Empty + 94, // 100: daemon.StartedService.SubmitOpenConnectAuthResponse:input_type -> daemon.OpenConnectAuthResponseSubmission + 95, // 101: daemon.StartedService.CancelOpenConnectAuthChallenge:input_type -> daemon.OpenConnectAuthChallengeCancel + 107, // 102: daemon.StartedService.SubscribeOpenVPNStatus:input_type -> google.protobuf.Empty + 100, // 103: daemon.StartedService.SubmitOpenVPNChallengeResponse:input_type -> daemon.OpenVPNChallengeSubmission + 101, // 104: daemon.StartedService.CancelOpenVPNChallenge:input_type -> daemon.OpenVPNChallengeCancel + 107, // 105: daemon.StartedService.SubscribeNotifications:input_type -> google.protobuf.Empty + 5, // 106: daemon.StartedService.GetVersion:output_type -> daemon.Version + 6, // 107: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus + 8, // 108: daemon.StartedService.SubscribeLog:output_type -> daemon.Log + 9, // 109: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel + 107, // 110: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty + 10, // 111: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status + 11, // 112: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups + 18, // 113: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus + 17, // 114: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode + 107, // 115: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty + 107, // 116: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty + 107, // 117: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty + 107, // 118: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty + 21, // 119: daemon.StartedService.SubscribeConnections:output_type -> daemon.ConnectionEvents + 107, // 120: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty + 107, // 121: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty + 25, // 122: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings + 27, // 123: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt + 28, // 124: daemon.StartedService.SubscribeOutbounds:output_type -> daemon.OutboundList + 30, // 125: daemon.StartedService.StartNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress + 32, // 126: daemon.StartedService.StartSTUNTest:output_type -> daemon.STUNTestProgress + 33, // 127: daemon.StartedService.SubscribeTailscaleStatus:output_type -> daemon.TailscaleStatusUpdate + 38, // 128: daemon.StartedService.StartTailscalePing:output_type -> daemon.TailscalePingResponse + 107, // 129: daemon.StartedService.SetTailscaleExitNode:output_type -> google.protobuf.Empty + 107, // 130: daemon.StartedService.TailscaleLogout:output_type -> google.protobuf.Empty + 45, // 131: daemon.StartedService.StartTailscaleSSHSession:output_type -> daemon.TailscaleSSHServerMessage + 53, // 132: daemon.StartedService.SubscribeTaildropInbox:output_type -> daemon.TaildropInbox + 107, // 133: daemon.StartedService.MarkTaildropInboxRead:output_type -> google.protobuf.Empty + 61, // 134: daemon.StartedService.SendTaildropFiles:output_type -> daemon.TaildropSendServerMessage + 64, // 135: daemon.StartedService.DownloadTaildropFile:output_type -> daemon.DownloadTaildropFileChunk + 107, // 136: daemon.StartedService.DeleteTaildropFile:output_type -> google.protobuf.Empty + 107, // 137: daemon.StartedService.CancelTaildropReceiving:output_type -> google.protobuf.Empty + 68, // 138: daemon.StartedService.ProvideUSBDevices:output_type -> daemon.USBServerMessage + 79, // 139: daemon.StartedService.SubscribeUSBIPServerStatus:output_type -> daemon.USBIPServerStatusUpdate + 82, // 140: daemon.StartedService.SubscribeOpenConnectStatus:output_type -> daemon.OpenConnectStatusUpdate + 107, // 141: daemon.StartedService.SubmitOpenConnectAuthResponse:output_type -> google.protobuf.Empty + 107, // 142: daemon.StartedService.CancelOpenConnectAuthChallenge:output_type -> google.protobuf.Empty + 96, // 143: daemon.StartedService.SubscribeOpenVPNStatus:output_type -> daemon.OpenVPNStatusUpdate + 107, // 144: daemon.StartedService.SubmitOpenVPNChallengeResponse:output_type -> google.protobuf.Empty + 107, // 145: daemon.StartedService.CancelOpenVPNChallenge:output_type -> google.protobuf.Empty + 102, // 146: daemon.StartedService.SubscribeNotifications:output_type -> daemon.NotificationEvent + 106, // [106:147] is the sub-list for method output_type + 65, // [65:106] is the sub-list for method input_type + 65, // [65:65] is the sub-list for extension type_name + 65, // [65:65] is the sub-list for extension extendee + 0, // [0:65] is the sub-list for field type_name } func init() { file_daemon_started_service_proto_init() } @@ -6906,32 +8255,45 @@ func file_daemon_started_service_proto_init() { (*TailscaleSSHServerMessage_Exit)(nil), (*TailscaleSSHServerMessage_Error)(nil), } - file_daemon_started_service_proto_msgTypes[46].OneofWrappers = []any{ + file_daemon_started_service_proto_msgTypes[51].OneofWrappers = []any{ + (*TaildropSendClientMessage_Start)(nil), + (*TaildropSendClientMessage_Chunk)(nil), + (*TaildropSendClientMessage_FileDone)(nil), + } + file_daemon_started_service_proto_msgTypes[56].OneofWrappers = []any{ + (*TaildropSendServerMessage_Progress)(nil), + (*TaildropSendServerMessage_ReceivedBytes)(nil), + } + file_daemon_started_service_proto_msgTypes[62].OneofWrappers = []any{ (*USBProviderMessage_Attach)(nil), (*USBProviderMessage_Detach)(nil), (*USBProviderMessage_UrbResponse)(nil), } - file_daemon_started_service_proto_msgTypes[47].OneofWrappers = []any{ + file_daemon_started_service_proto_msgTypes[63].OneofWrappers = []any{ (*USBServerMessage_Ready)(nil), (*USBServerMessage_UrbRequest)(nil), (*USBServerMessage_Abort)(nil), (*USBServerMessage_Error)(nil), } - file_daemon_started_service_proto_msgTypes[64].OneofWrappers = []any{ + file_daemon_started_service_proto_msgTypes[80].OneofWrappers = []any{ (*OpenConnectAuthChallenge_Form)(nil), (*OpenConnectAuthChallenge_Browser)(nil), } - file_daemon_started_service_proto_msgTypes[73].OneofWrappers = []any{ + file_daemon_started_service_proto_msgTypes[89].OneofWrappers = []any{ (*OpenConnectAuthResponseSubmission_Form)(nil), (*OpenConnectAuthResponseSubmission_Browser)(nil), } + file_daemon_started_service_proto_msgTypes[97].OneofWrappers = []any{ + (*NotificationEvent_Send)(nil), + (*NotificationEvent_Cancel)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc)), NumEnums: 5, - NumMessages: 83, + NumMessages: 102, NumExtensions: 0, NumServices: 1, }, diff --git a/daemon/started_service.proto b/daemon/started_service.proto index a03796f4..21116b30 100644 --- a/daemon/started_service.proto +++ b/daemon/started_service.proto @@ -36,6 +36,12 @@ service StartedService { rpc SetTailscaleExitNode(SetTailscaleExitNodeRequest) returns (google.protobuf.Empty) {} rpc TailscaleLogout(TailscaleLogoutRequest) returns (google.protobuf.Empty) {} rpc StartTailscaleSSHSession(stream TailscaleSSHClientMessage) returns (stream TailscaleSSHServerMessage) {} + rpc SubscribeTaildropInbox(SubscribeTaildropInboxRequest) returns (stream TaildropInbox) {} + rpc MarkTaildropInboxRead(MarkTaildropInboxReadRequest) returns (google.protobuf.Empty) {} + rpc SendTaildropFiles(stream TaildropSendClientMessage) returns (stream TaildropSendServerMessage) {} + rpc DownloadTaildropFile(DownloadTaildropFileRequest) returns (stream DownloadTaildropFileChunk) {} + rpc DeleteTaildropFile(DeleteTaildropFileRequest) returns (google.protobuf.Empty) {} + rpc CancelTaildropReceiving(CancelTaildropReceivingRequest) returns (google.protobuf.Empty) {} rpc ProvideUSBDevices(stream USBProviderMessage) returns (stream USBServerMessage) {} rpc SubscribeUSBIPServerStatus(google.protobuf.Empty) returns (stream USBIPServerStatusUpdate) {} rpc SubscribeOpenConnectStatus(google.protobuf.Empty) returns (stream OpenConnectStatusUpdate) {} @@ -44,6 +50,7 @@ service StartedService { rpc SubscribeOpenVPNStatus(google.protobuf.Empty) returns (stream OpenVPNStatusUpdate) {} rpc SubmitOpenVPNChallengeResponse(OpenVPNChallengeSubmission) returns (google.protobuf.Empty) {} rpc CancelOpenVPNChallenge(OpenVPNChallengeCancel) returns (google.protobuf.Empty) {} + rpc SubscribeNotifications(google.protobuf.Empty) returns (stream NotificationEvent) {} } message Version { @@ -282,6 +289,10 @@ message TailscaleEndpointStatus { repeated TailscaleUserGroup userGroups = 8; TailscalePeer exitNode = 9; bool keyAuth = 10; + bool canShareFiles = 11; + int32 waitingFileCount = 12; + int32 receivingFileCount = 13; + int32 unreadFileCount = 14; } message TailscaleUserGroup { @@ -309,6 +320,7 @@ message TailscalePeer { repeated string sshHostKeys = 14; bool shareeNode = 15; int64 lastSeen = 16; + bool canReceiveFiles = 17; } message TailscalePingRequest { @@ -323,6 +335,7 @@ message TailscalePingResponse { int32 derpRegionID = 4; string derpRegionCode = 5; string error = 6; + string peerRelay = 7; } message SetTailscaleExitNodeRequest { @@ -397,6 +410,94 @@ message TailscaleSSHError { string message = 1; } +message SubscribeTaildropInboxRequest { + string endpointTag = 1; +} + +message MarkTaildropInboxReadRequest { + string endpointTag = 1; +} + +message TaildropInbox { + string endpointTag = 1; + repeated TaildropFile files = 2; + repeated TaildropReceivingFile receiving = 3; +} + +message TaildropFile { + string name = 1; + int64 size = 2; + string senderName = 3; + int64 modifiedAt = 4; +} + +message TaildropReceivingFile { + string name = 1; + int64 size = 2; + int64 receivedBytes = 3; + string senderID = 4; + string senderName = 5; +} + +message TaildropSendClientMessage { + oneof message { + TaildropSendStart start = 1; + TaildropFileChunk chunk = 2; + TaildropFileDone fileDone = 3; + } +} + +message TaildropSendStart { + string endpointTag = 1; + string peerStableID = 2; + repeated TaildropOutgoingFile files = 3; +} + +message TaildropOutgoingFile { + string name = 1; + int64 size = 2; +} + +message TaildropFileChunk { + bytes data = 1; +} + +message TaildropFileDone {} + +message TaildropSendServerMessage { + oneof message { + TaildropSendProgress progress = 1; + int64 receivedBytes = 2; + } +} + +message TaildropSendProgress { + int32 fileIndex = 1; + int64 sentBytes = 2; + bool fileCompleted = 3; +} + +message DownloadTaildropFileRequest { + string endpointTag = 1; + string name = 2; +} + +message DownloadTaildropFileChunk { + int64 size = 1; + bytes data = 2; +} + +message DeleteTaildropFileRequest { + string endpointTag = 1; + string name = 2; +} + +message CancelTaildropReceivingRequest { + string endpointTag = 1; + string senderID = 2; + string name = 3; +} + message USBProviderMessage { oneof message { USBDeviceAttach attach = 1; @@ -670,3 +771,25 @@ message OpenVPNChallengeCancel { string endpointTag = 1; string challengeID = 2; } + +message NotificationEvent { + oneof event { + Notification send = 1; + NotificationCancel cancel = 2; + } +} + +message Notification { + string identifier = 1; + string typeName = 2; + int32 typeID = 3; + string title = 4; + string subtitle = 5; + string body = 6; + string openURL = 7; +} + +message NotificationCancel { + string identifier = 1; + int32 typeID = 2; +} diff --git a/daemon/started_service_grpc.pb.go b/daemon/started_service_grpc.pb.go index 405c8368..4a9fc458 100644 --- a/daemon/started_service_grpc.pb.go +++ b/daemon/started_service_grpc.pb.go @@ -41,6 +41,12 @@ const ( StartedService_SetTailscaleExitNode_FullMethodName = "/daemon.StartedService/SetTailscaleExitNode" StartedService_TailscaleLogout_FullMethodName = "/daemon.StartedService/TailscaleLogout" StartedService_StartTailscaleSSHSession_FullMethodName = "/daemon.StartedService/StartTailscaleSSHSession" + StartedService_SubscribeTaildropInbox_FullMethodName = "/daemon.StartedService/SubscribeTaildropInbox" + StartedService_MarkTaildropInboxRead_FullMethodName = "/daemon.StartedService/MarkTaildropInboxRead" + StartedService_SendTaildropFiles_FullMethodName = "/daemon.StartedService/SendTaildropFiles" + StartedService_DownloadTaildropFile_FullMethodName = "/daemon.StartedService/DownloadTaildropFile" + StartedService_DeleteTaildropFile_FullMethodName = "/daemon.StartedService/DeleteTaildropFile" + StartedService_CancelTaildropReceiving_FullMethodName = "/daemon.StartedService/CancelTaildropReceiving" StartedService_ProvideUSBDevices_FullMethodName = "/daemon.StartedService/ProvideUSBDevices" StartedService_SubscribeUSBIPServerStatus_FullMethodName = "/daemon.StartedService/SubscribeUSBIPServerStatus" StartedService_SubscribeOpenConnectStatus_FullMethodName = "/daemon.StartedService/SubscribeOpenConnectStatus" @@ -49,6 +55,7 @@ const ( StartedService_SubscribeOpenVPNStatus_FullMethodName = "/daemon.StartedService/SubscribeOpenVPNStatus" StartedService_SubmitOpenVPNChallengeResponse_FullMethodName = "/daemon.StartedService/SubmitOpenVPNChallengeResponse" StartedService_CancelOpenVPNChallenge_FullMethodName = "/daemon.StartedService/CancelOpenVPNChallenge" + StartedService_SubscribeNotifications_FullMethodName = "/daemon.StartedService/SubscribeNotifications" ) // StartedServiceClient is the client API for StartedService service. @@ -81,6 +88,12 @@ type StartedServiceClient interface { SetTailscaleExitNode(ctx context.Context, in *SetTailscaleExitNodeRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) TailscaleLogout(ctx context.Context, in *TailscaleLogoutRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) StartTailscaleSSHSession(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TailscaleSSHClientMessage, TailscaleSSHServerMessage], error) + SubscribeTaildropInbox(ctx context.Context, in *SubscribeTaildropInboxRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TaildropInbox], error) + MarkTaildropInboxRead(ctx context.Context, in *MarkTaildropInboxReadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + SendTaildropFiles(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TaildropSendClientMessage, TaildropSendServerMessage], error) + DownloadTaildropFile(ctx context.Context, in *DownloadTaildropFileRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[DownloadTaildropFileChunk], error) + DeleteTaildropFile(ctx context.Context, in *DeleteTaildropFileRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + CancelTaildropReceiving(ctx context.Context, in *CancelTaildropReceivingRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) ProvideUSBDevices(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[USBProviderMessage, USBServerMessage], error) SubscribeUSBIPServerStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[USBIPServerStatusUpdate], error) SubscribeOpenConnectStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OpenConnectStatusUpdate], error) @@ -89,6 +102,7 @@ type StartedServiceClient interface { SubscribeOpenVPNStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OpenVPNStatusUpdate], error) SubmitOpenVPNChallengeResponse(ctx context.Context, in *OpenVPNChallengeSubmission, opts ...grpc.CallOption) (*emptypb.Empty, error) CancelOpenVPNChallenge(ctx context.Context, in *OpenVPNChallengeCancel, opts ...grpc.CallOption) (*emptypb.Empty, error) + SubscribeNotifications(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NotificationEvent], error) } type startedServiceClient struct { @@ -461,9 +475,90 @@ func (c *startedServiceClient) StartTailscaleSSHSession(ctx context.Context, opt // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. type StartedService_StartTailscaleSSHSessionClient = grpc.BidiStreamingClient[TailscaleSSHClientMessage, TailscaleSSHServerMessage] +func (c *startedServiceClient) SubscribeTaildropInbox(ctx context.Context, in *SubscribeTaildropInboxRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TaildropInbox], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[12], StartedService_SubscribeTaildropInbox_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[SubscribeTaildropInboxRequest, TaildropInbox]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeTaildropInboxClient = grpc.ServerStreamingClient[TaildropInbox] + +func (c *startedServiceClient) MarkTaildropInboxRead(ctx context.Context, in *MarkTaildropInboxReadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_MarkTaildropInboxRead_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SendTaildropFiles(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TaildropSendClientMessage, TaildropSendServerMessage], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[13], StartedService_SendTaildropFiles_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[TaildropSendClientMessage, TaildropSendServerMessage]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SendTaildropFilesClient = grpc.BidiStreamingClient[TaildropSendClientMessage, TaildropSendServerMessage] + +func (c *startedServiceClient) DownloadTaildropFile(ctx context.Context, in *DownloadTaildropFileRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[DownloadTaildropFileChunk], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[14], StartedService_DownloadTaildropFile_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[DownloadTaildropFileRequest, DownloadTaildropFileChunk]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_DownloadTaildropFileClient = grpc.ServerStreamingClient[DownloadTaildropFileChunk] + +func (c *startedServiceClient) DeleteTaildropFile(ctx context.Context, in *DeleteTaildropFileRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_DeleteTaildropFile_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) CancelTaildropReceiving(ctx context.Context, in *CancelTaildropReceivingRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_CancelTaildropReceiving_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *startedServiceClient) ProvideUSBDevices(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[USBProviderMessage, USBServerMessage], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[12], StartedService_ProvideUSBDevices_FullMethodName, cOpts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[15], StartedService_ProvideUSBDevices_FullMethodName, cOpts...) if err != nil { return nil, err } @@ -476,7 +571,7 @@ type StartedService_ProvideUSBDevicesClient = grpc.BidiStreamingClient[USBProvid func (c *startedServiceClient) SubscribeUSBIPServerStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[USBIPServerStatusUpdate], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[13], StartedService_SubscribeUSBIPServerStatus_FullMethodName, cOpts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[16], StartedService_SubscribeUSBIPServerStatus_FullMethodName, cOpts...) if err != nil { return nil, err } @@ -495,7 +590,7 @@ type StartedService_SubscribeUSBIPServerStatusClient = grpc.ServerStreamingClien func (c *startedServiceClient) SubscribeOpenConnectStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OpenConnectStatusUpdate], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[14], StartedService_SubscribeOpenConnectStatus_FullMethodName, cOpts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[17], StartedService_SubscribeOpenConnectStatus_FullMethodName, cOpts...) if err != nil { return nil, err } @@ -534,7 +629,7 @@ func (c *startedServiceClient) CancelOpenConnectAuthChallenge(ctx context.Contex func (c *startedServiceClient) SubscribeOpenVPNStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OpenVPNStatusUpdate], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[15], StartedService_SubscribeOpenVPNStatus_FullMethodName, cOpts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[18], StartedService_SubscribeOpenVPNStatus_FullMethodName, cOpts...) if err != nil { return nil, err } @@ -571,6 +666,25 @@ func (c *startedServiceClient) CancelOpenVPNChallenge(ctx context.Context, in *O return out, nil } +func (c *startedServiceClient) SubscribeNotifications(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NotificationEvent], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[19], StartedService_SubscribeNotifications_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, NotificationEvent]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeNotificationsClient = grpc.ServerStreamingClient[NotificationEvent] + // StartedServiceServer is the server API for StartedService service. // All implementations must embed UnimplementedStartedServiceServer // for forward compatibility. @@ -601,6 +715,12 @@ type StartedServiceServer interface { SetTailscaleExitNode(context.Context, *SetTailscaleExitNodeRequest) (*emptypb.Empty, error) TailscaleLogout(context.Context, *TailscaleLogoutRequest) (*emptypb.Empty, error) StartTailscaleSSHSession(grpc.BidiStreamingServer[TailscaleSSHClientMessage, TailscaleSSHServerMessage]) error + SubscribeTaildropInbox(*SubscribeTaildropInboxRequest, grpc.ServerStreamingServer[TaildropInbox]) error + MarkTaildropInboxRead(context.Context, *MarkTaildropInboxReadRequest) (*emptypb.Empty, error) + SendTaildropFiles(grpc.BidiStreamingServer[TaildropSendClientMessage, TaildropSendServerMessage]) error + DownloadTaildropFile(*DownloadTaildropFileRequest, grpc.ServerStreamingServer[DownloadTaildropFileChunk]) error + DeleteTaildropFile(context.Context, *DeleteTaildropFileRequest) (*emptypb.Empty, error) + CancelTaildropReceiving(context.Context, *CancelTaildropReceivingRequest) (*emptypb.Empty, error) ProvideUSBDevices(grpc.BidiStreamingServer[USBProviderMessage, USBServerMessage]) error SubscribeUSBIPServerStatus(*emptypb.Empty, grpc.ServerStreamingServer[USBIPServerStatusUpdate]) error SubscribeOpenConnectStatus(*emptypb.Empty, grpc.ServerStreamingServer[OpenConnectStatusUpdate]) error @@ -609,6 +729,7 @@ type StartedServiceServer interface { SubscribeOpenVPNStatus(*emptypb.Empty, grpc.ServerStreamingServer[OpenVPNStatusUpdate]) error SubmitOpenVPNChallengeResponse(context.Context, *OpenVPNChallengeSubmission) (*emptypb.Empty, error) CancelOpenVPNChallenge(context.Context, *OpenVPNChallengeCancel) (*emptypb.Empty, error) + SubscribeNotifications(*emptypb.Empty, grpc.ServerStreamingServer[NotificationEvent]) error mustEmbedUnimplementedStartedServiceServer() } @@ -723,6 +844,30 @@ func (UnimplementedStartedServiceServer) StartTailscaleSSHSession(grpc.BidiStrea return status.Error(codes.Unimplemented, "method StartTailscaleSSHSession not implemented") } +func (UnimplementedStartedServiceServer) SubscribeTaildropInbox(*SubscribeTaildropInboxRequest, grpc.ServerStreamingServer[TaildropInbox]) error { + return status.Error(codes.Unimplemented, "method SubscribeTaildropInbox not implemented") +} + +func (UnimplementedStartedServiceServer) MarkTaildropInboxRead(context.Context, *MarkTaildropInboxReadRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method MarkTaildropInboxRead not implemented") +} + +func (UnimplementedStartedServiceServer) SendTaildropFiles(grpc.BidiStreamingServer[TaildropSendClientMessage, TaildropSendServerMessage]) error { + return status.Error(codes.Unimplemented, "method SendTaildropFiles not implemented") +} + +func (UnimplementedStartedServiceServer) DownloadTaildropFile(*DownloadTaildropFileRequest, grpc.ServerStreamingServer[DownloadTaildropFileChunk]) error { + return status.Error(codes.Unimplemented, "method DownloadTaildropFile not implemented") +} + +func (UnimplementedStartedServiceServer) DeleteTaildropFile(context.Context, *DeleteTaildropFileRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteTaildropFile not implemented") +} + +func (UnimplementedStartedServiceServer) CancelTaildropReceiving(context.Context, *CancelTaildropReceivingRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method CancelTaildropReceiving not implemented") +} + func (UnimplementedStartedServiceServer) ProvideUSBDevices(grpc.BidiStreamingServer[USBProviderMessage, USBServerMessage]) error { return status.Error(codes.Unimplemented, "method ProvideUSBDevices not implemented") } @@ -754,6 +899,10 @@ func (UnimplementedStartedServiceServer) SubmitOpenVPNChallengeResponse(context. func (UnimplementedStartedServiceServer) CancelOpenVPNChallenge(context.Context, *OpenVPNChallengeCancel) (*emptypb.Empty, error) { return nil, status.Error(codes.Unimplemented, "method CancelOpenVPNChallenge not implemented") } + +func (UnimplementedStartedServiceServer) SubscribeNotifications(*emptypb.Empty, grpc.ServerStreamingServer[NotificationEvent]) error { + return status.Error(codes.Unimplemented, "method SubscribeNotifications not implemented") +} func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {} func (UnimplementedStartedServiceServer) testEmbeddedByValue() {} @@ -1155,6 +1304,89 @@ func _StartedService_StartTailscaleSSHSession_Handler(srv interface{}, stream gr // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. type StartedService_StartTailscaleSSHSessionServer = grpc.BidiStreamingServer[TailscaleSSHClientMessage, TailscaleSSHServerMessage] +func _StartedService_SubscribeTaildropInbox_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SubscribeTaildropInboxRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeTaildropInbox(m, &grpc.GenericServerStream[SubscribeTaildropInboxRequest, TaildropInbox]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeTaildropInboxServer = grpc.ServerStreamingServer[TaildropInbox] + +func _StartedService_MarkTaildropInboxRead_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MarkTaildropInboxReadRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).MarkTaildropInboxRead(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_MarkTaildropInboxRead_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).MarkTaildropInboxRead(ctx, req.(*MarkTaildropInboxReadRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SendTaildropFiles_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(StartedServiceServer).SendTaildropFiles(&grpc.GenericServerStream[TaildropSendClientMessage, TaildropSendServerMessage]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SendTaildropFilesServer = grpc.BidiStreamingServer[TaildropSendClientMessage, TaildropSendServerMessage] + +func _StartedService_DownloadTaildropFile_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(DownloadTaildropFileRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).DownloadTaildropFile(m, &grpc.GenericServerStream[DownloadTaildropFileRequest, DownloadTaildropFileChunk]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_DownloadTaildropFileServer = grpc.ServerStreamingServer[DownloadTaildropFileChunk] + +func _StartedService_DeleteTaildropFile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteTaildropFileRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).DeleteTaildropFile(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_DeleteTaildropFile_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).DeleteTaildropFile(ctx, req.(*DeleteTaildropFileRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_CancelTaildropReceiving_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CancelTaildropReceivingRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).CancelTaildropReceiving(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_CancelTaildropReceiving_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).CancelTaildropReceiving(ctx, req.(*CancelTaildropReceivingRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _StartedService_ProvideUSBDevices_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(StartedServiceServer).ProvideUSBDevices(&grpc.GenericServerStream[USBProviderMessage, USBServerMessage]{ServerStream: stream}) } @@ -1267,6 +1499,17 @@ func _StartedService_CancelOpenVPNChallenge_Handler(srv interface{}, ctx context return interceptor(ctx, in, info, handler) } +func _StartedService_SubscribeNotifications_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeNotifications(m, &grpc.GenericServerStream[emptypb.Empty, NotificationEvent]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeNotificationsServer = grpc.ServerStreamingServer[NotificationEvent] + // StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1330,6 +1573,18 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{ MethodName: "TailscaleLogout", Handler: _StartedService_TailscaleLogout_Handler, }, + { + MethodName: "MarkTaildropInboxRead", + Handler: _StartedService_MarkTaildropInboxRead_Handler, + }, + { + MethodName: "DeleteTaildropFile", + Handler: _StartedService_DeleteTaildropFile_Handler, + }, + { + MethodName: "CancelTaildropReceiving", + Handler: _StartedService_CancelTaildropReceiving_Handler, + }, { MethodName: "SubmitOpenConnectAuthResponse", Handler: _StartedService_SubmitOpenConnectAuthResponse_Handler, @@ -1409,6 +1664,22 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{ ServerStreams: true, ClientStreams: true, }, + { + StreamName: "SubscribeTaildropInbox", + Handler: _StartedService_SubscribeTaildropInbox_Handler, + ServerStreams: true, + }, + { + StreamName: "SendTaildropFiles", + Handler: _StartedService_SendTaildropFiles_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "DownloadTaildropFile", + Handler: _StartedService_DownloadTaildropFile_Handler, + ServerStreams: true, + }, { StreamName: "ProvideUSBDevices", Handler: _StartedService_ProvideUSBDevices_Handler, @@ -1430,6 +1701,11 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{ Handler: _StartedService_SubscribeOpenVPNStatus_Handler, ServerStreams: true, }, + { + StreamName: "SubscribeNotifications", + Handler: _StartedService_SubscribeNotifications_Handler, + ServerStreams: true, + }, }, Metadata: "daemon/started_service.proto", } diff --git a/daemon/started_service_taildrop.go b/daemon/started_service_taildrop.go new file mode 100644 index 00000000..66db972a --- /dev/null +++ b/daemon/started_service_taildrop.go @@ -0,0 +1,394 @@ +package daemon + +import ( + "context" + "errors" + "io" + "os" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/service" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +const ( + // gRPC splits DATA frames at the unexported transport.http2MaxFrameLen (16384), + // counting the 5-byte message prefix. A chunk message that stays inside one frame + // arrives as a single mem.Buffer, so BufferSlice.MaterializeToBuffer returns it + // without taking a pooled buffer, and codecV2.Marshal fits the 16 KiB pool tier. + TaildropChunkSize = 16*1024 - 64 + + TaildropProgressMinInterval = 200 * time.Millisecond + + // Kept well below the sender's flow control window, so a sender that stops at + // the window edge always has an acknowledgement pending. + TaildropReceiveAckInterval = 1 << 20 +) + +func resolveTailscaleProvider(instance *Instance, tag string) (adapter.TailscaleEndpoint, string, error) { + if tag != "" { + endpoint, err := resolveTailscaleEndpoint(instance, tag) + if err != nil { + return nil, "", err + } + provider, loaded := endpoint.(adapter.TailscaleEndpoint) + if !loaded { + return nil, "", status.Error(codes.FailedPrecondition, "endpoint does not support tailscale") + } + return provider, endpoint.Tag(), nil + } + endpointManager := service.FromContext[adapter.EndpointManager](instance.ctx) + if endpointManager == nil { + return nil, "", status.Error(codes.FailedPrecondition, "endpoint manager not available") + } + for _, endpoint := range endpointManager.Endpoints() { + if endpoint.Type() != C.TypeTailscale { + continue + } + provider, loaded := endpoint.(adapter.TailscaleEndpoint) + if loaded { + return provider, endpoint.Tag(), nil + } + } + return nil, "", status.Error(codes.NotFound, "no Tailscale endpoint found") +} + +func taildropError(err error) error { + if errors.Is(err, os.ErrNotExist) { + return status.Error(codes.NotFound, err.Error()) + } + return err +} + +func (s *StartedService) SubscribeTaildropInbox( + request *SubscribeTaildropInboxRequest, + server grpc.ServerStreamingServer[TaildropInbox], +) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + return s.followInstance(server.Context(), func(ctx context.Context, instance *Instance) error { + var ( + provider adapter.TailscaleEndpoint + endpointTag string + ) + if instance != nil { + var resolveErr error + provider, endpointTag, resolveErr = resolveTailscaleProvider(instance, request.EndpointTag) + if resolveErr != nil && status.Code(resolveErr) != codes.NotFound { + return resolveErr + } + } + if provider == nil { + sendErr := server.Send(&TaildropInbox{}) + if sendErr != nil { + return sendErr + } + <-ctx.Done() + return nil + } + subscribeCtx, cancel := context.WithCancel(ctx) + defer cancel() + var sendErr error + subscribeErr := provider.SubscribeTaildropInbox(subscribeCtx, func(inbox *adapter.TaildropInbox) { + if sendErr != nil { + return + } + sendErr = server.Send(taildropInboxToProto(endpointTag, inbox)) + if sendErr != nil { + cancel() + } + }) + if sendErr != nil { + return sendErr + } + if subscribeErr != nil && !errors.Is(subscribeErr, context.Canceled) { + if !errors.Is(subscribeErr, os.ErrClosed) { + return taildropError(subscribeErr) + } + sendErr = server.Send(&TaildropInbox{EndpointTag: endpointTag}) + if sendErr != nil { + return sendErr + } + } + <-ctx.Done() + return nil + }) +} + +func taildropInboxToProto(endpointTag string, inbox *adapter.TaildropInbox) *TaildropInbox { + result := &TaildropInbox{EndpointTag: endpointTag} + for _, file := range inbox.Files { + result.Files = append(result.Files, &TaildropFile{ + Name: file.Name, + Size: file.Size, + SenderName: file.SenderName, + ModifiedAt: file.ModifiedAt, + }) + } + for _, file := range inbox.Receiving { + result.Receiving = append(result.Receiving, &TaildropReceivingFile{ + Name: file.Name, + Size: file.Size, + ReceivedBytes: file.ReceivedBytes, + SenderID: file.SenderID, + SenderName: file.SenderName, + }) + } + return result +} + +func (s *StartedService) MarkTaildropInboxRead(ctx context.Context, request *MarkTaildropInboxReadRequest) (*emptypb.Empty, error) { + err := s.waitForStarted(ctx) + if err != nil { + return nil, err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + + provider, _, err := resolveTailscaleProvider(boxService, request.EndpointTag) + if err != nil { + return nil, err + } + err = provider.MarkTaildropInboxRead() + if err != nil { + return nil, taildropError(err) + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) SendTaildropFiles( + server grpc.BidiStreamingServer[TaildropSendClientMessage, TaildropSendServerMessage], +) error { + streamCtx := server.Context() + err := s.waitForStarted(streamCtx) + if err != nil { + return err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + + firstMessage, err := server.Recv() + if err != nil { + return err + } + start := firstMessage.GetStart() + if start == nil { + return status.Error(codes.InvalidArgument, "expected start message") + } + if len(start.Files) == 0 { + return status.Error(codes.InvalidArgument, "no files to send") + } + provider, _, err := resolveTailscaleProvider(boxService, start.EndpointTag) + if err != nil { + return err + } + + var sendAccess sync.Mutex + sendMessage := func(message *TaildropSendServerMessage) { + sendAccess.Lock() + defer sendAccess.Unlock() + _ = server.Send(message) + } + + chunkSource := &taildropChunkReader{server: server} + chunkSource.reportReceived = func(receivedBytes int64) { + sendMessage(&TaildropSendServerMessage{ + Message: &TaildropSendServerMessage_ReceivedBytes{ReceivedBytes: receivedBytes}, + }) + } + for index, file := range start.Files { + fileIndex := int32(index) + chunkSource.beginFile() + var lastProgress time.Time + err = provider.SendTaildropFile(streamCtx, start.PeerStableID, file.Name, file.Size, chunkSource, func(sentBytes int64) { + now := time.Now() + if now.Sub(lastProgress) < TaildropProgressMinInterval { + return + } + lastProgress = now + sendMessage(&TaildropSendServerMessage{ + Message: &TaildropSendServerMessage_Progress{ + Progress: &TaildropSendProgress{FileIndex: fileIndex, SentBytes: sentBytes}, + }, + }) + }) + if err != nil { + return taildropError(err) + } + err = chunkSource.finishFile(file.Size) + if err != nil { + return err + } + sendMessage(&TaildropSendServerMessage{ + Message: &TaildropSendServerMessage_Progress{ + Progress: &TaildropSendProgress{FileIndex: fileIndex, SentBytes: chunkSource.fileReceived, FileCompleted: true}, + }, + }) + } + return nil +} + +type taildropChunkReader struct { + server grpc.BidiStreamingServer[TaildropSendClientMessage, TaildropSendServerMessage] + buffer []byte + fileDone bool + fileReceived int64 + reportReceived func(receivedBytes int64) + received int64 + acknowledged int64 +} + +func (r *taildropChunkReader) beginFile() { + r.fileDone = false + r.fileReceived = 0 +} + +func (r *taildropChunkReader) Read(destination []byte) (int, error) { + for len(r.buffer) == 0 { + if r.fileDone { + return 0, io.EOF + } + message, err := r.server.Recv() + if err != nil { + return 0, err + } + switch content := message.Message.(type) { + case *TaildropSendClientMessage_Chunk: + r.buffer = content.Chunk.Data + case *TaildropSendClientMessage_FileDone: + r.fileDone = true + default: + return 0, status.Error(codes.InvalidArgument, "expected chunk message") + } + } + n := copy(destination, r.buffer) + r.buffer = r.buffer[n:] + r.fileReceived += int64(n) + r.received += int64(n) + if r.reportReceived != nil && r.received-r.acknowledged >= TaildropReceiveAckInterval { + r.acknowledged = r.received + r.reportReceived(r.received) + } + return n, nil +} + +func (r *taildropChunkReader) finishFile(declaredSize int64) error { + for !r.fileDone { + if len(r.buffer) > 0 { + return status.Error(codes.InvalidArgument, "file changed while sending") + } + message, err := r.server.Recv() + if err != nil { + return err + } + switch content := message.Message.(type) { + case *TaildropSendClientMessage_Chunk: + r.buffer = content.Chunk.Data + case *TaildropSendClientMessage_FileDone: + r.fileDone = true + default: + return status.Error(codes.InvalidArgument, "expected chunk message") + } + } + if len(r.buffer) > 0 { + return status.Error(codes.InvalidArgument, "file changed while sending") + } + if declaredSize >= 0 && r.fileReceived != declaredSize { + return status.Error(codes.InvalidArgument, "file changed while sending") + } + return nil +} + +func (s *StartedService) DownloadTaildropFile( + request *DownloadTaildropFileRequest, + server grpc.ServerStreamingServer[DownloadTaildropFileChunk], +) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + + provider, _, err := resolveTailscaleProvider(boxService, request.EndpointTag) + if err != nil { + return err + } + file, size, err := provider.OpenTaildropFile(request.Name) + if err != nil { + return taildropError(err) + } + defer file.Close() + err = server.Send(&DownloadTaildropFileChunk{Size: size}) + if err != nil { + return err + } + buffer := make([]byte, TaildropChunkSize) + for { + n, readErr := file.Read(buffer) + if n > 0 { + err = server.Send(&DownloadTaildropFileChunk{Data: buffer[:n]}) + if err != nil { + return err + } + } + if readErr == io.EOF { + return nil + } + if readErr != nil { + return readErr + } + } +} + +func (s *StartedService) DeleteTaildropFile(ctx context.Context, request *DeleteTaildropFileRequest) (*emptypb.Empty, error) { + err := s.waitForStarted(ctx) + if err != nil { + return nil, err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + + provider, _, err := resolveTailscaleProvider(boxService, request.EndpointTag) + if err != nil { + return nil, err + } + err = provider.DeleteTaildropFile(request.Name) + if err != nil { + return nil, taildropError(err) + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) CancelTaildropReceiving(ctx context.Context, request *CancelTaildropReceivingRequest) (*emptypb.Empty, error) { + err := s.waitForStarted(ctx) + if err != nil { + return nil, err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + + provider, _, err := resolveTailscaleProvider(boxService, request.EndpointTag) + if err != nil { + return nil, err + } + err = provider.CancelTaildropReceiving(request.SenderID, request.Name) + if err != nil { + return nil, taildropError(err) + } + return &emptypb.Empty{}, nil +} diff --git a/docs/configuration/endpoint/tailscale.md b/docs/configuration/endpoint/tailscale.md index 1556367b..00d40154 100644 --- a/docs/configuration/endpoint/tailscale.md +++ b/docs/configuration/endpoint/tailscale.md @@ -5,7 +5,8 @@ icon: material/new-box !!! quote "Changes in sing-box 1.14.0" :material-plus: [listen_port](#listen_port) - :material-plus: [ssh_server](#ssh_server) + :material-plus: [ssh_server](#ssh_server) + :material-plus: [taildrop_directory](#taildrop_directory) !!! quote "Changes in sing-box 1.13.0" @@ -43,6 +44,7 @@ icon: material/new-box "system_interface_mtu": 0, "udp_timeout": "5m", "ssh_server": false, + "taildrop_directory": "", ... // Dial Fields } @@ -211,6 +213,17 @@ Refuse the SFTP subsystem. Refuse local and remote TCP and Unix-socket forwarding, including SSH agent forwarding. +#### taildrop_directory + +!!! question "Since sing-box 1.14.0" + +The directory where files received from tailnet peers are stored. + +Relative paths are resolved against the working directory, as [state_directory](#state_directory) +is. + +`Taildrop` is used by default. + ### Dial Fields !!! note diff --git a/docs/configuration/endpoint/tailscale.zh.md b/docs/configuration/endpoint/tailscale.zh.md index 52af1aef..adda6425 100644 --- a/docs/configuration/endpoint/tailscale.zh.md +++ b/docs/configuration/endpoint/tailscale.zh.md @@ -5,7 +5,8 @@ icon: material/new-box !!! quote "sing-box 1.14.0 中的更改" :material-plus: [listen_port](#listen_port) - :material-plus: [ssh_server](#ssh_server) + :material-plus: [ssh_server](#ssh_server) + :material-plus: [taildrop_directory](#taildrop_directory) !!! quote "sing-box 1.13.0 中的更改" @@ -43,6 +44,7 @@ icon: material/new-box "system_interface_mtu": 0, "udp_timeout": "5m", "ssh_server": false, + "taildrop_directory": "", ... // 拨号字段 } @@ -210,6 +212,16 @@ UDP NAT 过期时间。 拒绝本地和远程的 TCP 与 Unix 套接字转发,包括 SSH agent 转发。 +#### taildrop_directory + +!!! question "自 sing-box 1.14.0 起" + +存储从 tailnet 对等节点接收到的文件的目录。 + +相对路径基于工作目录解析,与 [state_directory](#state_directory) 相同。 + +默认使用 `Taildrop`。 + ### 拨号字段 !!! note diff --git a/docs/schema.json b/docs/schema.json index d5b95eea..888dca5c 100644 --- a/docs/schema.json +++ b/docs/schema.json @@ -4588,6 +4588,9 @@ "additionalProperties": false } ] + }, + "taildrop_directory": { + "type": "string" } }, "required": [ diff --git a/experimental/boxdd/cmd_service_linux.go b/experimental/boxdd/cmd_service_linux.go index 1161c105..d9f4d7c3 100644 --- a/experimental/boxdd/cmd_service_linux.go +++ b/experimental/boxdd/cmd_service_linux.go @@ -64,7 +64,9 @@ func serviceSetInsecureMode(value string) error { if err != nil { return E.Cause(err, "validate working directory") } - return saveSecuritySettings(directory, securitySettings{InsecureModeEnabled: enabled}) + return updateDaemonSettings(directory, func(settings *daemonSettings) { + settings.InsecureModeEnabled = enabled + }) } func validateProtectedLinuxDirectory(directory string) error { diff --git a/experimental/boxdd/cmd_service_windows.go b/experimental/boxdd/cmd_service_windows.go index c890321f..a65b6160 100644 --- a/experimental/boxdd/cmd_service_windows.go +++ b/experimental/boxdd/cmd_service_windows.go @@ -93,7 +93,9 @@ func serviceSetInsecureMode(value string) error { if err != nil { return E.Cause(err, "validate working directory") } - return saveSecuritySettings(directory, securitySettings{InsecureModeEnabled: enabled}) + return updateDaemonSettings(directory, func(settings *daemonSettings) { + settings.InsecureModeEnabled = enabled + }) } func installedServiceWorkingDirectory() (string, error) { diff --git a/experimental/boxdd/desktop_service.go b/experimental/boxdd/desktop_service.go index ef92deba..24759838 100644 --- a/experimental/boxdd/desktop_service.go +++ b/experimental/boxdd/desktop_service.go @@ -7,6 +7,7 @@ import ( "path/filepath" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/locale" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/tailscale/atomicfile" @@ -258,7 +259,9 @@ func (s *desktopService) SetInsecureModeEnabled(ctx context.Context, request *Se return nil, err } wasEnabled := s.daemon.insecureModeEnabled() - err = saveSecuritySettings(workingDirectory, securitySettings{InsecureModeEnabled: false}) + err = updateDaemonSettings(workingDirectory, func(settings *daemonSettings) { + settings.InsecureModeEnabled = false + }) if err != nil { return nil, err } @@ -276,6 +279,23 @@ func (s *desktopService) SetInsecureModeEnabled(ctx context.Context, request *Se return &emptypb.Empty{}, nil } +func (s *desktopService) SetLocale(ctx context.Context, request *SetLocaleRequest) (*emptypb.Empty, error) { + _, err := peerIdentityFromContext(ctx) + if err != nil { + return nil, err + } + if !locale.Set(request.Locale) { + return nil, status.Error(codes.InvalidArgument, "unsupported locale: "+request.Locale) + } + err = updateDaemonSettings(workingDirectory, func(settings *daemonSettings) { + settings.Locale = request.Locale + }) + if err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} + func (d *Daemon) cleanFailedStartLocked(ownerUserID string, options startOptions, startError error) error { var platformError error if d.platform != nil { diff --git a/experimental/boxdd/desktop_service.pb.go b/experimental/boxdd/desktop_service.pb.go index c9eaf014..eeaf0a32 100644 --- a/experimental/boxdd/desktop_service.pb.go +++ b/experimental/boxdd/desktop_service.pb.go @@ -1519,6 +1519,50 @@ func (x *SetInsecureModeEnabledRequest) GetEnabled() bool { return false } +type SetLocaleRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Locale string `protobuf:"bytes,1,opt,name=locale,proto3" json:"locale,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetLocaleRequest) Reset() { + *x = SetLocaleRequest{} + mi := &file_experimental_boxdd_desktop_service_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetLocaleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetLocaleRequest) ProtoMessage() {} + +func (x *SetLocaleRequest) ProtoReflect() protoreflect.Message { + mi := &file_experimental_boxdd_desktop_service_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetLocaleRequest.ProtoReflect.Descriptor instead. +func (*SetLocaleRequest) Descriptor() ([]byte, []int) { + return file_experimental_boxdd_desktop_service_proto_rawDescGZIP(), []int{25} +} + +func (x *SetLocaleRequest) GetLocale() string { + if x != nil { + return x.Locale + } + return "" +} + type InstallUpdateRequest struct { state protoimpl.MessageState `protogen:"open.v1"` InstallerPath string `protobuf:"bytes,1,opt,name=installer_path,json=installerPath,proto3" json:"installer_path,omitempty"` @@ -1528,7 +1572,7 @@ type InstallUpdateRequest struct { func (x *InstallUpdateRequest) Reset() { *x = InstallUpdateRequest{} - mi := &file_experimental_boxdd_desktop_service_proto_msgTypes[25] + mi := &file_experimental_boxdd_desktop_service_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1540,7 +1584,7 @@ func (x *InstallUpdateRequest) String() string { func (*InstallUpdateRequest) ProtoMessage() {} func (x *InstallUpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_experimental_boxdd_desktop_service_proto_msgTypes[25] + mi := &file_experimental_boxdd_desktop_service_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1553,7 +1597,7 @@ func (x *InstallUpdateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use InstallUpdateRequest.ProtoReflect.Descriptor instead. func (*InstallUpdateRequest) Descriptor() ([]byte, []int) { - return file_experimental_boxdd_desktop_service_proto_rawDescGZIP(), []int{25} + return file_experimental_boxdd_desktop_service_proto_rawDescGZIP(), []int{26} } func (x *InstallUpdateRequest) GetInstallerPath() string { @@ -1572,7 +1616,7 @@ type InstallUpdateResponse struct { func (x *InstallUpdateResponse) Reset() { *x = InstallUpdateResponse{} - mi := &file_experimental_boxdd_desktop_service_proto_msgTypes[26] + mi := &file_experimental_boxdd_desktop_service_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1584,7 +1628,7 @@ func (x *InstallUpdateResponse) String() string { func (*InstallUpdateResponse) ProtoMessage() {} func (x *InstallUpdateResponse) ProtoReflect() protoreflect.Message { - mi := &file_experimental_boxdd_desktop_service_proto_msgTypes[26] + mi := &file_experimental_boxdd_desktop_service_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1597,7 +1641,7 @@ func (x *InstallUpdateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use InstallUpdateResponse.ProtoReflect.Descriptor instead. func (*InstallUpdateResponse) Descriptor() ([]byte, []int) { - return file_experimental_boxdd_desktop_service_proto_rawDescGZIP(), []int{26} + return file_experimental_boxdd_desktop_service_proto_rawDescGZIP(), []int{27} } func (x *InstallUpdateResponse) GetResult() InstallUpdateResult { @@ -1706,7 +1750,9 @@ const file_experimental_boxdd_desktop_service_proto_rawDesc = "" + "\tavailable\x18\x01 \x01(\bR\tavailable\x122\n" + "\x15insecure_mode_enabled\x18\x02 \x01(\bR\x13insecureModeEnabled\"9\n" + "\x1dSetInsecureModeEnabledRequest\x12\x18\n" + - "\aenabled\x18\x01 \x01(\bR\aenabled\"=\n" + + "\aenabled\x18\x01 \x01(\bR\aenabled\"*\n" + + "\x10SetLocaleRequest\x12\x16\n" + + "\x06locale\x18\x01 \x01(\tR\x06locale\"=\n" + "\x14InstallUpdateRequest\x12%\n" + "\x0einstaller_path\x18\x01 \x01(\tR\rinstallerPath\"M\n" + "\x15InstallUpdateResponse\x124\n" + @@ -1720,7 +1766,7 @@ const file_experimental_boxdd_desktop_service_proto_rawDesc = "" + "!INSTALL_UPDATE_RESULT_UNSPECIFIED\x10\x00\x12!\n" + "\x1dINSTALL_UPDATE_RESULT_STARTED\x10\x01\x12)\n" + "%INSTALL_UPDATE_RESULT_SIGNER_MISMATCH\x10\x02\x12#\n" + - "\x1fINSTALL_UPDATE_RESULT_NOT_NEWER\x10\x032\xc4\f\n" + + "\x1fINSTALL_UPDATE_RESULT_NOT_NEWER\x10\x032\x86\r\n" + "\x0eDesktopService\x12>\n" + "\rGetDaemonInfo\x12\x16.google.protobuf.Empty\x1a\x13.desktop.DaemonInfo\"\x00\x12@\n" + "\fClaimService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12C\n" + @@ -1742,7 +1788,8 @@ const file_experimental_boxdd_desktop_service_proto_rawDesc = "" + "\x13DeleteAllOOMReports\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12P\n" + "\rInstallUpdate\x12\x1d.desktop.InstallUpdateRequest\x1a\x1e.desktop.InstallUpdateResponse\"\x00\x12J\n" + "\x13GetSecuritySettings\x12\x16.google.protobuf.Empty\x1a\x19.desktop.SecuritySettings\"\x00\x12Z\n" + - "\x16SetInsecureModeEnabled\x12&.desktop.SetInsecureModeEnabledRequest\x1a\x16.google.protobuf.Empty\"\x002\x87\x05\n" + + "\x16SetInsecureModeEnabled\x12&.desktop.SetInsecureModeEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12@\n" + + "\tSetLocale\x12\x19.desktop.SetLocaleRequest\x1a\x16.google.protobuf.Empty\"\x002\x87\x05\n" + "\x12ApplicationService\x12?\n" + "\vCheckConfig\x12\x16.desktop.ConfigContent\x1a\x16.google.protobuf.Empty\"\x00\x12@\n" + "\fFormatConfig\x12\x16.desktop.ConfigContent\x1a\x16.desktop.ConfigContent\"\x00\x12H\n" + @@ -1767,7 +1814,7 @@ func file_experimental_boxdd_desktop_service_proto_rawDescGZIP() []byte { var ( file_experimental_boxdd_desktop_service_proto_enumTypes = make([]protoimpl.EnumInfo, 3) - file_experimental_boxdd_desktop_service_proto_msgTypes = make([]protoimpl.MessageInfo, 27) + file_experimental_boxdd_desktop_service_proto_msgTypes = make([]protoimpl.MessageInfo, 28) file_experimental_boxdd_desktop_service_proto_goTypes = []any{ (DaemonOwnership)(0), // 0: desktop.DaemonOwnership (InstallUpdateResult)(0), // 1: desktop.InstallUpdateResult @@ -1797,11 +1844,12 @@ var ( (*OOMReportFile)(nil), // 25: desktop.OOMReportFile (*SecuritySettings)(nil), // 26: desktop.SecuritySettings (*SetInsecureModeEnabledRequest)(nil), // 27: desktop.SetInsecureModeEnabledRequest - (*InstallUpdateRequest)(nil), // 28: desktop.InstallUpdateRequest - (*InstallUpdateResponse)(nil), // 29: desktop.InstallUpdateResponse - (*emptypb.Empty)(nil), // 30: google.protobuf.Empty - (*daemon.NetworkQualityTestProgress)(nil), // 31: daemon.NetworkQualityTestProgress - (*daemon.STUNTestProgress)(nil), // 32: daemon.STUNTestProgress + (*SetLocaleRequest)(nil), // 28: desktop.SetLocaleRequest + (*InstallUpdateRequest)(nil), // 29: desktop.InstallUpdateRequest + (*InstallUpdateResponse)(nil), // 30: desktop.InstallUpdateResponse + (*emptypb.Empty)(nil), // 31: google.protobuf.Empty + (*daemon.NetworkQualityTestProgress)(nil), // 32: daemon.NetworkQualityTestProgress + (*daemon.STUNTestProgress)(nil), // 33: daemon.STUNTestProgress } ) @@ -1814,66 +1862,68 @@ var file_experimental_boxdd_desktop_service_proto_depIdxs = []int32{ 21, // 5: desktop.OOMReportList.reports:type_name -> desktop.OOMReportEntry 25, // 6: desktop.OOMReportContent.files:type_name -> desktop.OOMReportFile 1, // 7: desktop.InstallUpdateResponse.result:type_name -> desktop.InstallUpdateResult - 30, // 8: desktop.DesktopService.GetDaemonInfo:input_type -> google.protobuf.Empty - 30, // 9: desktop.DesktopService.ClaimService:input_type -> google.protobuf.Empty - 30, // 10: desktop.DesktopService.TakeOverService:input_type -> google.protobuf.Empty + 31, // 8: desktop.DesktopService.GetDaemonInfo:input_type -> google.protobuf.Empty + 31, // 9: desktop.DesktopService.ClaimService:input_type -> google.protobuf.Empty + 31, // 10: desktop.DesktopService.TakeOverService:input_type -> google.protobuf.Empty 7, // 11: desktop.DesktopService.StartService:input_type -> desktop.StartServiceRequest - 30, // 12: desktop.DesktopService.GetWorkingDirectory:input_type -> google.protobuf.Empty - 30, // 13: desktop.DesktopService.DestroyWorkingDirectory:input_type -> google.protobuf.Empty - 30, // 14: desktop.DesktopService.ListCrashReports:input_type -> google.protobuf.Empty + 31, // 12: desktop.DesktopService.GetWorkingDirectory:input_type -> google.protobuf.Empty + 31, // 13: desktop.DesktopService.DestroyWorkingDirectory:input_type -> google.protobuf.Empty + 31, // 14: desktop.DesktopService.ListCrashReports:input_type -> google.protobuf.Empty 15, // 15: desktop.DesktopService.ReadCrashReport:input_type -> desktop.CrashReportRequest 15, // 16: desktop.DesktopService.MarkCrashReportRead:input_type -> desktop.CrashReportRequest 16, // 17: desktop.DesktopService.ExportCrashReport:input_type -> desktop.CrashReportExportRequest 15, // 18: desktop.DesktopService.DeleteCrashReport:input_type -> desktop.CrashReportRequest - 30, // 19: desktop.DesktopService.DeleteAllCrashReports:input_type -> google.protobuf.Empty - 30, // 20: desktop.DesktopService.ListOOMReports:input_type -> google.protobuf.Empty + 31, // 19: desktop.DesktopService.DeleteAllCrashReports:input_type -> google.protobuf.Empty + 31, // 20: desktop.DesktopService.ListOOMReports:input_type -> google.protobuf.Empty 22, // 21: desktop.DesktopService.ReadOOMReport:input_type -> desktop.OOMReportRequest 22, // 22: desktop.DesktopService.MarkOOMReportRead:input_type -> desktop.OOMReportRequest 23, // 23: desktop.DesktopService.ExportOOMReport:input_type -> desktop.OOMReportExportRequest 22, // 24: desktop.DesktopService.DeleteOOMReport:input_type -> desktop.OOMReportRequest - 30, // 25: desktop.DesktopService.DeleteAllOOMReports:input_type -> google.protobuf.Empty - 28, // 26: desktop.DesktopService.InstallUpdate:input_type -> desktop.InstallUpdateRequest - 30, // 27: desktop.DesktopService.GetSecuritySettings:input_type -> google.protobuf.Empty + 31, // 25: desktop.DesktopService.DeleteAllOOMReports:input_type -> google.protobuf.Empty + 29, // 26: desktop.DesktopService.InstallUpdate:input_type -> desktop.InstallUpdateRequest + 31, // 27: desktop.DesktopService.GetSecuritySettings:input_type -> google.protobuf.Empty 27, // 28: desktop.DesktopService.SetInsecureModeEnabled:input_type -> desktop.SetInsecureModeEnabledRequest - 9, // 29: desktop.ApplicationService.CheckConfig:input_type -> desktop.ConfigContent - 9, // 30: desktop.ApplicationService.FormatConfig:input_type -> desktop.ConfigContent - 30, // 31: desktop.ApplicationService.GenerateConfigSchema:input_type -> google.protobuf.Empty - 10, // 32: desktop.ApplicationService.EncodeProfile:input_type -> desktop.ProfileContent - 11, // 33: desktop.ApplicationService.DecodeProfile:input_type -> desktop.ProfileData - 3, // 34: desktop.ApplicationService.ArchiveReport:input_type -> desktop.ArchiveReportRequest - 4, // 35: desktop.ApplicationService.StartStandaloneNetworkQualityTest:input_type -> desktop.StandaloneNetworkQualityTestRequest - 5, // 36: desktop.ApplicationService.StartStandaloneSTUNTest:input_type -> desktop.StandaloneSTUNTestRequest - 6, // 37: desktop.DesktopService.GetDaemonInfo:output_type -> desktop.DaemonInfo - 30, // 38: desktop.DesktopService.ClaimService:output_type -> google.protobuf.Empty - 30, // 39: desktop.DesktopService.TakeOverService:output_type -> google.protobuf.Empty - 30, // 40: desktop.DesktopService.StartService:output_type -> google.protobuf.Empty - 12, // 41: desktop.DesktopService.GetWorkingDirectory:output_type -> desktop.WorkingDirectoryInfo - 30, // 42: desktop.DesktopService.DestroyWorkingDirectory:output_type -> google.protobuf.Empty - 13, // 43: desktop.DesktopService.ListCrashReports:output_type -> desktop.CrashReportList - 17, // 44: desktop.DesktopService.ReadCrashReport:output_type -> desktop.CrashReportContent - 30, // 45: desktop.DesktopService.MarkCrashReportRead:output_type -> google.protobuf.Empty - 19, // 46: desktop.DesktopService.ExportCrashReport:output_type -> desktop.CrashReportArchive - 30, // 47: desktop.DesktopService.DeleteCrashReport:output_type -> google.protobuf.Empty - 30, // 48: desktop.DesktopService.DeleteAllCrashReports:output_type -> google.protobuf.Empty - 20, // 49: desktop.DesktopService.ListOOMReports:output_type -> desktop.OOMReportList - 24, // 50: desktop.DesktopService.ReadOOMReport:output_type -> desktop.OOMReportContent - 30, // 51: desktop.DesktopService.MarkOOMReportRead:output_type -> google.protobuf.Empty - 19, // 52: desktop.DesktopService.ExportOOMReport:output_type -> desktop.CrashReportArchive - 30, // 53: desktop.DesktopService.DeleteOOMReport:output_type -> google.protobuf.Empty - 30, // 54: desktop.DesktopService.DeleteAllOOMReports:output_type -> google.protobuf.Empty - 29, // 55: desktop.DesktopService.InstallUpdate:output_type -> desktop.InstallUpdateResponse - 26, // 56: desktop.DesktopService.GetSecuritySettings:output_type -> desktop.SecuritySettings - 30, // 57: desktop.DesktopService.SetInsecureModeEnabled:output_type -> google.protobuf.Empty - 30, // 58: desktop.ApplicationService.CheckConfig:output_type -> google.protobuf.Empty - 9, // 59: desktop.ApplicationService.FormatConfig:output_type -> desktop.ConfigContent - 9, // 60: desktop.ApplicationService.GenerateConfigSchema:output_type -> desktop.ConfigContent - 11, // 61: desktop.ApplicationService.EncodeProfile:output_type -> desktop.ProfileData - 10, // 62: desktop.ApplicationService.DecodeProfile:output_type -> desktop.ProfileContent - 30, // 63: desktop.ApplicationService.ArchiveReport:output_type -> google.protobuf.Empty - 31, // 64: desktop.ApplicationService.StartStandaloneNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress - 32, // 65: desktop.ApplicationService.StartStandaloneSTUNTest:output_type -> daemon.STUNTestProgress - 37, // [37:66] is the sub-list for method output_type - 8, // [8:37] is the sub-list for method input_type + 28, // 29: desktop.DesktopService.SetLocale:input_type -> desktop.SetLocaleRequest + 9, // 30: desktop.ApplicationService.CheckConfig:input_type -> desktop.ConfigContent + 9, // 31: desktop.ApplicationService.FormatConfig:input_type -> desktop.ConfigContent + 31, // 32: desktop.ApplicationService.GenerateConfigSchema:input_type -> google.protobuf.Empty + 10, // 33: desktop.ApplicationService.EncodeProfile:input_type -> desktop.ProfileContent + 11, // 34: desktop.ApplicationService.DecodeProfile:input_type -> desktop.ProfileData + 3, // 35: desktop.ApplicationService.ArchiveReport:input_type -> desktop.ArchiveReportRequest + 4, // 36: desktop.ApplicationService.StartStandaloneNetworkQualityTest:input_type -> desktop.StandaloneNetworkQualityTestRequest + 5, // 37: desktop.ApplicationService.StartStandaloneSTUNTest:input_type -> desktop.StandaloneSTUNTestRequest + 6, // 38: desktop.DesktopService.GetDaemonInfo:output_type -> desktop.DaemonInfo + 31, // 39: desktop.DesktopService.ClaimService:output_type -> google.protobuf.Empty + 31, // 40: desktop.DesktopService.TakeOverService:output_type -> google.protobuf.Empty + 31, // 41: desktop.DesktopService.StartService:output_type -> google.protobuf.Empty + 12, // 42: desktop.DesktopService.GetWorkingDirectory:output_type -> desktop.WorkingDirectoryInfo + 31, // 43: desktop.DesktopService.DestroyWorkingDirectory:output_type -> google.protobuf.Empty + 13, // 44: desktop.DesktopService.ListCrashReports:output_type -> desktop.CrashReportList + 17, // 45: desktop.DesktopService.ReadCrashReport:output_type -> desktop.CrashReportContent + 31, // 46: desktop.DesktopService.MarkCrashReportRead:output_type -> google.protobuf.Empty + 19, // 47: desktop.DesktopService.ExportCrashReport:output_type -> desktop.CrashReportArchive + 31, // 48: desktop.DesktopService.DeleteCrashReport:output_type -> google.protobuf.Empty + 31, // 49: desktop.DesktopService.DeleteAllCrashReports:output_type -> google.protobuf.Empty + 20, // 50: desktop.DesktopService.ListOOMReports:output_type -> desktop.OOMReportList + 24, // 51: desktop.DesktopService.ReadOOMReport:output_type -> desktop.OOMReportContent + 31, // 52: desktop.DesktopService.MarkOOMReportRead:output_type -> google.protobuf.Empty + 19, // 53: desktop.DesktopService.ExportOOMReport:output_type -> desktop.CrashReportArchive + 31, // 54: desktop.DesktopService.DeleteOOMReport:output_type -> google.protobuf.Empty + 31, // 55: desktop.DesktopService.DeleteAllOOMReports:output_type -> google.protobuf.Empty + 30, // 56: desktop.DesktopService.InstallUpdate:output_type -> desktop.InstallUpdateResponse + 26, // 57: desktop.DesktopService.GetSecuritySettings:output_type -> desktop.SecuritySettings + 31, // 58: desktop.DesktopService.SetInsecureModeEnabled:output_type -> google.protobuf.Empty + 31, // 59: desktop.DesktopService.SetLocale:output_type -> google.protobuf.Empty + 31, // 60: desktop.ApplicationService.CheckConfig:output_type -> google.protobuf.Empty + 9, // 61: desktop.ApplicationService.FormatConfig:output_type -> desktop.ConfigContent + 9, // 62: desktop.ApplicationService.GenerateConfigSchema:output_type -> desktop.ConfigContent + 11, // 63: desktop.ApplicationService.EncodeProfile:output_type -> desktop.ProfileData + 10, // 64: desktop.ApplicationService.DecodeProfile:output_type -> desktop.ProfileContent + 31, // 65: desktop.ApplicationService.ArchiveReport:output_type -> google.protobuf.Empty + 32, // 66: desktop.ApplicationService.StartStandaloneNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress + 33, // 67: desktop.ApplicationService.StartStandaloneSTUNTest:output_type -> daemon.STUNTestProgress + 38, // [38:68] is the sub-list for method output_type + 8, // [8:38] is the sub-list for method input_type 8, // [8:8] is the sub-list for extension type_name 8, // [8:8] is the sub-list for extension extendee 0, // [0:8] is the sub-list for field type_name @@ -1890,7 +1940,7 @@ func file_experimental_boxdd_desktop_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_experimental_boxdd_desktop_service_proto_rawDesc), len(file_experimental_boxdd_desktop_service_proto_rawDesc)), NumEnums: 3, - NumMessages: 27, + NumMessages: 28, NumExtensions: 0, NumServices: 2, }, diff --git a/experimental/boxdd/desktop_service.proto b/experimental/boxdd/desktop_service.proto index 4365c625..484d720f 100644 --- a/experimental/boxdd/desktop_service.proto +++ b/experimental/boxdd/desktop_service.proto @@ -28,6 +28,7 @@ service DesktopService { rpc InstallUpdate(InstallUpdateRequest) returns (InstallUpdateResponse) {} rpc GetSecuritySettings(google.protobuf.Empty) returns (SecuritySettings) {} rpc SetInsecureModeEnabled(SetInsecureModeEnabledRequest) returns (google.protobuf.Empty) {} + rpc SetLocale(SetLocaleRequest) returns (google.protobuf.Empty) {} } service ApplicationService { @@ -185,6 +186,10 @@ message SetInsecureModeEnabledRequest { bool enabled = 1; } +message SetLocaleRequest { + string locale = 1; +} + message InstallUpdateRequest { string installer_path = 1; } diff --git a/experimental/boxdd/desktop_service_grpc.pb.go b/experimental/boxdd/desktop_service_grpc.pb.go index 5ad6af21..90915625 100644 --- a/experimental/boxdd/desktop_service_grpc.pb.go +++ b/experimental/boxdd/desktop_service_grpc.pb.go @@ -37,6 +37,7 @@ const ( DesktopService_InstallUpdate_FullMethodName = "/desktop.DesktopService/InstallUpdate" DesktopService_GetSecuritySettings_FullMethodName = "/desktop.DesktopService/GetSecuritySettings" DesktopService_SetInsecureModeEnabled_FullMethodName = "/desktop.DesktopService/SetInsecureModeEnabled" + DesktopService_SetLocale_FullMethodName = "/desktop.DesktopService/SetLocale" ) // DesktopServiceClient is the client API for DesktopService service. @@ -64,6 +65,7 @@ type DesktopServiceClient interface { InstallUpdate(ctx context.Context, in *InstallUpdateRequest, opts ...grpc.CallOption) (*InstallUpdateResponse, error) GetSecuritySettings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SecuritySettings, error) SetInsecureModeEnabled(ctx context.Context, in *SetInsecureModeEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + SetLocale(ctx context.Context, in *SetLocaleRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) } type desktopServiceClient struct { @@ -284,6 +286,16 @@ func (c *desktopServiceClient) SetInsecureModeEnabled(ctx context.Context, in *S return out, nil } +func (c *desktopServiceClient) SetLocale(ctx context.Context, in *SetLocaleRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, DesktopService_SetLocale_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // DesktopServiceServer is the server API for DesktopService service. // All implementations must embed UnimplementedDesktopServiceServer // for forward compatibility. @@ -309,6 +321,7 @@ type DesktopServiceServer interface { InstallUpdate(context.Context, *InstallUpdateRequest) (*InstallUpdateResponse, error) GetSecuritySettings(context.Context, *emptypb.Empty) (*SecuritySettings, error) SetInsecureModeEnabled(context.Context, *SetInsecureModeEnabledRequest) (*emptypb.Empty, error) + SetLocale(context.Context, *SetLocaleRequest) (*emptypb.Empty, error) mustEmbedUnimplementedDesktopServiceServer() } @@ -402,6 +415,10 @@ func (UnimplementedDesktopServiceServer) GetSecuritySettings(context.Context, *e func (UnimplementedDesktopServiceServer) SetInsecureModeEnabled(context.Context, *SetInsecureModeEnabledRequest) (*emptypb.Empty, error) { return nil, status.Error(codes.Unimplemented, "method SetInsecureModeEnabled not implemented") } + +func (UnimplementedDesktopServiceServer) SetLocale(context.Context, *SetLocaleRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method SetLocale not implemented") +} func (UnimplementedDesktopServiceServer) mustEmbedUnimplementedDesktopServiceServer() {} func (UnimplementedDesktopServiceServer) testEmbeddedByValue() {} @@ -801,6 +818,24 @@ func _DesktopService_SetInsecureModeEnabled_Handler(srv interface{}, ctx context return interceptor(ctx, in, info, handler) } +func _DesktopService_SetLocale_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetLocaleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DesktopServiceServer).SetLocale(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DesktopService_SetLocale_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DesktopServiceServer).SetLocale(ctx, req.(*SetLocaleRequest)) + } + return interceptor(ctx, in, info, handler) +} + // DesktopService_ServiceDesc is the grpc.ServiceDesc for DesktopService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -892,6 +927,10 @@ var DesktopService_ServiceDesc = grpc.ServiceDesc{ MethodName: "SetInsecureModeEnabled", Handler: _DesktopService_SetInsecureModeEnabled_Handler, }, + { + MethodName: "SetLocale", + Handler: _DesktopService_SetLocale_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "experimental/boxdd/desktop_service.proto", diff --git a/experimental/boxdd/insecure_mode.go b/experimental/boxdd/insecure_mode.go deleted file mode 100644 index 77965ce0..00000000 --- a/experimental/boxdd/insecure_mode.go +++ /dev/null @@ -1,22 +0,0 @@ -package main - -import ( - "path/filepath" - - "github.com/sagernet/sing/common/json" - "github.com/sagernet/tailscale/atomicfile" -) - -const securitySettingsFileName = "security.json" - -type securitySettings struct { - InsecureModeEnabled bool `json:"insecure_mode_enabled"` -} - -func saveSecuritySettings(directory string, settings securitySettings) error { - content, err := json.Marshal(settings) - if err != nil { - return err - } - return atomicfile.WriteFile(filepath.Join(directory, securitySettingsFileName), content, 0o600) -} diff --git a/experimental/boxdd/insecure_mode_linux.go b/experimental/boxdd/insecure_mode_linux.go index e43657bf..164f2830 100644 --- a/experimental/boxdd/insecure_mode_linux.go +++ b/experimental/boxdd/insecure_mode_linux.go @@ -4,11 +4,8 @@ package main import ( "context" - "os" - "path/filepath" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" ) @@ -26,20 +23,8 @@ func insecureModePlatformName() string { return "Linux" } -func loadSecuritySettings(directory string) (securitySettings, error) { - content, err := os.ReadFile(filepath.Join(directory, securitySettingsFileName)) - if err != nil { - return securitySettings{}, err - } - settings, err := json.UnmarshalExtended[securitySettings](content) - if err != nil { - return securitySettings{}, err - } - return settings, nil -} - func (d *Daemon) insecureModeEnabled() bool { - settings, err := loadSecuritySettings(workingDirectory) + settings, err := loadDaemonSettings(workingDirectory) if err != nil { return false } diff --git a/experimental/boxdd/insecure_mode_windows.go b/experimental/boxdd/insecure_mode_windows.go index 40de43c2..05d6c7a8 100644 --- a/experimental/boxdd/insecure_mode_windows.go +++ b/experimental/boxdd/insecure_mode_windows.go @@ -4,11 +4,8 @@ package main import ( "context" - "os" - "path/filepath" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" ) @@ -26,20 +23,8 @@ func insecureModePlatformName() string { return "Windows" } -func loadSecuritySettings(directory string) (securitySettings, error) { - content, err := os.ReadFile(filepath.Join(directory, securitySettingsFileName)) - if err != nil { - return securitySettings{}, err - } - settings, err := json.UnmarshalExtended[securitySettings](content) - if err != nil { - return securitySettings{}, err - } - return settings, nil -} - func (d *Daemon) insecureModeEnabled() bool { - settings, err := loadSecuritySettings(workingDirectory) + settings, err := loadDaemonSettings(workingDirectory) if err != nil { return false } diff --git a/experimental/boxdd/platform_linux.go b/experimental/boxdd/platform_linux.go index 77dfedcd..60341979 100644 --- a/experimental/boxdd/platform_linux.go +++ b/experimental/boxdd/platform_linux.go @@ -137,11 +137,15 @@ func (p *linuxPlatformInterface) UsePlatformWIFIMonitor() bool { } func (p *linuxPlatformInterface) UsePlatformNotification() bool { - return false + return true } func (p *linuxPlatformInterface) SendNotification(notification *adapter.Notification) error { - return nil + return p.daemon.startedService.SendNotification(notification) +} + +func (p *linuxPlatformInterface) CancelNotification(identifier string, typeID int32) error { + return p.daemon.startedService.CancelNotification(identifier, typeID) } func (p *linuxPlatformInterface) MyInterfaceAddress() []netip.Addr { diff --git a/experimental/boxdd/platform_windows.go b/experimental/boxdd/platform_windows.go index 964198f9..9d972218 100644 --- a/experimental/boxdd/platform_windows.go +++ b/experimental/boxdd/platform_windows.go @@ -152,11 +152,15 @@ func (p *windowsPlatformInterface) UsePlatformWIFIMonitor() bool { } func (p *windowsPlatformInterface) UsePlatformNotification() bool { - return false + return true } func (p *windowsPlatformInterface) SendNotification(notification *adapter.Notification) error { - return nil + return p.daemon.startedService.SendNotification(notification) +} + +func (p *windowsPlatformInterface) CancelNotification(identifier string, typeID int32) error { + return p.daemon.startedService.CancelNotification(identifier, typeID) } func (p *windowsPlatformInterface) MyInterfaceAddress() []netip.Addr { diff --git a/experimental/boxdd/server.go b/experimental/boxdd/server.go index af50f819..912e93a1 100644 --- a/experimental/boxdd/server.go +++ b/experimental/boxdd/server.go @@ -40,6 +40,7 @@ type Daemon struct { } func newDaemon() (*Daemon, error) { + restoreLocale() ctx := include.Context(context.Background()) d := &Daemon{ ctx: ctx, diff --git a/experimental/boxdd/settings.go b/experimental/boxdd/settings.go new file mode 100644 index 00000000..1c318f42 --- /dev/null +++ b/experimental/boxdd/settings.go @@ -0,0 +1,62 @@ +package main + +import ( + "os" + "path/filepath" + "sync" + + "github.com/sagernet/sing-box/experimental/locale" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/tailscale/atomicfile" +) + +const ( + settingsFileName = "settings.json" + legacySecuritySettingsFileName = "security.json" +) + +type daemonSettings struct { + InsecureModeEnabled bool `json:"insecure_mode_enabled"` + Locale string `json:"locale,omitempty"` +} + +var settingsAccess sync.Mutex + +func loadDaemonSettings(directory string) (daemonSettings, error) { + content, err := os.ReadFile(filepath.Join(directory, settingsFileName)) + if os.IsNotExist(err) { + content, err = os.ReadFile(filepath.Join(directory, legacySecuritySettingsFileName)) + } + if err != nil { + return daemonSettings{}, err + } + return json.UnmarshalExtended[daemonSettings](content) +} + +func updateDaemonSettings(directory string, modify func(settings *daemonSettings)) error { + settingsAccess.Lock() + defer settingsAccess.Unlock() + settings, err := loadDaemonSettings(directory) + if err != nil && !os.IsNotExist(err) { + return err + } + modify(&settings) + content, err := json.Marshal(settings) + if err != nil { + return err + } + err = atomicfile.WriteFile(filepath.Join(directory, settingsFileName), content, 0o600) + if err != nil { + return err + } + _ = os.Remove(filepath.Join(directory, legacySecuritySettingsFileName)) + return nil +} + +func restoreLocale() { + settings, err := loadDaemonSettings(workingDirectory) + if err != nil { + return + } + locale.Set(settings.Locale) +} diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index d19f3e99..5c5ad912 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -108,6 +108,7 @@ const ( commandClientDialAttempts = 10 commandClientDialBaseDelay = 100 * time.Millisecond commandClientDialStepDelay = 50 * time.Millisecond + commandClientProbeTimeout = 2 * time.Second ) func commandClientDialDelay(attempt int) time.Duration { @@ -167,14 +168,33 @@ func (c *CommandClient) establishConnection() (*grpc.ClientConn, daemon.StartedS return c.dialRemote() } target, contextDialer := dialTarget() - return c.dialWithRetry(target, localDialOptions(contextDialer), true) + return c.dialWithRetry(target, localDialOptions(contextDialer), !c.standalone) } -// dialWithRetry connects to the local command server. The retry loop exists to -// wait out the server starting up: WaitForReady keeps the probe redialing and -// the loop reissues it with a growing delay, so a freshly launched extension is -// picked up without surfacing a transient "unavailable" to the UI. +// dialWithRetry connects to the local command server. For a handler-bound +// client the retry loop waits out the server starting up: WaitForReady keeps +// the probe redialing and the loop reissues it with a growing delay, so a +// freshly launched extension is picked up without surfacing a transient +// "unavailable" to the UI. A standalone client issues a single fail-fast +// probe instead: it serves a query from a UI that does not own the service +// lifecycle, and a server that is not running is reported immediately. func (c *CommandClient) dialWithRetry(target string, dialOptions []grpc.DialOption, retryDial bool) (*grpc.ClientConn, daemon.StartedServiceClient, error) { + if !retryDial { + connection, err := grpc.NewClient(target, dialOptions...) + if err != nil { + return nil, nil, E.Cause(err, "create command client") + } + client := daemon.NewStartedServiceClient(connection) + ctx, cancel := context.WithTimeout(context.Background(), commandClientProbeTimeout) + _, err = client.GetStartedAt(ctx, &emptypb.Empty{}, grpc.WaitForReady(false)) + cancel() + if err != nil { + connection.Close() + return nil, nil, E.Cause(err, "probe command server") + } + return connection, client, nil + } + var connection *grpc.ClientConn var client daemon.StartedServiceClient var lastError error @@ -185,9 +205,6 @@ func (c *CommandClient) dialWithRetry(target string, dialOptions []grpc.DialOpti connection, err = grpc.NewClient(target, dialOptions...) if err != nil { lastError = err - if !retryDial { - return nil, nil, E.Cause(err, "create command client") - } time.Sleep(commandClientDialDelay(attempt)) continue } @@ -1304,3 +1321,231 @@ func (c *CommandClient) ProvideUSBDevices(handler USBProviderHandler) (*USBProvi return session, nil } + +func (c *CommandClient) SubscribeTaildropInbox(endpointTag string, handler TaildropInboxHandler) (*TaildropInboxSubscription, error) { + session := new(TaildropInboxSubscription) + err := subscribeStatus(c, &session.streamSession, "taildrop inbox", func(ctx context.Context, client daemon.StartedServiceClient) (grpc.ServerStreamingClient[daemon.TaildropInbox], error) { + return client.SubscribeTaildropInbox(ctx, &daemon.SubscribeTaildropInboxRequest{EndpointTag: endpointTag}) + }, func(update *daemon.TaildropInbox) { + handler.OnInboxUpdate(taildropInboxFromGRPC(update)) + }, handler.OnError) + if err != nil { + return nil, err + } + return session, nil +} + +func (c *CommandClient) SendTaildropFiles(options *TaildropSendOptions, handler TaildropSendHandler) (*TaildropSendSession, error) { + client, parentCtx, err := c.getClientForCall() + if err != nil { + return nil, E.Cause(err, "send taildrop files") + } + + streamCtx, cancel := context.WithCancel(parentCtx) + failStart := func(cause error, message string) (*TaildropSendSession, error) { + cancel() + if c.standalone { + c.closeConnection() + } + return nil, E.Cause(cause, message) + } + + stream, err := client.SendTaildropFiles(streamCtx) + if err != nil { + return failStart(err, "send taildrop files") + } + + sendErr := stream.Send(&daemon.TaildropSendClientMessage{ + Message: &daemon.TaildropSendClientMessage_Start{Start: &daemon.TaildropSendStart{ + EndpointTag: options.EndpointTag, + PeerStableID: options.PeerStableID, + Files: options.files, + }}, + }) + if sendErr != nil { + return failStart(sendErr, "send taildrop start") + } + session := &TaildropSendSession{ + streamSession: streamSession{ + ctx: streamCtx, + cancel: cancel, + closeDone: make(chan struct{}), + }, + stream: stream, + } + + standalone := c.standalone + go func() { + defer func() { + close(session.closeDone) + if standalone { + c.closeConnection() + } + }() + for { + message, recvErr := stream.Recv() + if recvErr != nil { + switch { + case recvErr == io.EOF: + handler.OnFinish("") + case streamCtx.Err() == nil: + handler.OnFinish(E.Cause(recvErr, "taildrop send").Error()) + } + cancel() + return + } + progress := message.GetProgress() + if progress == nil { + continue + } + if progress.FileCompleted { + handler.OnFileCompleted(progress.FileIndex, progress.SentBytes) + } else { + handler.OnProgress(progress.FileIndex, progress.SentBytes) + } + } + }() + + return session, nil +} + +func (c *CommandClient) DownloadTaildropFile(endpointTag string, name string, destinationPath string, handler TaildropDownloadHandler) (*TaildropDownloadSession, error) { + client, parentCtx, err := c.getClientForCall() + if err != nil { + return nil, E.Cause(err, "download taildrop file") + } + + streamCtx, cancel := context.WithCancel(parentCtx) + failStart := func(cause error, message string) (*TaildropDownloadSession, error) { + cancel() + if c.standalone { + c.closeConnection() + } + return nil, E.Cause(cause, message) + } + + stream, err := client.DownloadTaildropFile(streamCtx, &daemon.DownloadTaildropFileRequest{ + EndpointTag: endpointTag, + Name: name, + }) + if err != nil { + return failStart(err, "download taildrop file") + } + firstChunk, err := stream.Recv() + if err != nil { + return failStart(err, "download taildrop file") + } + destinationFile, err := os.Create(destinationPath) + if err != nil { + return failStart(err, "download taildrop file") + } + + session := &TaildropDownloadSession{ + streamSession: streamSession{ + ctx: streamCtx, + cancel: cancel, + closeDone: make(chan struct{}), + }, + } + + standalone := c.standalone + go func() { + defer func() { + close(session.closeDone) + if standalone { + c.closeConnection() + } + }() + totalSize := firstChunk.Size + var ( + downloaded int64 + lastProgress time.Time + ) + writeChunk := func(data []byte) error { + if len(data) == 0 { + return nil + } + _, writeErr := destinationFile.Write(data) + if writeErr != nil { + return writeErr + } + downloaded += int64(len(data)) + now := time.Now() + if downloaded == totalSize || now.Sub(lastProgress) >= daemon.TaildropProgressMinInterval { + lastProgress = now + handler.OnProgress(downloaded, totalSize) + } + return nil + } + downloadErr := writeChunk(firstChunk.Data) + for downloadErr == nil { + var chunk *daemon.DownloadTaildropFileChunk + chunk, downloadErr = stream.Recv() + if downloadErr == io.EOF { + downloadErr = nil + break + } + if downloadErr != nil { + downloadErr = E.Cause(downloadErr, "download taildrop file") + break + } + downloadErr = writeChunk(chunk.Data) + } + if downloadErr == nil { + downloadErr = destinationFile.Close() + } else { + destinationFile.Close() + } + if downloadErr != nil { + os.Remove(destinationPath) + if streamCtx.Err() == nil { + handler.OnFinish(downloadErr.Error()) + } + cancel() + return + } + handler.OnFinish("") + cancel() + }() + + return session, nil +} + +func (c *CommandClient) DeleteTaildropFile(endpointTag string, name string) error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.DeleteTaildropFile(ctx, &daemon.DeleteTaildropFileRequest{ + EndpointTag: endpointTag, + Name: name, + }) + }) + if err != nil { + return E.Cause(err, "delete taildrop file") + } + return nil +} + +func (c *CommandClient) CancelTaildropReceiving(endpointTag string, senderID string, name string) error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.CancelTaildropReceiving(ctx, &daemon.CancelTaildropReceivingRequest{ + EndpointTag: endpointTag, + SenderID: senderID, + Name: name, + }) + }) + if err != nil { + return E.Cause(err, "cancel taildrop receiving") + } + return nil +} + +func (c *CommandClient) MarkTaildropInboxRead(endpointTag string) error { + _, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.MarkTaildropInboxRead(ctx, &daemon.MarkTaildropInboxReadRequest{ + EndpointTag: endpointTag, + }) + }) + if err != nil { + return E.Cause(err, "mark taildrop inbox read") + } + return nil +} diff --git a/experimental/libbox/command_types_taildrop.go b/experimental/libbox/command_types_taildrop.go new file mode 100644 index 00000000..3adca4ec --- /dev/null +++ b/experimental/libbox/command_types_taildrop.go @@ -0,0 +1,155 @@ +package libbox + +import ( + "os" + + "github.com/sagernet/sing-box/daemon" +) + +const TaildropChunkSize = daemon.TaildropChunkSize + +type TaildropInbox struct { + EndpointTag string + files []*TaildropFile + receiving []*TaildropReceivingFile +} + +func (i *TaildropInbox) Files() TaildropFileIterator { + return newIterator(i.files) +} + +func (i *TaildropInbox) Receiving() TaildropReceivingFileIterator { + return newIterator(i.receiving) +} + +type TaildropFile struct { + Name string + Size int64 + SenderName string + ModifiedAt int64 +} + +type TaildropFileIterator interface { + Next() *TaildropFile + HasNext() bool +} + +type TaildropReceivingFile struct { + Name string + Size int64 + ReceivedBytes int64 + SenderID string + SenderName string +} + +type TaildropReceivingFileIterator interface { + Next() *TaildropReceivingFile + HasNext() bool +} + +type TaildropInboxHandler interface { + OnInboxUpdate(inbox *TaildropInbox) + OnError(message string) +} + +type TaildropInboxSubscription struct { + streamSession +} + +func taildropInboxFromGRPC(inbox *daemon.TaildropInbox) *TaildropInbox { + result := &TaildropInbox{EndpointTag: inbox.EndpointTag} + for _, file := range inbox.Files { + result.files = append(result.files, &TaildropFile{ + Name: file.Name, + Size: file.Size, + SenderName: file.SenderName, + ModifiedAt: file.ModifiedAt, + }) + } + for _, file := range inbox.Receiving { + result.receiving = append(result.receiving, &TaildropReceivingFile{ + Name: file.Name, + Size: file.Size, + ReceivedBytes: file.ReceivedBytes, + SenderID: file.SenderID, + SenderName: file.SenderName, + }) + } + return result +} + +type TaildropSendOptions struct { + EndpointTag string + PeerStableID string + files []*daemon.TaildropOutgoingFile +} + +func NewTaildropSendOptions() *TaildropSendOptions { + return &TaildropSendOptions{} +} + +// AddFile queues a file whose content the caller writes through +// TaildropSendSession.WriteChunk in queue order, terminated by FinishFile. +// A negative size declares the size as unknown; a non-negative size is +// verified against the written byte count. +func (o *TaildropSendOptions) AddFile(name string, size int64) { + o.files = append(o.files, &daemon.TaildropOutgoingFile{ + Name: name, + Size: size, + }) +} + +type TaildropDownloadHandler interface { + OnProgress(downloaded int64, total int64) + OnFinish(errorMessage string) +} + +type TaildropDownloadSession struct { + streamSession +} + +type TaildropSendHandler interface { + OnProgress(fileIndex int32, sentBytes int64) + OnFileCompleted(fileIndex int32, sentBytes int64) + OnFinish(errorMessage string) +} + +type TaildropSendSession struct { + streamSession + stream daemon.StartedService_SendTaildropFilesClient +} + +func (s *TaildropSendSession) WriteChunk(data []byte) error { + if s.ctx.Err() != nil { + return os.ErrClosed + } + err := s.stream.Send(&daemon.TaildropSendClientMessage{ + Message: &daemon.TaildropSendClientMessage_Chunk{Chunk: &daemon.TaildropFileChunk{Data: data}}, + }) + if err != nil { + return s.finishSend() + } + return nil +} + +func (s *TaildropSendSession) FinishFile() error { + if s.ctx.Err() != nil { + return os.ErrClosed + } + err := s.stream.Send(&daemon.TaildropSendClientMessage{ + Message: &daemon.TaildropSendClientMessage_FileDone{FileDone: &daemon.TaildropFileDone{}}, + }) + if err != nil { + return s.finishSend() + } + return nil +} + +// grpc-go reports the RPC status only through RecvMsg; a Send on a stream +// terminated by the server returns bare io.EOF. Wait for the receive +// goroutine to deliver the status through OnFinish, then return a sentinel +// the caller does not report. +func (s *TaildropSendSession) finishSend() error { + <-s.closeDone + return os.ErrClosed +} diff --git a/experimental/libbox/command_types_tailscale.go b/experimental/libbox/command_types_tailscale.go index 8f6e183e..c197a075 100644 --- a/experimental/libbox/command_types_tailscale.go +++ b/experimental/libbox/command_types_tailscale.go @@ -16,16 +16,20 @@ type TailscaleEndpointStatusIterator interface { } type TailscaleEndpointStatus struct { - EndpointTag string - BackendState string - StateText string - AuthURL string - NetworkName string - MagicDNSSuffix string - Self *TailscalePeer - ExitNode *TailscalePeer - KeyAuth bool - userGroups []*TailscaleUserGroup + EndpointTag string + BackendState string + StateText string + AuthURL string + NetworkName string + MagicDNSSuffix string + Self *TailscalePeer + ExitNode *TailscalePeer + KeyAuth bool + CanShareFiles bool + WaitingFileCount int32 + ReceivingFileCount int32 + UnreadFileCount int32 + userGroups []*TailscaleUserGroup } func (s *TailscaleEndpointStatus) UserGroups() TailscaleUserGroupIterator { @@ -55,22 +59,23 @@ type TailscalePeerIterator interface { } type TailscalePeer struct { - StableID string - HostName string - DNSName string - OS string - tailscaleIPs []string - sshHostKeys []string - Online bool - ExitNode bool - ExitNodeOption bool - ShareeNode bool - Expired bool - Active bool - RxBytes int64 - TxBytes int64 - KeyExpiry int64 - LastSeen int64 + StableID string + HostName string + DNSName string + OS string + tailscaleIPs []string + sshHostKeys []string + Online bool + ExitNode bool + ExitNodeOption bool + ShareeNode bool + Expired bool + Active bool + CanReceiveFiles bool + RxBytes int64 + TxBytes int64 + KeyExpiry int64 + LastSeen int64 } func (p *TailscalePeer) TailscaleIPs() StringIterator { @@ -104,14 +109,18 @@ func tailscaleEndpointStatusFromGRPC(status *daemon.TailscaleEndpointStatus) *Ta userGroups[i] = tailscaleUserGroupFromGRPC(group) } result := &TailscaleEndpointStatus{ - EndpointTag: status.EndpointTag, - BackendState: status.BackendState, - StateText: status.StateText, - AuthURL: status.AuthURL, - NetworkName: status.NetworkName, - MagicDNSSuffix: status.MagicDNSSuffix, - KeyAuth: status.GetKeyAuth(), - userGroups: userGroups, + EndpointTag: status.EndpointTag, + BackendState: status.BackendState, + StateText: status.StateText, + AuthURL: status.AuthURL, + NetworkName: status.NetworkName, + MagicDNSSuffix: status.MagicDNSSuffix, + KeyAuth: status.GetKeyAuth(), + CanShareFiles: status.CanShareFiles, + WaitingFileCount: status.WaitingFileCount, + ReceivingFileCount: status.ReceivingFileCount, + UnreadFileCount: status.UnreadFileCount, + userGroups: userGroups, } if status.Self != nil { result.Self = tailscalePeerFromGRPC(status.Self) @@ -138,21 +147,22 @@ func tailscaleUserGroupFromGRPC(group *daemon.TailscaleUserGroup) *TailscaleUser func tailscalePeerFromGRPC(peer *daemon.TailscalePeer) *TailscalePeer { return &TailscalePeer{ - StableID: peer.StableID, - HostName: peer.HostName, - DNSName: peer.DnsName, - OS: peer.Os, - tailscaleIPs: peer.TailscaleIPs, - sshHostKeys: peer.SshHostKeys, - Online: peer.Online, - ExitNode: peer.ExitNode, - ExitNodeOption: peer.ExitNodeOption, - ShareeNode: peer.ShareeNode, - Expired: peer.Expired, - Active: peer.Active, - RxBytes: peer.RxBytes, - TxBytes: peer.TxBytes, - KeyExpiry: peer.KeyExpiry, - LastSeen: peer.LastSeen, + StableID: peer.StableID, + HostName: peer.HostName, + DNSName: peer.DnsName, + OS: peer.Os, + tailscaleIPs: peer.TailscaleIPs, + sshHostKeys: peer.SshHostKeys, + Online: peer.Online, + ExitNode: peer.ExitNode, + ExitNodeOption: peer.ExitNodeOption, + ShareeNode: peer.ShareeNode, + Expired: peer.Expired, + Active: peer.Active, + CanReceiveFiles: peer.CanReceiveFiles, + RxBytes: peer.RxBytes, + TxBytes: peer.TxBytes, + KeyExpiry: peer.KeyExpiry, + LastSeen: peer.LastSeen, } } diff --git a/experimental/libbox/command_types_tailscale_ping.go b/experimental/libbox/command_types_tailscale_ping.go index 5b8ebc2f..f55f3fee 100644 --- a/experimental/libbox/command_types_tailscale_ping.go +++ b/experimental/libbox/command_types_tailscale_ping.go @@ -6,6 +6,7 @@ type TailscalePingResult struct { LatencyMs float64 IsDirect bool Endpoint string + PeerRelay string DERPRegionID int32 DERPRegionCode string Error string @@ -25,6 +26,7 @@ func tailscalePingResultFromGRPC(response *daemon.TailscalePingResponse) *Tailsc LatencyMs: response.LatencyMs, IsDirect: response.IsDirect, Endpoint: response.Endpoint, + PeerRelay: response.PeerRelay, DERPRegionID: response.DerpRegionID, DERPRegionCode: response.DerpRegionCode, Error: response.Error, diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index bc05eaff..1459ceaa 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -147,6 +147,10 @@ func (s *platformInterfaceStub) SendNotification(notification *adapter.Notificat return nil } +func (s *platformInterfaceStub) CancelNotification(identifier string, typeID int32) error { + return nil +} + func (s *platformInterfaceStub) MyInterfaceAddress() []netip.Addr { return nil } diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index 87040da9..3cf24489 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -17,6 +17,7 @@ type PlatformInterface interface { ReadWIFIState() *WIFIState ClearDNSCache() SendNotification(notification *Notification) error + CancelNotification(identifier string, typeID int32) error StartNeighborMonitor(listener NeighborUpdateListener) error CloseNeighborMonitor(listener NeighborUpdateListener) error RegisterMyInterface(name string) diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 00ed91d8..1ae370fd 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -239,6 +239,10 @@ func (w *platformInterfaceWrapper) SendNotification(notification *adapter.Notifi return w.iif.SendNotification((*Notification)(notification)) } +func (w *platformInterfaceWrapper) CancelNotification(identifier string, typeID int32) error { + return w.iif.CancelNotification(identifier, typeID) +} + func (w *platformInterfaceWrapper) UsePlatformNeighborResolver() bool { return true } diff --git a/experimental/locale/locale.go b/experimental/locale/locale.go index 06ad77e0..74c65f51 100644 --- a/experimental/locale/locale.go +++ b/experimental/locale/locale.go @@ -39,6 +39,9 @@ type Locale struct { TailscaleStopped string TailscaleStarting string TailscaleRunning string + TaildropReceiving string + TaildropReceived string + TaildropSendCanceled string VPNConnecting string VPNAuthentication string VPNConnected string @@ -59,6 +62,9 @@ var defaultLocale = &Locale{ TailscaleStopped: "Stopped", TailscaleStarting: "Starting", TailscaleRunning: "Running", + TaildropReceiving: "Receiving %s from %s", + TaildropReceived: "%s received from %s", + TaildropSendCanceled: "Sending %s canceled by receiver", VPNConnecting: "Connecting", VPNAuthentication: "Authentication required", VPNConnected: "Connected", diff --git a/experimental/locale/locale_fa.go b/experimental/locale/locale_fa.go index 7c2281b5..c5e1e485 100644 --- a/experimental/locale/locale_fa.go +++ b/experimental/locale/locale_fa.go @@ -14,6 +14,9 @@ func init() { TailscaleStopped: "متوقف\u200cشده", TailscaleStarting: "در حال شروع", TailscaleRunning: "در حال اجرا", + TaildropReceiving: "در حال دریافت %[1]s از %[2]s", + TaildropReceived: "%[1]s از %[2]s دریافت شد", + TaildropSendCanceled: "ارسال %s توسط گیرنده لغو شد", VPNConnecting: "در حال اتصال", VPNAuthentication: "نیاز به احراز هویت", VPNConnected: "متصل", diff --git a/experimental/locale/locale_ru.go b/experimental/locale/locale_ru.go index 0cdce750..ffbc24e5 100644 --- a/experimental/locale/locale_ru.go +++ b/experimental/locale/locale_ru.go @@ -14,6 +14,9 @@ func init() { TailscaleStopped: "Остановлено", TailscaleStarting: "Запуск", TailscaleRunning: "Работает", + TaildropReceiving: "Получение %[1]s от %[2]s", + TaildropReceived: "%[1]s получен от %[2]s", + TaildropSendCanceled: "Отправка %s отменена получателем", VPNConnecting: "Подключение", VPNAuthentication: "Требуется аутентификация", VPNConnected: "Подключено", diff --git a/experimental/locale/locale_zh_CN.go b/experimental/locale/locale_zh_CN.go index 3d4cce81..8bc3bf22 100644 --- a/experimental/locale/locale_zh_CN.go +++ b/experimental/locale/locale_zh_CN.go @@ -16,6 +16,9 @@ func init() { TailscaleStopped: "已停止", TailscaleStarting: "启动中", TailscaleRunning: "运行中", + TaildropReceiving: "正在接收来自 %[2]s 的 %[1]s", + TaildropReceived: "已收到来自 %[2]s 的 %[1]s", + TaildropSendCanceled: "发送 %s 已被接收方取消", VPNConnecting: "正在连接", VPNAuthentication: "需要认证", VPNConnected: "已连接", diff --git a/experimental/locale/locale_zh_TW.go b/experimental/locale/locale_zh_TW.go index efd8ac4c..07cc6cad 100644 --- a/experimental/locale/locale_zh_TW.go +++ b/experimental/locale/locale_zh_TW.go @@ -14,6 +14,9 @@ func init() { TailscaleStopped: "已停止", TailscaleStarting: "啟動中", TailscaleRunning: "執行中", + TaildropReceiving: "正在接收來自 %[2]s 的 %[1]s", + TaildropReceived: "已收到來自 %[2]s 的 %[1]s", + TaildropSendCanceled: "傳送 %s 已被接收方取消", VPNConnecting: "正在連線", VPNAuthentication: "需要認證", VPNConnected: "已連線", diff --git a/go.mod b/go.mod index 30d7730d..53c64618 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( github.com/sagernet/sing-usbip v0.0.0-20260813125128-908a3a2fa917 github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 - github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.2 + github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.3 github.com/sagernet/wireguard-go v0.0.5-0.20260810121456-c6c8a831ef70 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index c0bfc359..53326a4c 100644 --- a/go.sum +++ b/go.sum @@ -348,8 +348,8 @@ github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkV github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= -github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.2 h1:JXYUrqxvIYGVVvTz0xzYR6DufhLBfnV5wh5lwhbdJVM= -github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.2/go.mod h1:WLUSOPmTcf7VN9gLCe01qUSIvD+/cKC177neENyZPkI= +github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.3 h1:c7jWyEt7n3WdbDxkc7EqmPQtPT+Hct2l1CtmEDztMuo= +github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.3/go.mod h1:WLUSOPmTcf7VN9gLCe01qUSIvD+/cKC177neENyZPkI= github.com/sagernet/wireguard-go v0.0.5-0.20260810121456-c6c8a831ef70 h1:WTVgbkDDGnZqxIUnBsE9QyJz2dtSaeoh9mFwxmQqfgI= github.com/sagernet/wireguard-go v0.0.5-0.20260810121456-c6c8a831ef70/go.mod h1:er10sELpmzLXq7S7Pbc1Zsbyapcr+/gxNAHKTo6fzVA= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= diff --git a/option/tailscale.go b/option/tailscale.go index e0b9e8fd..1142b2e0 100644 --- a/option/tailscale.go +++ b/option/tailscale.go @@ -33,6 +33,7 @@ type TailscaleEndpointOptions struct { SystemInterfaceMTU uint32 `json:"system_interface_mtu,omitempty"` UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` SSHServer *TailscaleSSHServerOptions `json:"ssh_server,omitempty"` + TaildropDirectory string `json:"taildrop_directory,omitempty"` } type _TailscaleSSHServerOptions struct { diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 31628ff4..fb4d491c 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -47,6 +47,7 @@ import ( tailscaleroot "github.com/sagernet/tailscale" _ "github.com/sagernet/tailscale/feature/relayserver" "github.com/sagernet/tailscale/ipn" + "github.com/sagernet/tailscale/ipn/ipnlocal" tsDNS "github.com/sagernet/tailscale/net/dns" "github.com/sagernet/tailscale/net/netmon" "github.com/sagernet/tailscale/net/netns" @@ -117,6 +118,8 @@ type Endpoint struct { sshServerInstance *tailssh.Server sshServerOptions *option.TailscaleSSHServerOptions + taildrop *taildropManager + localBackend *ipnlocal.LocalBackend systemInterface bool systemInterfaceName string @@ -181,6 +184,12 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL } dialerQueryOptions := outboundDialer.(dialer.ResolveDialer).QueryOptions() dnsRouter := service.FromContext[adapter.DNSRouter](ctx) + taildropDirectory := options.TaildropDirectory + if taildropDirectory == "" { + taildropDirectory = "Taildrop" + } + taildropDirectory = filemanager.BasePath(ctx, os.ExpandEnv(taildropDirectory)) + taildropDirectory, _ = filepath.Abs(taildropDirectory) return &Endpoint{ Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil), ctx: ctx, @@ -231,6 +240,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL relayServerPort: options.RelayServerPort, relayServerStaticEndpoints: options.RelayServerStaticEndpoints, sshServerOptions: options.SSHServer, + taildrop: newTaildropManager(ctx, logger, tag, taildropDirectory, platformInterface), udpTimeout: udpTimeout, icmpTimeout: C.ICMPTimeout, systemInterface: options.SystemInterface, @@ -247,6 +257,12 @@ func (t *Endpoint) Start(stage adapter.StartStage) error { if mkdirErr != nil { return E.Cause(mkdirErr, "create state directory") } + if !version.IsAppleTV() { + mkdirErr = filemanager.MkdirAll(t.ctx, t.taildrop.directory, 0o700) + if mkdirErr != nil { + return E.Cause(mkdirErr, "create taildrop directory") + } + } t.server.PeerDNSQueryHandler = (*peerDNSQueryHandler)(t) case adapter.StartStateStart: return t.start() @@ -400,7 +416,13 @@ func (t *Endpoint) postStart() error { }, true }) } - wgEngine := t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine) + localBackend := t.server.ExportLocalBackend() + t.localBackend = localBackend + if !version.IsAppleTV() { + registerTaildropEndpoint(localBackend, t) + go t.taildrop.start() + } + wgEngine := localBackend.ExportEngine().(wgengine.ExportedUserspaceEngine) wgEngine.SetOnReconfigListener(t.onReconfig) t.wgEngine = wgEngine @@ -689,6 +711,11 @@ func (t *Endpoint) Logout(ctx context.Context) error { func (t *Endpoint) Close() error { var err error t.started.Store(false) + if t.localBackend != nil { + unregisterTaildropEndpoint(t.localBackend) + t.localBackend = nil + } + t.taildrop.close() if t.icmpForwarder != nil { t.icmpForwarder.Close() t.icmpForwarder = nil diff --git a/protocol/tailscale/ping.go b/protocol/tailscale/ping.go index 8bb0476b..5d046a91 100644 --- a/protocol/tailscale/ping.go +++ b/protocol/tailscale/ping.go @@ -48,6 +48,7 @@ func convertPingResult(result *ipnstate.PingResult) *adapter.TailscalePingResult LatencyMs: result.LatencySeconds * 1000, IsDirect: result.Endpoint != "", Endpoint: result.Endpoint, + PeerRelay: result.PeerRelay, DERPRegionID: int32(result.DERPRegionID), DERPRegionCode: result.DERPRegionCode, Error: result.Err, diff --git a/protocol/tailscale/platform_tvos.go b/protocol/tailscale/platform_tvos.go new file mode 100644 index 00000000..c9875dbb --- /dev/null +++ b/protocol/tailscale/platform_tvos.go @@ -0,0 +1,9 @@ +//go:build tvos + +package tailscale + +import "github.com/sagernet/tailscale/version" + +func init() { + version.SetAppleTV() +} diff --git a/protocol/tailscale/status.go b/protocol/tailscale/status.go index 62f3b532..17b6d640 100644 --- a/protocol/tailscale/status.go +++ b/protocol/tailscale/status.go @@ -37,9 +37,36 @@ func (t *Endpoint) SubscribeTailscaleStatus(ctx context.Context, fn func(*adapte status := localBackend.Status() result := convertTailscaleStatus(status) result.KeyAuth = t.keyAuth + canShareFiles, taildropTargets := t.taildropTargets() + result.CanShareFiles = canShareFiles + result.WaitingFileCount = t.taildrop.waitingFileCount() + result.ReceivingFileCount = t.taildrop.receivingFileCount() + result.UnreadFileCount = t.taildrop.unreadFileCount() + if len(taildropTargets) > 0 { + for _, group := range result.UserGroups { + for _, peer := range group.Peers { + peer.CanReceiveFiles = taildropTargets[peer.StableID] + } + } + } fn(result) } }() + fileSignal := make(chan struct{}, 1) + watchErr := t.taildrop.watch(t.taildrop.fileWatchers, fileSignal) + if watchErr == nil { + defer t.taildrop.unwatch(t.taildrop.fileWatchers, fileSignal) + go func() { + for { + select { + case <-ctx.Done(): + return + case <-fileSignal: + scheduleUpdate() + } + } + }() + } scheduleUpdate() for { var busError string diff --git a/protocol/tailscale/taildrop.go b/protocol/tailscale/taildrop.go new file mode 100644 index 00000000..8c7f5d2d --- /dev/null +++ b/protocol/tailscale/taildrop.go @@ -0,0 +1,1011 @@ +//go:build with_gvisor + +package tailscale + +import ( + "cmp" + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "regexp" + "runtime" + "slices" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + "unicode/utf8" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/experimental/locale" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/service/filemanager" + "github.com/sagernet/tailscale/ipn/ipnlocal" + "github.com/sagernet/tailscale/tailcfg" + "github.com/sagernet/tailscale/util/backoff" + "github.com/sagernet/tailscale/util/httphdr" +) + +const ( + taildropPartialSuffix = ".partial" + taildropDeletedSuffix = ".deleted" + taildropBlockSize = 64 << 10 + taildropDeleteDelay = time.Hour + taildropDeleteRetryDelay = 5 * time.Second + taildropRejectDrainTimeout = 3 * time.Second + taildropNotificationTypeID = 11 +) + +var ( + errTaildropFileExists = E.New("taildrop: file already exists") + errTaildropCanceled = E.New("taildrop: canceled by receiver") +) + +var ( + taildropAccess sync.RWMutex + taildropEndpoints = make(map[*ipnlocal.LocalBackend]*Endpoint) +) + +func init() { + ipnlocal.RegisterPeerAPIHandler("/v0/put/", handleTaildropRequest) +} + +func registerTaildropEndpoint(backend *ipnlocal.LocalBackend, endpoint *Endpoint) { + taildropAccess.Lock() + taildropEndpoints[backend] = endpoint + taildropAccess.Unlock() +} + +func unregisterTaildropEndpoint(backend *ipnlocal.LocalBackend) { + taildropAccess.Lock() + delete(taildropEndpoints, backend) + taildropAccess.Unlock() +} + +func handleTaildropRequest(handler ipnlocal.PeerAPIHandler, w http.ResponseWriter, r *http.Request) { + taildropAccess.RLock() + endpoint := taildropEndpoints[handler.LocalBackend()] + taildropAccess.RUnlock() + if endpoint == nil { + http.Error(w, "taildrop not available", http.StatusForbidden) + return + } + endpoint.taildrop.handlePeerRequest(handler, w, r) +} + +type taildropIncomingKey struct { + senderID string + name string +} + +type taildropIncomingFile struct { + manager *taildropManager + writer io.Writer + interrupt func() + senderName string + started time.Time + size int64 + canceled atomic.Bool + + access sync.Mutex + copied int64 + lastNotify time.Time +} + +func (f *taildropIncomingFile) Write(content []byte) (int, error) { + if f.canceled.Load() { + return 0, errTaildropCanceled + } + n, err := f.writer.Write(content) + if n > 0 { + var notify bool + f.access.Lock() + f.copied += int64(n) + now := time.Now() + if now.Sub(f.lastNotify) >= time.Second { + f.lastNotify = now + notify = true + } + f.access.Unlock() + if notify { + f.manager.notifyInboxUpdated() + } + } + return n, err +} + +type taildropManager struct { + ctx context.Context + logger logger.ContextLogger + tag string + directory string + platformInterface adapter.PlatformInterface + + renameAccess sync.Mutex + + totalReceived atomic.Int64 + emptySince atomic.Int64 + + access sync.Mutex + closed bool + incoming map[taildropIncomingKey]*taildropIncomingFile + senderNames map[string]string + unreadFiles map[string]bool + deleteTimers map[string]*time.Timer + inboxWatchers map[chan struct{}]bool + fileWatchers map[chan struct{}]bool +} + +func newTaildropManager(ctx context.Context, logger logger.ContextLogger, tag string, directory string, platformInterface adapter.PlatformInterface) *taildropManager { + manager := &taildropManager{ + ctx: ctx, + logger: logger, + tag: tag, + directory: directory, + platformInterface: platformInterface, + incoming: make(map[taildropIncomingKey]*taildropIncomingFile), + senderNames: make(map[string]string), + unreadFiles: make(map[string]bool), + deleteTimers: make(map[string]*time.Timer), + inboxWatchers: make(map[chan struct{}]bool), + fileWatchers: make(map[chan struct{}]bool), + } + manager.emptySince.Store(-1) + return manager +} + +func (m *taildropManager) start() { + entries, err := os.ReadDir(m.directory) + if err != nil { + return + } + now := time.Now() + for _, entry := range entries { + if !entry.Type().IsRegular() { + continue + } + name := entry.Name() + switch { + case strings.HasSuffix(name, taildropPartialSuffix): + information, infoErr := entry.Info() + if infoErr != nil { + continue + } + m.scheduleDelete(name, taildropDeleteDelay-now.Sub(information.ModTime())) + case strings.HasSuffix(name, taildropDeletedSuffix): + baseName := strings.TrimSuffix(name, taildropDeletedSuffix) + baseErr := os.Remove(filepath.Join(m.directory, baseName)) + if baseErr == nil || os.IsNotExist(baseErr) { + markerErr := os.Remove(filepath.Join(m.directory, name)) + if markerErr == nil || os.IsNotExist(markerErr) { + continue + } + } + m.scheduleDelete(name, taildropDeleteDelay) + } + } +} + +func (m *taildropManager) close() { + m.access.Lock() + m.closed = true + for _, timer := range m.deleteTimers { + timer.Stop() + } + clear(m.deleteTimers) + watchers := make([]chan struct{}, 0, len(m.inboxWatchers)+len(m.fileWatchers)) + for signal := range m.inboxWatchers { + watchers = append(watchers, signal) + } + for signal := range m.fileWatchers { + watchers = append(watchers, signal) + } + m.access.Unlock() + for _, signal := range watchers { + select { + case signal <- struct{}{}: + default: + } + } +} + +func (m *taildropManager) scheduleDelete(name string, delay time.Duration) { + if delay < 0 { + delay = 0 + } + m.access.Lock() + defer m.access.Unlock() + if m.closed { + return + } + if _, exists := m.deleteTimers[name]; exists { + return + } + m.deleteTimers[name] = time.AfterFunc(delay, func() { + m.deleteExpired(name) + }) +} + +func (m *taildropManager) cancelDelete(name string) { + m.access.Lock() + defer m.access.Unlock() + timer, exists := m.deleteTimers[name] + if exists { + timer.Stop() + delete(m.deleteTimers, name) + } +} + +func (m *taildropManager) deleteExpired(name string) { + m.access.Lock() + delete(m.deleteTimers, name) + closed := m.closed + var active bool + if nameAndSender, isPartial := strings.CutSuffix(name, taildropPartialSuffix); isPartial { + separatorIndex := strings.LastIndexByte(nameAndSender, '.') + if separatorIndex > 0 { + key := taildropIncomingKey{senderID: nameAndSender[separatorIndex+1:], name: nameAndSender[:separatorIndex]} + active = m.incoming[key] != nil + } + } + m.access.Unlock() + if closed { + return + } + if active { + m.scheduleDelete(name, taildropDeleteDelay) + return + } + if baseName, isMarker := strings.CutSuffix(name, taildropDeletedSuffix); isMarker { + baseErr := os.Remove(filepath.Join(m.directory, baseName)) + if baseErr != nil && !os.IsNotExist(baseErr) { + m.scheduleDelete(name, taildropDeleteDelay) + return + } + m.access.Lock() + delete(m.senderNames, baseName) + delete(m.unreadFiles, baseName) + m.access.Unlock() + } + err := os.Remove(filepath.Join(m.directory, name)) + if err != nil && !os.IsNotExist(err) { + m.scheduleDelete(name, taildropDeleteDelay) + } +} + +func (m *taildropManager) watch(watchers map[chan struct{}]bool, signal chan struct{}) error { + m.access.Lock() + defer m.access.Unlock() + if m.closed { + return os.ErrClosed + } + watchers[signal] = true + return nil +} + +func (m *taildropManager) isClosed() bool { + m.access.Lock() + defer m.access.Unlock() + return m.closed +} + +func (m *taildropManager) unwatch(watchers map[chan struct{}]bool, signal chan struct{}) { + m.access.Lock() + delete(watchers, signal) + m.access.Unlock() +} + +func (m *taildropManager) notifyInboxUpdated() { + m.access.Lock() + defer m.access.Unlock() + for signal := range m.inboxWatchers { + select { + case signal <- struct{}{}: + default: + } + } +} + +func (m *taildropManager) notifyStatusChanged() { + m.access.Lock() + defer m.access.Unlock() + for signal := range m.fileWatchers { + select { + case signal <- struct{}{}: + default: + } + } +} + +func (m *taildropManager) notifyFilesChanged() { + m.notifyStatusChanged() + m.notifyInboxUpdated() +} + +func (m *taildropManager) markInboxRead() { + m.access.Lock() + if len(m.unreadFiles) == 0 { + m.access.Unlock() + return + } + clear(m.unreadFiles) + m.access.Unlock() + m.notifyStatusChanged() +} + +var errTaildropInvalidFileName = E.New("taildrop: invalid filename") + +func validateTaildropFileName(name string) error { + if !utf8.ValidString(name) || + name == "." || + len(name) > 255 || + strings.ContainsRune(name, 0) || + filepath.Base(name) != name || + !filepath.IsLocal(name) || + strings.HasSuffix(name, taildropPartialSuffix) || + strings.HasSuffix(name, taildropDeletedSuffix) { + return errTaildropInvalidFileName + } + return nil +} + +var ( + taildropExtensionSuffix = regexp.MustCompile(`(\.[a-zA-Z0-9]{0,3}[a-zA-Z][a-zA-Z0-9]{0,3})*$`) + taildropNumberSuffix = regexp.MustCompile(` \([0-9]+\)`) +) + +func nextFileName(name string) string { + extension := taildropExtensionSuffix.FindString(strings.TrimPrefix(name, ".")) + name = strings.TrimSuffix(name, extension) + var sequence uint64 + if taildropNumberSuffix.MatchString(name) { + separatorIndex := strings.LastIndex(name, " (") + sequence, _ = strconv.ParseUint(name[separatorIndex+len(" ("):len(name)-len(")")], 10, 64) + if sequence > 0 { + name = name[:separatorIndex] + } + } + return name + " (" + strconv.FormatUint(sequence+1, 10) + ")" + extension +} + +func (m *taildropManager) putFile(senderID string, senderName string, baseName string, content io.Reader, offset int64, declaredLength int64, interrupt func()) error { + err := validateTaildropFileName(baseName) + if err != nil { + return err + } + partialName := baseName + "." + senderID + taildropPartialSuffix + m.cancelDelete(partialName) + totalSize := int64(-1) + if declaredLength >= 0 { + totalSize = offset + declaredLength + } + key := taildropIncomingKey{senderID: senderID, name: baseName} + incoming := &taildropIncomingFile{ + manager: m, + interrupt: interrupt, + senderName: senderName, + started: time.Now(), + size: totalSize, + copied: offset, + } + m.access.Lock() + if m.incoming[key] != nil { + m.access.Unlock() + return errTaildropFileExists + } + m.incoming[key] = incoming + m.access.Unlock() + m.notifyFilesChanged() + receivingName := "receiving/" + senderID + "/" + baseName + m.sendNotification(receivingName, fmt.Sprintf(locale.FromContext(m.ctx).TaildropReceiving, baseName, senderName)) + defer m.cancelNotification(receivingName) + defer func() { + m.access.Lock() + delete(m.incoming, key) + m.access.Unlock() + m.notifyFilesChanged() + }() + partialPath := filepath.Join(m.directory, partialName) + file, err := filemanager.OpenFile(m.ctx, partialPath, os.O_CREATE|os.O_RDWR, 0o666) + if err != nil { + m.scheduleDelete(partialName, taildropDeleteDelay) + return E.Cause(err, "taildrop: create partial file") + } + if offset == 0 { + err = file.Truncate(0) + if err != nil { + err = E.Cause(err, "taildrop: truncate partial file") + } + } else { + var currentSize int64 + currentSize, err = file.Seek(0, io.SeekEnd) + if err != nil { + err = E.Cause(err, "taildrop: seek partial file") + } + if err == nil && (offset < 0 || offset > currentSize) { + err = E.New("taildrop: offset ", offset, " out of range") + } + if err == nil { + _, err = file.Seek(offset, io.SeekStart) + if err != nil { + err = E.Cause(err, "taildrop: seek partial file") + } + } + if err == nil { + err = file.Truncate(offset) + if err != nil { + err = E.Cause(err, "taildrop: truncate partial file") + } + } + } + if err != nil { + file.Close() + m.scheduleDelete(partialName, taildropDeleteDelay) + return err + } + incoming.writer = file + copied, err := io.Copy(incoming, content) + if err != nil && incoming.canceled.Load() { + err = errTaildropCanceled + } + if err == nil && declaredLength >= 0 && copied != declaredLength { + err = E.New("taildrop: copied ", copied, " bytes, expected ", declaredLength) + } + if err != nil { + file.Close() + if errors.Is(err, errTaildropCanceled) { + removeErr := os.Remove(partialPath) + if removeErr != nil && !os.IsNotExist(removeErr) { + m.scheduleDelete(partialName, taildropDeleteDelay) + } + } else { + m.scheduleDelete(partialName, taildropDeleteDelay) + } + return err + } + err = file.Close() + if err != nil { + m.scheduleDelete(partialName, taildropDeleteDelay) + return E.Cause(err, "taildrop: close partial file") + } + finalName, err := m.renamePartial(partialPath, baseName) + if err != nil { + m.scheduleDelete(partialName, taildropDeleteDelay) + return err + } + m.access.Lock() + m.senderNames[finalName] = senderName + m.unreadFiles[finalName] = true + m.access.Unlock() + m.totalReceived.Add(1) + m.logger.Info("taildrop: received ", finalName, " (", offset+copied, " bytes) from ", senderName) + m.notifyFilesChanged() + m.sendNotification(finalName, fmt.Sprintf(locale.FromContext(m.ctx).TaildropReceived, finalName, senderName)) + return nil +} + +func (m *taildropManager) renamePartial(partialPath string, baseName string) (string, error) { + partialInfo, err := os.Stat(partialPath) + if err != nil { + return "", E.Cause(err, "taildrop: stat partial file") + } + finalName := baseName + for range 10 { + finalPath := filepath.Join(m.directory, finalName) + m.renameAccess.Lock() + _, statErr := os.Stat(finalPath) + if os.IsNotExist(statErr) { + renameErr := filemanager.Rename(m.ctx, partialPath, finalPath) + m.renameAccess.Unlock() + if renameErr != nil { + return "", E.Cause(renameErr, "taildrop: rename partial file") + } + return finalName, nil + } + m.renameAccess.Unlock() + if statErr != nil { + return "", E.Cause(statErr, "taildrop: stat received file") + } + identical, compareErr := filesIdentical(partialPath, finalPath, partialInfo.Size()) + if compareErr != nil { + return "", E.Cause(compareErr, "taildrop: compare received file") + } + if identical { + removeErr := os.Remove(partialPath) + if removeErr != nil { + return "", E.Cause(removeErr, "taildrop: remove partial file") + } + return finalName, nil + } + finalName = nextFileName(finalName) + } + return "", E.New("taildrop: too many rename attempts for ", baseName) +} + +func filesIdentical(leftPath string, rightPath string, leftSize int64) (bool, error) { + rightInfo, err := os.Stat(rightPath) + if err != nil { + return false, err + } + if rightInfo.Size() != leftSize { + return false, nil + } + leftSum, err := fileChecksum(leftPath) + if err != nil { + return false, err + } + rightSum, err := fileChecksum(rightPath) + if err != nil { + return false, err + } + return leftSum == rightSum, nil +} + +func fileChecksum(filePath string) ([sha256.Size]byte, error) { + var sum [sha256.Size]byte + file, err := os.Open(filePath) + if err != nil { + return sum, err + } + defer file.Close() + hash := sha256.New() + _, err = io.Copy(hash, file) + if err != nil { + return sum, err + } + copy(sum[:], hash.Sum(nil)) + return sum, nil +} + +func (m *taildropManager) sendNotification(name string, body string) { + if m.platformInterface == nil || !m.platformInterface.UsePlatformNotification() { + return + } + err := m.platformInterface.SendNotification(&adapter.Notification{ + Identifier: "taildrop/" + m.tag + "/" + name, + TypeName: "Taildrop Notifications", + TypeID: taildropNotificationTypeID, + Title: "Taildrop", + Body: body, + OpenURL: "sing-box:taildrop?endpoint=" + percentEscape(m.tag), + }) + if err != nil { + m.logger.Error("taildrop: send notification: ", err) + } +} + +func (m *taildropManager) cancelNotification(name string) { + if m.platformInterface == nil || !m.platformInterface.UsePlatformNotification() { + return + } + err := m.platformInterface.CancelNotification("taildrop/"+m.tag+"/"+name, taildropNotificationTypeID) + if err != nil { + m.logger.Error("taildrop: cancel notification: ", err) + } +} + +func percentEscape(value string) string { + return strings.ReplaceAll(url.QueryEscape(value), "+", "%20") +} + +func (m *taildropManager) receivingFileCount() int32 { + m.access.Lock() + defer m.access.Unlock() + return int32(len(m.incoming)) +} + +func (m *taildropManager) cancelReceiving(senderID string, baseName string) { + key := taildropIncomingKey{senderID: senderID, name: baseName} + m.access.Lock() + incoming := m.incoming[key] + m.access.Unlock() + if incoming == nil { + return + } + incoming.canceled.Store(true) + if incoming.interrupt != nil { + incoming.interrupt() + } +} + +func (m *taildropManager) inbox() *adapter.TaildropInbox { + inbox := &adapter.TaildropInbox{} + entries, err := os.ReadDir(m.directory) + if err == nil { + deletedNames := make(map[string]bool) + fileNames := make(map[string]bool, len(entries)) + for _, entry := range entries { + if !entry.Type().IsRegular() { + continue + } + name := entry.Name() + fileNames[name] = true + baseName, isMarker := strings.CutSuffix(name, taildropDeletedSuffix) + if isMarker { + deletedNames[baseName] = true + } + } + m.access.Lock() + for _, entry := range entries { + if !entry.Type().IsRegular() { + continue + } + name := entry.Name() + if strings.HasSuffix(name, taildropPartialSuffix) || + strings.HasSuffix(name, taildropDeletedSuffix) || + deletedNames[name] { + continue + } + information, infoErr := entry.Info() + if infoErr != nil { + continue + } + inbox.Files = append(inbox.Files, &adapter.TaildropFile{ + Name: name, + Size: information.Size(), + SenderName: m.senderNames[name], + ModifiedAt: information.ModTime().Unix(), + }) + } + for name := range m.senderNames { + if !fileNames[name] { + delete(m.senderNames, name) + delete(m.unreadFiles, name) + } + } + m.access.Unlock() + } + slices.SortFunc(inbox.Files, func(left, right *adapter.TaildropFile) int { + if left.ModifiedAt != right.ModifiedAt { + return cmp.Compare(right.ModifiedAt, left.ModifiedAt) + } + return strings.Compare(left.Name, right.Name) + }) + m.access.Lock() + receiving := make([]*taildropIncomingFile, 0, len(m.incoming)) + receivingKeys := make(map[*taildropIncomingFile]taildropIncomingKey, len(m.incoming)) + for key, incoming := range m.incoming { + receiving = append(receiving, incoming) + receivingKeys[incoming] = key + } + m.access.Unlock() + slices.SortFunc(receiving, func(left, right *taildropIncomingFile) int { + return left.started.Compare(right.started) + }) + for _, incoming := range receiving { + key := receivingKeys[incoming] + incoming.access.Lock() + inbox.Receiving = append(inbox.Receiving, &adapter.TaildropReceivingFile{ + Name: key.name, + Size: incoming.size, + ReceivedBytes: incoming.copied, + SenderID: key.senderID, + SenderName: incoming.senderName, + }) + incoming.access.Unlock() + } + return inbox +} + +func (m *taildropManager) waitingFileCount() int32 { + totalReceived := m.totalReceived.Load() + if totalReceived == m.emptySince.Load() { + return 0 + } + entries, err := os.ReadDir(m.directory) + if err != nil { + return 0 + } + deletedNames := make(map[string]bool) + names := make([]string, 0, len(entries)) + for _, entry := range entries { + if !entry.Type().IsRegular() { + continue + } + name := entry.Name() + baseName, isMarker := strings.CutSuffix(name, taildropDeletedSuffix) + if isMarker { + deletedNames[baseName] = true + continue + } + if strings.HasSuffix(name, taildropPartialSuffix) { + continue + } + names = append(names, name) + } + var count int32 + for _, name := range names { + if deletedNames[name] { + continue + } + count++ + } + if count == 0 { + m.emptySince.Store(totalReceived) + } + return count +} + +func (m *taildropManager) unreadFileCount() int32 { + m.access.Lock() + defer m.access.Unlock() + return int32(len(m.unreadFiles)) +} + +func (m *taildropManager) partialFileNames(senderID string) []string { + fileNames := make([]string, 0) + entries, err := os.ReadDir(m.directory) + if err != nil { + return fileNames + } + suffix := "." + senderID + taildropPartialSuffix + for _, entry := range entries { + if entry.Type().IsRegular() && strings.HasSuffix(entry.Name(), suffix) { + fileNames = append(fileNames, entry.Name()) + } + } + return fileNames +} + +func (m *taildropManager) openFile(baseName string) (io.ReadCloser, int64, error) { + err := validateTaildropFileName(baseName) + if err != nil { + return nil, 0, err + } + _, markerErr := os.Stat(filepath.Join(m.directory, baseName+taildropDeletedSuffix)) + if markerErr == nil { + return nil, 0, E.Extend(os.ErrNotExist, baseName) + } + file, err := os.Open(filepath.Join(m.directory, baseName)) + if err != nil { + return nil, 0, err + } + information, err := file.Stat() + if err != nil { + file.Close() + return nil, 0, err + } + return file, information.Size(), nil +} + +func (m *taildropManager) deleteFile(baseName string) error { + err := validateTaildropFileName(baseName) + if err != nil { + return err + } + var ( + removeBackoff *backoff.Backoff + startedAt = time.Now() + ) + for { + err = os.Remove(filepath.Join(m.directory, baseName)) + if err == nil || os.IsNotExist(err) { + break + } + if runtime.GOOS != "windows" { + return E.Cause(err, "taildrop: delete received file") + } + if time.Since(startedAt) < taildropDeleteRetryDelay { + if removeBackoff == nil { + removeBackoff = backoff.NewBackoff("taildrop-delete", func(format string, args ...any) { + m.logger.Debug(fmt.Sprintf(format, args...)) + }, time.Second) + } + removeBackoff.BackOff(m.ctx, err) + if m.ctx.Err() != nil { + return E.Cause(err, "taildrop: delete received file") + } + continue + } + marker, markerErr := filemanager.OpenFile(m.ctx, filepath.Join(m.directory, baseName+taildropDeletedSuffix), os.O_CREATE|os.O_WRONLY, 0o666) + if markerErr != nil { + return E.Cause(err, "taildrop: delete received file") + } + marker.Close() + m.scheduleDelete(baseName+taildropDeletedSuffix, taildropDeleteDelay) + break + } + m.access.Lock() + delete(m.senderNames, baseName) + delete(m.unreadFiles, baseName) + m.access.Unlock() + m.notifyFilesChanged() + return nil +} + +type taildropBlockChecksum struct { + Checksum string `json:"checksum"` + Algorithm string `json:"algo"` + Size int64 `json:"size"` +} + +func (m *taildropManager) handlePeerRequest(handler ipnlocal.PeerAPIHandler, w http.ResponseWriter, r *http.Request) { + if handler.Peer().UnsignedPeerAPIOnly() || + !(handler.IsSelfUntagged() || handler.PeerCaps().HasCapability(tailcfg.PeerCapabilityFileSharingSend)) { + http.Error(w, "Taildrop access denied", http.StatusForbidden) + return + } + if !handler.Self().CapMap().Contains(tailcfg.CapabilityFileSharing) { + http.Error(w, "file sharing not enabled by Tailscale admin", http.StatusForbidden) + return + } + escapedName, found := strings.CutPrefix(r.URL.EscapedPath(), "/v0/put/") + if !found { + http.Error(w, "invalid path", http.StatusBadRequest) + return + } + baseName, err := url.PathUnescape(escapedName) + if err != nil { + http.Error(w, errTaildropInvalidFileName.Error(), http.StatusBadRequest) + return + } + senderID := string(handler.Peer().StableID()) + if senderID == "" || validateTaildropFileName(senderID) != nil { + http.Error(w, "invalid peer identity", http.StatusForbidden) + return + } + switch r.Method { + case http.MethodGet: + if escapedName == "" { + err = json.NewEncoder(w).Encode(m.partialFileNames(senderID)) + if err != nil { + m.logger.Error("taildrop: write partial file list: ", err) + } + } else { + m.writePartialChecksums(w, senderID, baseName) + } + case http.MethodPut: + var offset int64 + rangeHeader := r.Header.Get("Range") + if rangeHeader != "" { + ranges, valid := httphdr.ParseRange(rangeHeader) + if !valid || len(ranges) != 1 || ranges[0].Length != 0 { + http.Error(w, "invalid Range header", http.StatusBadRequest) + return + } + offset = ranges[0].Start + } + responseController := http.NewResponseController(w) + err = m.putFile(senderID, handler.Peer().ComputedName(), baseName, r.Body, offset, r.ContentLength, func() { + _ = responseController.SetReadDeadline(time.Now()) + }) + if err == nil { + io.WriteString(w, "{}\n") + return + } + var ( + statusCode int + message string + ) + switch { + case errors.Is(err, errTaildropInvalidFileName): + statusCode = http.StatusBadRequest + message = err.Error() + case errors.Is(err, errTaildropFileExists): + statusCode = http.StatusConflict + message = err.Error() + case errors.Is(err, errTaildropCanceled): + m.logger.Debug("taildrop: receive ", baseName, ": ", err) + statusCode = http.StatusForbidden + message = err.Error() + default: + m.logger.Error("taildrop: receive ", baseName, ": ", err) + statusCode = http.StatusInternalServerError + message = "taildrop: receive failed" + } + // http.Error deletes Content-Length, so a response flushed before the + // handler returns is sent chunked and the sender cannot finish reading + // it until the handler returns; net/http then tears the connection down + // while the request body is unread, which aborts it with a reset and + // discards the response along with any pending retransmission of it. + // Write a complete response instead, then hold the connection open + // until the sender reads the response and closes it. + responseBody := message + "\n" + responseHeader := w.Header() + responseHeader.Set("Content-Type", "text/plain; charset=utf-8") + responseHeader.Set("Content-Length", strconv.Itoa(len(responseBody))) + if r.ProtoMajor == 1 { + responseHeader.Set("Connection", "close") + } + w.WriteHeader(statusCode) + io.WriteString(w, responseBody) + _ = responseController.Flush() + _ = responseController.SetReadDeadline(time.Now().Add(taildropRejectDrainTimeout)) + _, _ = io.Copy(io.Discard, r.Body) + default: + http.Error(w, "expected method GET or PUT", http.StatusMethodNotAllowed) + } +} + +func (m *taildropManager) writePartialChecksums(w http.ResponseWriter, senderID string, baseName string) { + if validateTaildropFileName(baseName) != nil { + http.Error(w, errTaildropInvalidFileName.Error(), http.StatusBadRequest) + return + } + file, err := os.Open(filepath.Join(m.directory, baseName+"."+senderID+taildropPartialSuffix)) + if err != nil { + if os.IsNotExist(err) { + return + } + http.Error(w, "taildrop: open partial file", http.StatusInternalServerError) + return + } + defer file.Close() + encoder := json.NewEncoder(w) + block := make([]byte, taildropBlockSize) + for { + n, readErr := io.ReadFull(file, block) + if readErr != nil && readErr != io.EOF && readErr != io.ErrUnexpectedEOF { + m.logger.Error("taildrop: read partial file: ", readErr) + return + } + if n == 0 { + return + } + sum := sha256.Sum256(block[:n]) + err = encoder.Encode(taildropBlockChecksum{ + Checksum: hex.EncodeToString(sum[:]), + Algorithm: "sha256", + Size: int64(n), + }) + if err != nil { + return + } + if readErr != nil { + return + } + } +} + +func (t *Endpoint) SubscribeTaildropInbox(ctx context.Context, fn func(*adapter.TaildropInbox)) error { + manager := t.taildrop + signal := make(chan struct{}, 1) + err := manager.watch(manager.inboxWatchers, signal) + if err != nil { + return err + } + defer manager.unwatch(manager.inboxWatchers, signal) + for { + fn(manager.inbox()) + select { + case <-ctx.Done(): + return ctx.Err() + case <-signal: + if manager.isClosed() { + return os.ErrClosed + } + } + } +} + +func (t *Endpoint) MarkTaildropInboxRead() error { + t.taildrop.markInboxRead() + return nil +} + +func (t *Endpoint) OpenTaildropFile(fileName string) (io.ReadCloser, int64, error) { + return t.taildrop.openFile(fileName) +} + +func (t *Endpoint) DeleteTaildropFile(fileName string) error { + return t.taildrop.deleteFile(fileName) +} + +func (t *Endpoint) CancelTaildropReceiving(senderID string, fileName string) error { + err := validateTaildropFileName(fileName) + if err != nil { + return err + } + t.taildrop.cancelReceiving(senderID, fileName) + return nil +} diff --git a/protocol/tailscale/taildrop_send.go b/protocol/tailscale/taildrop_send.go new file mode 100644 index 00000000..ef7d8fd4 --- /dev/null +++ b/protocol/tailscale/taildrop_send.go @@ -0,0 +1,202 @@ +//go:build with_gvisor + +package tailscale + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/sagernet/sing-box/experimental/locale" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/tailscale/ipn" + "github.com/sagernet/tailscale/tailcfg" +) + +func (t *Endpoint) SendTaildropFile(ctx context.Context, peerStableID string, fileName string, size int64, content io.Reader, progress func(sentBytes int64)) error { + if !t.started.Load() { + return E.New("Tailscale is not ready yet") + } + err := validateTaildropFileName(fileName) + if err != nil { + return err + } + localBackend := t.server.ExportLocalBackend() + if localBackend.State() != ipn.Running { + return E.New("taildrop: not connected to the tailnet") + } + nodeBackend := localBackend.NodeBackend() + self := nodeBackend.Self() + if !self.Valid() { + return E.New("taildrop: not connected to the tailnet") + } + if !self.CapMap().Contains(tailcfg.CapabilityFileSharing) { + return E.New("taildrop: file sharing not enabled by Tailscale admin") + } + var peer tailcfg.NodeView + for _, candidate := range nodeBackend.Peers() { + if string(candidate.StableID()) == peerStableID { + peer = candidate + break + } + } + if !peer.Valid() { + return E.New("taildrop: peer not found: ", peerStableID) + } + if peer.Hostinfo().OS() == "tvOS" { + return E.New("taildrop: peer cannot receive files") + } + if self.User() != peer.User() && !nodeBackend.PeerHasCap(peer, tailcfg.PeerCapabilityFileSharingTarget) { + return E.New("taildrop: peer is not a permitted file target") + } + peerAPIBase := nodeBackend.PeerAPIBase(peer) + if peerAPIBase == "" { + return E.New("taildrop: peer does not support peer API") + } + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network string, address string) (net.Conn, error) { + return t.DialContext(ctx, N.NetworkTCP, M.ParseSocksaddr(address)) + }, + }, + } + defer httpClient.CloseIdleConnections() + fileURL := peerAPIBase + "/v0/put/" + url.PathEscape(fileName) + offset, remaining := taildropResume(ctx, httpClient, fileURL, content) + request, err := http.NewRequestWithContext(ctx, http.MethodPut, fileURL, &taildropProgressReader{ + reader: remaining, + sent: offset, + progress: progress, + }) + if err != nil { + return err + } + if size >= 0 { + request.ContentLength = size - offset + } + if offset > 0 { + request.Header.Set("Range", "bytes="+strconv.FormatInt(offset, 10)+"-") + } + if progress != nil { + progress(offset) + } + response, err := httpClient.Do(request) + if err != nil { + return E.Cause(err, "taildrop: send ", fileName) + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + messageBytes, _ := io.ReadAll(io.LimitReader(response.Body, 1024)) + message := strings.TrimSpace(string(messageBytes)) + if message == errTaildropCanceled.Error() { + return E.New(fmt.Sprintf(locale.Current().TaildropSendCanceled, fileName)) + } + return E.New("taildrop: send ", fileName, ": peer responded ", response.Status, ": ", message) + } + _, err = io.Copy(io.Discard, response.Body) + if err != nil { + return err + } + return nil +} + +func taildropResume(ctx context.Context, httpClient *http.Client, fileURL string, content io.Reader) (int64, io.Reader) { + probeCtx, cancelProbe := context.WithTimeout(ctx, 10*time.Second) + defer cancelProbe() + request, err := http.NewRequestWithContext(probeCtx, http.MethodGet, fileURL, nil) + if err != nil { + return 0, content + } + response, err := httpClient.Do(request) + if err != nil { + return 0, content + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return 0, content + } + decoder := json.NewDecoder(response.Body) + var offset int64 + block := make([]byte, 0, taildropBlockSize) + for { + var remoteChecksum taildropBlockChecksum + err = decoder.Decode(&remoteChecksum) + if err != nil || remoteChecksum.Algorithm != "sha256" || remoteChecksum.Size < 0 || remoteChecksum.Size > taildropBlockSize { + break + } + var n int + n, err = io.ReadFull(content, block[:remoteChecksum.Size]) + block = block[:n] + if n == 0 || (err != nil && err != io.EOF && err != io.ErrUnexpectedEOF) { + break + } + localSum := sha256.Sum256(block) + if hex.EncodeToString(localSum[:]) != remoteChecksum.Checksum { + break + } + offset += int64(n) + block = block[:0] + } + if len(block) > 0 { + return offset, io.MultiReader(bytes.NewReader(block), content) + } + return offset, content +} + +type taildropProgressReader struct { + reader io.Reader + sent int64 + progress func(sentBytes int64) +} + +func (r *taildropProgressReader) Read(buffer []byte) (int, error) { + n, err := r.reader.Read(buffer) + if n > 0 { + r.sent += int64(n) + if r.progress != nil { + r.progress(r.sent) + } + } + return n, err +} + +func (t *Endpoint) taildropTargets() (canShareFiles bool, targets map[string]bool) { + if !t.started.Load() { + return false, nil + } + localBackend := t.server.ExportLocalBackend() + if localBackend.State() != ipn.Running { + return false, nil + } + nodeBackend := localBackend.NodeBackend() + self := nodeBackend.Self() + if !self.Valid() || !self.CapMap().Contains(tailcfg.CapabilityFileSharing) { + return false, nil + } + targets = make(map[string]bool) + for _, peer := range nodeBackend.Peers() { + if !peer.Valid() || peer.Hostinfo().OS() == "tvOS" { + continue + } + if self.User() != peer.User() && !nodeBackend.PeerHasCap(peer, tailcfg.PeerCapabilityFileSharingTarget) { + continue + } + if !nodeBackend.PeerHasPeerAPI(peer) { + continue + } + targets[string(peer.StableID())] = true + } + return true, targets +}