tools: Tailscale status

This commit is contained in:
世界
2026-04-09 19:34:27 +08:00
parent cfcc766d74
commit 5e7e58f5e9
10 changed files with 988 additions and 100 deletions

39
adapter/tailscale.go Normal file
View File

@@ -0,0 +1,39 @@
package adapter
import "context"
type TailscaleStatusProvider interface {
SubscribeTailscaleStatus(ctx context.Context, fn func(*TailscaleEndpointStatus)) error
}
type TailscaleEndpointStatus struct {
BackendState string
AuthURL string
NetworkName string
MagicDNSSuffix string
Self *TailscalePeer
Users map[int64]*TailscaleUser
Peers []*TailscalePeer
}
type TailscalePeer struct {
HostName string
DNSName string
OS string
TailscaleIPs []string
Online bool
ExitNode bool
ExitNodeOption bool
Active bool
RxBytes int64
TxBytes int64
UserID int64
KeyExpiry int64
}
type TailscaleUser struct {
ID int64
LoginName string
DisplayName string
ProfilePicURL string
}

View File

@@ -6,12 +6,14 @@ import (
"runtime"
"sync"
"time"
"unsafe"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/networkquality"
"github.com/sagernet/sing-box/common/stun"
"github.com/sagernet/sing-box/common/urltest"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/clashapi"
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
"github.com/sagernet/sing-box/experimental/deprecated"
@@ -707,7 +709,7 @@ func (s *StartedService) TriggerDebugCrash(ctx context.Context, request *DebugCr
switch request.Type {
case DebugCrashRequest_GO:
time.AfterFunc(200*time.Millisecond, func() {
panic("debug go crash")
*(*int)(unsafe.Pointer(uintptr(0))) = 0
})
case DebugCrashRequest_NATIVE:
err := s.handler.TriggerNativeCrash()
@@ -1287,6 +1289,142 @@ func (s *StartedService) StartSTUNTest(
})
}
func (s *StartedService) SubscribeTailscaleStatus(
_ *emptypb.Empty,
server grpc.ServerStreamingServer[TailscaleStatusUpdate],
) error {
err := s.waitForStarted(server.Context())
if err != nil {
return err
}
s.serviceAccess.RLock()
boxService := s.instance
s.serviceAccess.RUnlock()
endpointManager := service.FromContext[adapter.EndpointManager](boxService.ctx)
if endpointManager == nil {
return status.Error(codes.FailedPrecondition, "endpoint manager not available")
}
type tailscaleEndpoint struct {
tag string
provider adapter.TailscaleStatusProvider
}
var endpoints []tailscaleEndpoint
for _, endpoint := range endpointManager.Endpoints() {
if endpoint.Type() != C.TypeTailscale {
continue
}
provider, loaded := endpoint.(adapter.TailscaleStatusProvider)
if !loaded {
continue
}
endpoints = append(endpoints, tailscaleEndpoint{
tag: endpoint.Tag(),
provider: provider,
})
}
if len(endpoints) == 0 {
return status.Error(codes.NotFound, "no Tailscale endpoint found")
}
type taggedStatus struct {
tag string
status *adapter.TailscaleEndpointStatus
}
updates := make(chan taggedStatus, len(endpoints))
ctx, cancel := context.WithCancel(server.Context())
defer cancel()
var waitGroup sync.WaitGroup
for _, endpoint := range endpoints {
waitGroup.Add(1)
go func(tag string, provider adapter.TailscaleStatusProvider) {
defer waitGroup.Done()
_ = provider.SubscribeTailscaleStatus(ctx, func(endpointStatus *adapter.TailscaleEndpointStatus) {
select {
case updates <- taggedStatus{tag: tag, status: endpointStatus}:
case <-ctx.Done():
}
})
}(endpoint.tag, endpoint.provider)
}
go func() {
waitGroup.Wait()
close(updates)
}()
statuses := make(map[string]*adapter.TailscaleEndpointStatus, len(endpoints))
for update := range updates {
statuses[update.tag] = update.status
protoEndpoints := make([]*TailscaleEndpointStatus, 0, len(statuses))
for tag, endpointStatus := range statuses {
protoEndpoints = append(protoEndpoints, tailscaleEndpointStatusToProto(tag, endpointStatus))
}
sendErr := server.Send(&TailscaleStatusUpdate{
Endpoints: protoEndpoints,
})
if sendErr != nil {
return sendErr
}
}
return nil
}
func tailscaleEndpointStatusToProto(tag string, s *adapter.TailscaleEndpointStatus) *TailscaleEndpointStatus {
userGroupMap := make(map[int64]*TailscaleUserGroup)
for userID, user := range s.Users {
userGroupMap[userID] = &TailscaleUserGroup{
UserID: userID,
LoginName: user.LoginName,
DisplayName: user.DisplayName,
ProfilePicURL: user.ProfilePicURL,
}
}
for _, peer := range s.Peers {
protoPeer := tailscalePeerToProto(peer)
group, loaded := userGroupMap[peer.UserID]
if !loaded {
group = &TailscaleUserGroup{UserID: peer.UserID}
userGroupMap[peer.UserID] = group
}
group.Peers = append(group.Peers, protoPeer)
}
userGroups := make([]*TailscaleUserGroup, 0, len(userGroupMap))
for _, group := range userGroupMap {
userGroups = append(userGroups, group)
}
result := &TailscaleEndpointStatus{
EndpointTag: tag,
BackendState: s.BackendState,
AuthURL: s.AuthURL,
NetworkName: s.NetworkName,
MagicDNSSuffix: s.MagicDNSSuffix,
UserGroups: userGroups,
}
if s.Self != nil {
result.Self = tailscalePeerToProto(s.Self)
}
return result
}
func tailscalePeerToProto(peer *adapter.TailscalePeer) *TailscalePeer {
return &TailscalePeer{
HostName: peer.HostName,
DnsName: peer.DNSName,
Os: peer.OS,
TailscaleIPs: peer.TailscaleIPs,
Online: peer.Online,
ExitNode: peer.ExitNode,
ExitNodeOption: peer.ExitNodeOption,
Active: peer.Active,
RxBytes: peer.RxBytes,
TxBytes: peer.TxBytes,
KeyExpiry: peer.KeyExpiry,
}
}
func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() {
}

View File

@@ -2248,6 +2248,342 @@ func (x *STUNTestProgress) GetNatTypeSupported() bool {
return false
}
type TailscaleStatusUpdate struct {
state protoimpl.MessageState `protogen:"open.v1"`
Endpoints []*TailscaleEndpointStatus `protobuf:"bytes,1,rep,name=endpoints,proto3" json:"endpoints,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TailscaleStatusUpdate) Reset() {
*x = TailscaleStatusUpdate{}
mi := &file_daemon_started_service_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TailscaleStatusUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TailscaleStatusUpdate) ProtoMessage() {}
func (x *TailscaleStatusUpdate) ProtoReflect() protoreflect.Message {
mi := &file_daemon_started_service_proto_msgTypes[31]
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 TailscaleStatusUpdate.ProtoReflect.Descriptor instead.
func (*TailscaleStatusUpdate) Descriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{31}
}
func (x *TailscaleStatusUpdate) GetEndpoints() []*TailscaleEndpointStatus {
if x != nil {
return x.Endpoints
}
return nil
}
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"`
AuthURL string `protobuf:"bytes,3,opt,name=authURL,proto3" json:"authURL,omitempty"`
NetworkName string `protobuf:"bytes,4,opt,name=networkName,proto3" json:"networkName,omitempty"`
MagicDNSSuffix string `protobuf:"bytes,5,opt,name=magicDNSSuffix,proto3" json:"magicDNSSuffix,omitempty"`
Self *TailscalePeer `protobuf:"bytes,6,opt,name=self,proto3" json:"self,omitempty"`
UserGroups []*TailscaleUserGroup `protobuf:"bytes,7,rep,name=userGroups,proto3" json:"userGroups,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TailscaleEndpointStatus) Reset() {
*x = TailscaleEndpointStatus{}
mi := &file_daemon_started_service_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TailscaleEndpointStatus) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TailscaleEndpointStatus) ProtoMessage() {}
func (x *TailscaleEndpointStatus) ProtoReflect() protoreflect.Message {
mi := &file_daemon_started_service_proto_msgTypes[32]
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 TailscaleEndpointStatus.ProtoReflect.Descriptor instead.
func (*TailscaleEndpointStatus) Descriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{32}
}
func (x *TailscaleEndpointStatus) GetEndpointTag() string {
if x != nil {
return x.EndpointTag
}
return ""
}
func (x *TailscaleEndpointStatus) GetBackendState() string {
if x != nil {
return x.BackendState
}
return ""
}
func (x *TailscaleEndpointStatus) GetAuthURL() string {
if x != nil {
return x.AuthURL
}
return ""
}
func (x *TailscaleEndpointStatus) GetNetworkName() string {
if x != nil {
return x.NetworkName
}
return ""
}
func (x *TailscaleEndpointStatus) GetMagicDNSSuffix() string {
if x != nil {
return x.MagicDNSSuffix
}
return ""
}
func (x *TailscaleEndpointStatus) GetSelf() *TailscalePeer {
if x != nil {
return x.Self
}
return nil
}
func (x *TailscaleEndpointStatus) GetUserGroups() []*TailscaleUserGroup {
if x != nil {
return x.UserGroups
}
return nil
}
type TailscaleUserGroup struct {
state protoimpl.MessageState `protogen:"open.v1"`
UserID int64 `protobuf:"varint,1,opt,name=userID,proto3" json:"userID,omitempty"`
LoginName string `protobuf:"bytes,2,opt,name=loginName,proto3" json:"loginName,omitempty"`
DisplayName string `protobuf:"bytes,3,opt,name=displayName,proto3" json:"displayName,omitempty"`
ProfilePicURL string `protobuf:"bytes,4,opt,name=profilePicURL,proto3" json:"profilePicURL,omitempty"`
Peers []*TailscalePeer `protobuf:"bytes,5,rep,name=peers,proto3" json:"peers,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TailscaleUserGroup) Reset() {
*x = TailscaleUserGroup{}
mi := &file_daemon_started_service_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TailscaleUserGroup) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TailscaleUserGroup) ProtoMessage() {}
func (x *TailscaleUserGroup) ProtoReflect() protoreflect.Message {
mi := &file_daemon_started_service_proto_msgTypes[33]
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 TailscaleUserGroup.ProtoReflect.Descriptor instead.
func (*TailscaleUserGroup) Descriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{33}
}
func (x *TailscaleUserGroup) GetUserID() int64 {
if x != nil {
return x.UserID
}
return 0
}
func (x *TailscaleUserGroup) GetLoginName() string {
if x != nil {
return x.LoginName
}
return ""
}
func (x *TailscaleUserGroup) GetDisplayName() string {
if x != nil {
return x.DisplayName
}
return ""
}
func (x *TailscaleUserGroup) GetProfilePicURL() string {
if x != nil {
return x.ProfilePicURL
}
return ""
}
func (x *TailscaleUserGroup) GetPeers() []*TailscalePeer {
if x != nil {
return x.Peers
}
return nil
}
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"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TailscalePeer) Reset() {
*x = TailscalePeer{}
mi := &file_daemon_started_service_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TailscalePeer) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TailscalePeer) ProtoMessage() {}
func (x *TailscalePeer) ProtoReflect() protoreflect.Message {
mi := &file_daemon_started_service_proto_msgTypes[34]
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 TailscalePeer.ProtoReflect.Descriptor instead.
func (*TailscalePeer) Descriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{34}
}
func (x *TailscalePeer) GetHostName() string {
if x != nil {
return x.HostName
}
return ""
}
func (x *TailscalePeer) GetDnsName() string {
if x != nil {
return x.DnsName
}
return ""
}
func (x *TailscalePeer) GetOs() string {
if x != nil {
return x.Os
}
return ""
}
func (x *TailscalePeer) GetTailscaleIPs() []string {
if x != nil {
return x.TailscaleIPs
}
return nil
}
func (x *TailscalePeer) GetOnline() bool {
if x != nil {
return x.Online
}
return false
}
func (x *TailscalePeer) GetExitNode() bool {
if x != nil {
return x.ExitNode
}
return false
}
func (x *TailscalePeer) GetExitNodeOption() bool {
if x != nil {
return x.ExitNodeOption
}
return false
}
func (x *TailscalePeer) GetActive() bool {
if x != nil {
return x.Active
}
return false
}
func (x *TailscalePeer) GetRxBytes() int64 {
if x != nil {
return x.RxBytes
}
return 0
}
func (x *TailscalePeer) GetTxBytes() int64 {
if x != nil {
return x.TxBytes
}
return 0
}
func (x *TailscalePeer) GetKeyExpiry() int64 {
if x != nil {
return x.KeyExpiry
}
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"`
@@ -2258,7 +2594,7 @@ type Log_Message struct {
func (x *Log_Message) Reset() {
*x = Log_Message{}
mi := &file_daemon_started_service_proto_msgTypes[31]
mi := &file_daemon_started_service_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2270,7 +2606,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[31]
mi := &file_daemon_started_service_proto_msgTypes[35]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2472,7 +2808,38 @@ const file_daemon_started_service_proto_rawDesc = "" +
"\fnatFiltering\x18\x05 \x01(\x05R\fnatFiltering\x12\x18\n" +
"\aisFinal\x18\x06 \x01(\bR\aisFinal\x12\x14\n" +
"\x05error\x18\a \x01(\tR\x05error\x12*\n" +
"\x10natTypeSupported\x18\b \x01(\bR\x10natTypeSupported*U\n" +
"\x10natTypeSupported\x18\b \x01(\bR\x10natTypeSupported\"V\n" +
"\x15TailscaleStatusUpdate\x12=\n" +
"\tendpoints\x18\x01 \x03(\v2\x1f.daemon.TailscaleEndpointStatusR\tendpoints\"\xaa\x02\n" +
"\x17TailscaleEndpointStatus\x12 \n" +
"\vendpointTag\x18\x01 \x01(\tR\vendpointTag\x12\"\n" +
"\fbackendState\x18\x02 \x01(\tR\fbackendState\x12\x18\n" +
"\aauthURL\x18\x03 \x01(\tR\aauthURL\x12 \n" +
"\vnetworkName\x18\x04 \x01(\tR\vnetworkName\x12&\n" +
"\x0emagicDNSSuffix\x18\x05 \x01(\tR\x0emagicDNSSuffix\x12)\n" +
"\x04self\x18\x06 \x01(\v2\x15.daemon.TailscalePeerR\x04self\x12:\n" +
"\n" +
"userGroups\x18\a \x03(\v2\x1a.daemon.TailscaleUserGroupR\n" +
"userGroups\"\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\"\xbf\x02\n" +
"\rTailscalePeer\x12\x1a\n" +
"\bhostName\x18\x01 \x01(\tR\bhostName\x12\x18\n" +
"\adnsName\x18\x02 \x01(\tR\adnsName\x12\x0e\n" +
"\x02os\x18\x03 \x01(\tR\x02os\x12\"\n" +
"\ftailscaleIPs\x18\x04 \x03(\tR\ftailscaleIPs\x12\x16\n" +
"\x06online\x18\x05 \x01(\bR\x06online\x12\x1a\n" +
"\bexitNode\x18\x06 \x01(\bR\bexitNode\x12&\n" +
"\x0eexitNodeOption\x18\a \x01(\bR\x0eexitNodeOption\x12\x16\n" +
"\x06active\x18\b \x01(\bR\x06active\x12\x18\n" +
"\arxBytes\x18\t \x01(\x03R\arxBytes\x12\x18\n" +
"\atxBytes\x18\n" +
" \x01(\x03R\atxBytes\x12\x1c\n" +
"\tkeyExpiry\x18\v \x01(\x03R\tkeyExpiry*U\n" +
"\bLogLevel\x12\t\n" +
"\x05PANIC\x10\x00\x12\t\n" +
"\x05FATAL\x10\x01\x12\t\n" +
@@ -2484,7 +2851,7 @@ const file_daemon_started_service_proto_rawDesc = "" +
"\x13ConnectionEventType\x12\x18\n" +
"\x14CONNECTION_EVENT_NEW\x10\x00\x12\x1b\n" +
"\x17CONNECTION_EVENT_UPDATE\x10\x01\x12\x1b\n" +
"\x17CONNECTION_EVENT_CLOSED\x10\x022\xac\x0f\n" +
"\x17CONNECTION_EVENT_CLOSED\x10\x022\x83\x10\n" +
"\x0eStartedService\x12=\n" +
"\vStopService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12?\n" +
"\rReloadService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12K\n" +
@@ -2512,7 +2879,8 @@ const file_daemon_started_service_proto_rawDesc = "" +
"\rListOutbounds\x12\x16.google.protobuf.Empty\x1a\x14.daemon.OutboundList\"\x00\x12F\n" +
"\x12SubscribeOutbounds\x12\x16.google.protobuf.Empty\x1a\x14.daemon.OutboundList\"\x000\x01\x12d\n" +
"\x17StartNetworkQualityTest\x12!.daemon.NetworkQualityTestRequest\x1a\".daemon.NetworkQualityTestProgress\"\x000\x01\x12F\n" +
"\rStartSTUNTest\x12\x17.daemon.STUNTestRequest\x1a\x18.daemon.STUNTestProgress\"\x000\x01B%Z#github.com/sagernet/sing-box/daemonb\x06proto3"
"\rStartSTUNTest\x12\x17.daemon.STUNTestRequest\x1a\x18.daemon.STUNTestProgress\"\x000\x01\x12U\n" +
"\x18SubscribeTailscaleStatus\x12\x16.google.protobuf.Empty\x1a\x1d.daemon.TailscaleStatusUpdate\"\x000\x01B%Z#github.com/sagernet/sing-box/daemonb\x06proto3"
var (
file_daemon_started_service_proto_rawDescOnce sync.Once
@@ -2528,7 +2896,7 @@ func file_daemon_started_service_proto_rawDescGZIP() []byte {
var (
file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 32)
file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 36)
file_daemon_started_service_proto_goTypes = []any{
(LogLevel)(0), // 0: daemon.LogLevel
(ConnectionEventType)(0), // 1: daemon.ConnectionEventType
@@ -2565,14 +2933,18 @@ var (
(*NetworkQualityTestProgress)(nil), // 32: daemon.NetworkQualityTestProgress
(*STUNTestRequest)(nil), // 33: daemon.STUNTestRequest
(*STUNTestProgress)(nil), // 34: daemon.STUNTestProgress
(*Log_Message)(nil), // 35: daemon.Log.Message
(*emptypb.Empty)(nil), // 36: google.protobuf.Empty
(*TailscaleStatusUpdate)(nil), // 35: daemon.TailscaleStatusUpdate
(*TailscaleEndpointStatus)(nil), // 36: daemon.TailscaleEndpointStatus
(*TailscaleUserGroup)(nil), // 37: daemon.TailscaleUserGroup
(*TailscalePeer)(nil), // 38: daemon.TailscalePeer
(*Log_Message)(nil), // 39: daemon.Log.Message
(*emptypb.Empty)(nil), // 40: google.protobuf.Empty
}
)
var file_daemon_started_service_proto_depIdxs = []int32{
2, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type
35, // 1: daemon.Log.messages:type_name -> daemon.Log.Message
39, // 1: daemon.Log.messages:type_name -> daemon.Log.Message
0, // 2: daemon.DefaultLogLevel.level:type_name -> daemon.LogLevel
11, // 3: daemon.Groups.group:type_name -> daemon.Group
12, // 4: daemon.Group.items:type_name -> daemon.GroupItem
@@ -2583,66 +2955,72 @@ var file_daemon_started_service_proto_depIdxs = []int32{
25, // 9: daemon.Connection.processInfo:type_name -> daemon.ProcessInfo
28, // 10: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning
12, // 11: daemon.OutboundList.outbounds:type_name -> daemon.GroupItem
0, // 12: daemon.Log.Message.level:type_name -> daemon.LogLevel
36, // 13: daemon.StartedService.StopService:input_type -> google.protobuf.Empty
36, // 14: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty
36, // 15: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty
36, // 16: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty
36, // 17: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty
36, // 18: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty
6, // 19: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest
36, // 20: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty
36, // 21: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty
36, // 22: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty
16, // 23: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode
13, // 24: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest
14, // 25: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest
15, // 26: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest
36, // 27: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty
19, // 28: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest
20, // 29: daemon.StartedService.TriggerDebugCrash:input_type -> daemon.DebugCrashRequest
36, // 30: daemon.StartedService.TriggerOOMReport:input_type -> google.protobuf.Empty
21, // 31: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest
26, // 32: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest
36, // 33: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty
36, // 34: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty
36, // 35: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty
36, // 36: daemon.StartedService.ListOutbounds:input_type -> google.protobuf.Empty
36, // 37: daemon.StartedService.SubscribeOutbounds:input_type -> google.protobuf.Empty
31, // 38: daemon.StartedService.StartNetworkQualityTest:input_type -> daemon.NetworkQualityTestRequest
33, // 39: daemon.StartedService.StartSTUNTest:input_type -> daemon.STUNTestRequest
36, // 40: daemon.StartedService.StopService:output_type -> google.protobuf.Empty
36, // 41: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty
4, // 42: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus
7, // 43: daemon.StartedService.SubscribeLog:output_type -> daemon.Log
8, // 44: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel
36, // 45: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty
9, // 46: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status
10, // 47: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups
17, // 48: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus
16, // 49: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode
36, // 50: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty
36, // 51: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty
36, // 52: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty
36, // 53: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty
18, // 54: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus
36, // 55: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty
36, // 56: daemon.StartedService.TriggerDebugCrash:output_type -> google.protobuf.Empty
36, // 57: daemon.StartedService.TriggerOOMReport:output_type -> google.protobuf.Empty
23, // 58: daemon.StartedService.SubscribeConnections:output_type -> daemon.ConnectionEvents
36, // 59: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty
36, // 60: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty
27, // 61: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings
29, // 62: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt
30, // 63: daemon.StartedService.ListOutbounds:output_type -> daemon.OutboundList
30, // 64: daemon.StartedService.SubscribeOutbounds:output_type -> daemon.OutboundList
32, // 65: daemon.StartedService.StartNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress
34, // 66: daemon.StartedService.StartSTUNTest:output_type -> daemon.STUNTestProgress
40, // [40:67] is the sub-list for method output_type
13, // [13:40] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
36, // 12: daemon.TailscaleStatusUpdate.endpoints:type_name -> daemon.TailscaleEndpointStatus
38, // 13: daemon.TailscaleEndpointStatus.self:type_name -> daemon.TailscalePeer
37, // 14: daemon.TailscaleEndpointStatus.userGroups:type_name -> daemon.TailscaleUserGroup
38, // 15: daemon.TailscaleUserGroup.peers:type_name -> daemon.TailscalePeer
0, // 16: daemon.Log.Message.level:type_name -> daemon.LogLevel
40, // 17: daemon.StartedService.StopService:input_type -> google.protobuf.Empty
40, // 18: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty
40, // 19: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty
40, // 20: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty
40, // 21: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty
40, // 22: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty
6, // 23: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest
40, // 24: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty
40, // 25: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty
40, // 26: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty
16, // 27: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode
13, // 28: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest
14, // 29: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest
15, // 30: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest
40, // 31: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty
19, // 32: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest
20, // 33: daemon.StartedService.TriggerDebugCrash:input_type -> daemon.DebugCrashRequest
40, // 34: daemon.StartedService.TriggerOOMReport:input_type -> google.protobuf.Empty
21, // 35: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest
26, // 36: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest
40, // 37: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty
40, // 38: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty
40, // 39: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty
40, // 40: daemon.StartedService.ListOutbounds:input_type -> google.protobuf.Empty
40, // 41: daemon.StartedService.SubscribeOutbounds:input_type -> google.protobuf.Empty
31, // 42: daemon.StartedService.StartNetworkQualityTest:input_type -> daemon.NetworkQualityTestRequest
33, // 43: daemon.StartedService.StartSTUNTest:input_type -> daemon.STUNTestRequest
40, // 44: daemon.StartedService.SubscribeTailscaleStatus:input_type -> google.protobuf.Empty
40, // 45: daemon.StartedService.StopService:output_type -> google.protobuf.Empty
40, // 46: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty
4, // 47: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus
7, // 48: daemon.StartedService.SubscribeLog:output_type -> daemon.Log
8, // 49: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel
40, // 50: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty
9, // 51: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status
10, // 52: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups
17, // 53: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus
16, // 54: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode
40, // 55: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty
40, // 56: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty
40, // 57: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty
40, // 58: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty
18, // 59: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus
40, // 60: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty
40, // 61: daemon.StartedService.TriggerDebugCrash:output_type -> google.protobuf.Empty
40, // 62: daemon.StartedService.TriggerOOMReport:output_type -> google.protobuf.Empty
23, // 63: daemon.StartedService.SubscribeConnections:output_type -> daemon.ConnectionEvents
40, // 64: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty
40, // 65: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty
27, // 66: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings
29, // 67: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt
30, // 68: daemon.StartedService.ListOutbounds:output_type -> daemon.OutboundList
30, // 69: daemon.StartedService.SubscribeOutbounds:output_type -> daemon.OutboundList
32, // 70: daemon.StartedService.StartNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress
34, // 71: daemon.StartedService.StartSTUNTest:output_type -> daemon.STUNTestProgress
35, // 72: daemon.StartedService.SubscribeTailscaleStatus:output_type -> daemon.TailscaleStatusUpdate
45, // [45:73] is the sub-list for method output_type
17, // [17:45] is the sub-list for method input_type
17, // [17:17] is the sub-list for extension type_name
17, // [17:17] is the sub-list for extension extendee
0, // [0:17] is the sub-list for field type_name
}
func init() { file_daemon_started_service_proto_init() }
@@ -2656,7 +3034,7 @@ func file_daemon_started_service_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc)),
NumEnums: 4,
NumMessages: 32,
NumMessages: 36,
NumExtensions: 0,
NumServices: 1,
},

View File

@@ -39,6 +39,7 @@ service StartedService {
rpc SubscribeOutbounds(google.protobuf.Empty) returns (stream OutboundList) {}
rpc StartNetworkQualityTest(NetworkQualityTestRequest) returns (stream NetworkQualityTestProgress) {}
rpc StartSTUNTest(STUNTestRequest) returns (stream STUNTestProgress) {}
rpc SubscribeTailscaleStatus(google.protobuf.Empty) returns (stream TailscaleStatusUpdate) {}
}
message ServiceStatus {
@@ -278,3 +279,39 @@ message STUNTestProgress {
string error = 7;
bool natTypeSupported = 8;
}
message TailscaleStatusUpdate {
repeated TailscaleEndpointStatus endpoints = 1;
}
message TailscaleEndpointStatus {
string endpointTag = 1;
string backendState = 2;
string authURL = 3;
string networkName = 4;
string magicDNSSuffix = 5;
TailscalePeer self = 6;
repeated TailscaleUserGroup userGroups = 7;
}
message TailscaleUserGroup {
int64 userID = 1;
string loginName = 2;
string displayName = 3;
string profilePicURL = 4;
repeated TailscalePeer peers = 5;
}
message TailscalePeer {
string hostName = 1;
string dnsName = 2;
string os = 3;
repeated string tailscaleIPs = 4;
bool online = 5;
bool exitNode = 6;
bool exitNodeOption = 7;
bool active = 8;
int64 rxBytes = 9;
int64 txBytes = 10;
int64 keyExpiry = 11;
}

View File

@@ -15,33 +15,34 @@ import (
const _ = grpc.SupportPackageIsVersion9
const (
StartedService_StopService_FullMethodName = "/daemon.StartedService/StopService"
StartedService_ReloadService_FullMethodName = "/daemon.StartedService/ReloadService"
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs"
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode"
StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode"
StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest"
StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound"
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
StartedService_TriggerDebugCrash_FullMethodName = "/daemon.StartedService/TriggerDebugCrash"
StartedService_TriggerOOMReport_FullMethodName = "/daemon.StartedService/TriggerOOMReport"
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt"
StartedService_ListOutbounds_FullMethodName = "/daemon.StartedService/ListOutbounds"
StartedService_SubscribeOutbounds_FullMethodName = "/daemon.StartedService/SubscribeOutbounds"
StartedService_StartNetworkQualityTest_FullMethodName = "/daemon.StartedService/StartNetworkQualityTest"
StartedService_StartSTUNTest_FullMethodName = "/daemon.StartedService/StartSTUNTest"
StartedService_StopService_FullMethodName = "/daemon.StartedService/StopService"
StartedService_ReloadService_FullMethodName = "/daemon.StartedService/ReloadService"
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs"
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode"
StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode"
StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest"
StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound"
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
StartedService_TriggerDebugCrash_FullMethodName = "/daemon.StartedService/TriggerDebugCrash"
StartedService_TriggerOOMReport_FullMethodName = "/daemon.StartedService/TriggerOOMReport"
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt"
StartedService_ListOutbounds_FullMethodName = "/daemon.StartedService/ListOutbounds"
StartedService_SubscribeOutbounds_FullMethodName = "/daemon.StartedService/SubscribeOutbounds"
StartedService_StartNetworkQualityTest_FullMethodName = "/daemon.StartedService/StartNetworkQualityTest"
StartedService_StartSTUNTest_FullMethodName = "/daemon.StartedService/StartSTUNTest"
StartedService_SubscribeTailscaleStatus_FullMethodName = "/daemon.StartedService/SubscribeTailscaleStatus"
)
// StartedServiceClient is the client API for StartedService service.
@@ -75,6 +76,7 @@ type StartedServiceClient interface {
SubscribeOutbounds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OutboundList], error)
StartNetworkQualityTest(ctx context.Context, in *NetworkQualityTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NetworkQualityTestProgress], error)
StartSTUNTest(ctx context.Context, in *STUNTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[STUNTestProgress], error)
SubscribeTailscaleStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscaleStatusUpdate], error)
}
type startedServiceClient struct {
@@ -436,6 +438,25 @@ func (c *startedServiceClient) StartSTUNTest(ctx context.Context, in *STUNTestRe
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_StartSTUNTestClient = grpc.ServerStreamingClient[STUNTestProgress]
func (c *startedServiceClient) SubscribeTailscaleStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscaleStatusUpdate], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[9], StartedService_SubscribeTailscaleStatus_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[emptypb.Empty, TailscaleStatusUpdate]{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_SubscribeTailscaleStatusClient = grpc.ServerStreamingClient[TailscaleStatusUpdate]
// StartedServiceServer is the server API for StartedService service.
// All implementations must embed UnimplementedStartedServiceServer
// for forward compatibility.
@@ -467,6 +488,7 @@ type StartedServiceServer interface {
SubscribeOutbounds(*emptypb.Empty, grpc.ServerStreamingServer[OutboundList]) error
StartNetworkQualityTest(*NetworkQualityTestRequest, grpc.ServerStreamingServer[NetworkQualityTestProgress]) error
StartSTUNTest(*STUNTestRequest, grpc.ServerStreamingServer[STUNTestProgress]) error
SubscribeTailscaleStatus(*emptypb.Empty, grpc.ServerStreamingServer[TailscaleStatusUpdate]) error
mustEmbedUnimplementedStartedServiceServer()
}
@@ -584,6 +606,10 @@ func (UnimplementedStartedServiceServer) StartNetworkQualityTest(*NetworkQuality
func (UnimplementedStartedServiceServer) StartSTUNTest(*STUNTestRequest, grpc.ServerStreamingServer[STUNTestProgress]) error {
return status.Error(codes.Unimplemented, "method StartSTUNTest not implemented")
}
func (UnimplementedStartedServiceServer) SubscribeTailscaleStatus(*emptypb.Empty, grpc.ServerStreamingServer[TailscaleStatusUpdate]) error {
return status.Error(codes.Unimplemented, "method SubscribeTailscaleStatus not implemented")
}
func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}
func (UnimplementedStartedServiceServer) testEmbeddedByValue() {}
@@ -1028,6 +1054,17 @@ func _StartedService_StartSTUNTest_Handler(srv interface{}, stream grpc.ServerSt
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StartedService_StartSTUNTestServer = grpc.ServerStreamingServer[STUNTestProgress]
func _StartedService_SubscribeTailscaleStatus_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(emptypb.Empty)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(StartedServiceServer).SubscribeTailscaleStatus(m, &grpc.GenericServerStream[emptypb.Empty, TailscaleStatusUpdate]{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_SubscribeTailscaleStatusServer = grpc.ServerStreamingServer[TailscaleStatusUpdate]
// 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)
@@ -1154,6 +1191,11 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
Handler: _StartedService_StartSTUNTest_Handler,
ServerStreams: true,
},
{
StreamName: "SubscribeTailscaleStatus",
Handler: _StartedService_SubscribeTailscaleStatus_Handler,
ServerStreams: true,
},
},
Metadata: "daemon/started_service.proto",
}

View File

@@ -720,3 +720,25 @@ func (c *CommandClient) StartSTUNTest(server string, outboundTag string, handler
handler.OnProgress(stunTestProgressFromGRPC(event))
}
}
func (c *CommandClient) SubscribeTailscaleStatus(handler TailscaleStatusHandler) error {
client, err := c.getClientForCall()
if err != nil {
return err
}
if c.standalone {
defer c.closeConnection()
}
stream, err := client.SubscribeTailscaleStatus(context.Background(), &emptypb.Empty{})
if err != nil {
return err
}
for {
event, recvErr := stream.Recv()
if recvErr != nil {
handler.OnError(recvErr.Error())
return recvErr
}
handler.OnStatusUpdate(tailscaleStatusUpdateFromGRPC(event))
}
}

View File

@@ -0,0 +1,132 @@
package libbox
import "github.com/sagernet/sing-box/daemon"
type TailscaleStatusUpdate struct {
endpoints []*TailscaleEndpointStatus
}
func (u *TailscaleStatusUpdate) Endpoints() TailscaleEndpointStatusIterator {
return newIterator(u.endpoints)
}
type TailscaleEndpointStatusIterator interface {
Next() *TailscaleEndpointStatus
HasNext() bool
}
type TailscaleEndpointStatus struct {
EndpointTag string
BackendState string
AuthURL string
NetworkName string
MagicDNSSuffix string
Self *TailscalePeer
userGroups []*TailscaleUserGroup
}
func (s *TailscaleEndpointStatus) UserGroups() TailscaleUserGroupIterator {
return newIterator(s.userGroups)
}
type TailscaleUserGroupIterator interface {
Next() *TailscaleUserGroup
HasNext() bool
}
type TailscaleUserGroup struct {
UserID int64
LoginName string
DisplayName string
ProfilePicURL string
peers []*TailscalePeer
}
func (g *TailscaleUserGroup) Peers() TailscalePeerIterator {
return newIterator(g.peers)
}
type TailscalePeerIterator interface {
Next() *TailscalePeer
HasNext() bool
}
type TailscalePeer struct {
HostName string
DNSName string
OS string
tailscaleIPs []string
Online bool
ExitNode bool
ExitNodeOption bool
Active bool
RxBytes int64
TxBytes int64
KeyExpiry int64
}
func (p *TailscalePeer) TailscaleIPs() StringIterator {
return newIterator(p.tailscaleIPs)
}
type TailscaleStatusHandler interface {
OnStatusUpdate(status *TailscaleStatusUpdate)
OnError(message string)
}
func tailscaleStatusUpdateFromGRPC(update *daemon.TailscaleStatusUpdate) *TailscaleStatusUpdate {
endpoints := make([]*TailscaleEndpointStatus, len(update.Endpoints))
for i, endpoint := range update.Endpoints {
endpoints[i] = tailscaleEndpointStatusFromGRPC(endpoint)
}
return &TailscaleStatusUpdate{endpoints: endpoints}
}
func tailscaleEndpointStatusFromGRPC(status *daemon.TailscaleEndpointStatus) *TailscaleEndpointStatus {
userGroups := make([]*TailscaleUserGroup, len(status.UserGroups))
for i, group := range status.UserGroups {
userGroups[i] = tailscaleUserGroupFromGRPC(group)
}
result := &TailscaleEndpointStatus{
EndpointTag: status.EndpointTag,
BackendState: status.BackendState,
AuthURL: status.AuthURL,
NetworkName: status.NetworkName,
MagicDNSSuffix: status.MagicDNSSuffix,
userGroups: userGroups,
}
if status.Self != nil {
result.Self = tailscalePeerFromGRPC(status.Self)
}
return result
}
func tailscaleUserGroupFromGRPC(group *daemon.TailscaleUserGroup) *TailscaleUserGroup {
peers := make([]*TailscalePeer, len(group.Peers))
for i, peer := range group.Peers {
peers[i] = tailscalePeerFromGRPC(peer)
}
return &TailscaleUserGroup{
UserID: group.UserID,
LoginName: group.LoginName,
DisplayName: group.DisplayName,
ProfilePicURL: group.ProfilePicURL,
peers: peers,
}
}
func tailscalePeerFromGRPC(peer *daemon.TailscalePeer) *TailscalePeer {
return &TailscalePeer{
HostName: peer.HostName,
DNSName: peer.DnsName,
OS: peer.Os,
tailscaleIPs: peer.TailscaleIPs,
Online: peer.Online,
ExitNode: peer.ExitNode,
ExitNodeOption: peer.ExitNodeOption,
Active: peer.Active,
RxBytes: peer.RxBytes,
TxBytes: peer.TxBytes,
KeyExpiry: peer.KeyExpiry,
}
}

View File

@@ -1,9 +1,12 @@
package libbox
import "time"
import (
"time"
"unsafe"
)
func TriggerGoPanic() {
time.AfterFunc(200*time.Millisecond, func() {
panic("debug go crash")
*(*int)(unsafe.Pointer(uintptr(0))) = 0
})
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/sagernet/sing-box/common/networkquality"
"github.com/sagernet/sing-box/common/stun"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/experimental/locale"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/service/oomkiller"
@@ -181,6 +182,10 @@ func FormatNATFiltering(value int32) string {
return stun.NATFiltering(value).String()
}
func FormatFQDN(fqdn string) string {
return dns.FqdnToDomain(fqdn)
}
func ProxyDisplayType(proxyType string) string {
return C.ProxyDisplayName(proxyType)
}

View File

@@ -0,0 +1,92 @@
//go:build with_gvisor
package tailscale
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/tailscale/ipn"
"github.com/sagernet/tailscale/ipn/ipnstate"
"github.com/sagernet/tailscale/tailcfg"
)
var _ adapter.TailscaleStatusProvider = (*Endpoint)(nil)
func (t *Endpoint) SubscribeTailscaleStatus(ctx context.Context, fn func(*adapter.TailscaleEndpointStatus)) error {
localBackend := t.server.ExportLocalBackend()
sendStatus := func() {
status := localBackend.Status()
fn(convertTailscaleStatus(status))
}
sendStatus()
localBackend.WatchNotifications(ctx, ipn.NotifyInitialState|ipn.NotifyInitialNetMap|ipn.NotifyRateLimit, nil, func(roNotify *ipn.Notify) (keepGoing bool) {
select {
case <-ctx.Done():
return false
default:
}
if roNotify.State != nil || roNotify.NetMap != nil || roNotify.BrowseToURL != nil {
sendStatus()
}
return true
})
return ctx.Err()
}
func convertTailscaleStatus(status *ipnstate.Status) *adapter.TailscaleEndpointStatus {
result := &adapter.TailscaleEndpointStatus{
BackendState: status.BackendState,
AuthURL: status.AuthURL,
}
if status.CurrentTailnet != nil {
result.NetworkName = status.CurrentTailnet.Name
result.MagicDNSSuffix = status.CurrentTailnet.MagicDNSSuffix
}
if status.Self != nil {
result.Self = convertTailscalePeer(status.Self)
}
result.Users = make(map[int64]*adapter.TailscaleUser, len(status.User))
for userID, profile := range status.User {
result.Users[int64(userID)] = convertTailscaleUser(userID, profile)
}
result.Peers = make([]*adapter.TailscalePeer, 0, len(status.Peer))
for _, peer := range status.Peer {
result.Peers = append(result.Peers, convertTailscalePeer(peer))
}
return result
}
func convertTailscalePeer(peer *ipnstate.PeerStatus) *adapter.TailscalePeer {
ips := make([]string, len(peer.TailscaleIPs))
for i, ip := range peer.TailscaleIPs {
ips[i] = ip.String()
}
var keyExpiry int64
if peer.KeyExpiry != nil {
keyExpiry = peer.KeyExpiry.Unix()
}
return &adapter.TailscalePeer{
HostName: peer.HostName,
DNSName: peer.DNSName,
OS: peer.OS,
TailscaleIPs: ips,
Online: peer.Online,
ExitNode: peer.ExitNode,
ExitNodeOption: peer.ExitNodeOption,
Active: peer.Active,
RxBytes: peer.RxBytes,
TxBytes: peer.TxBytes,
UserID: int64(peer.UserID),
KeyExpiry: keyExpiry,
}
}
func convertTailscaleUser(id tailcfg.UserID, profile tailcfg.UserProfile) *adapter.TailscaleUser {
return &adapter.TailscaleUser{
ID: int64(id),
LoginName: profile.LoginName,
DisplayName: profile.DisplayName,
ProfilePicURL: profile.ProfilePicURL,
}
}