diff --git a/adapter/experimental.go b/adapter/experimental.go index de01d7be5..d4d37922b 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "time" + "github.com/sagernet/sing/common/observable" "github.com/sagernet/sing/common/varbin" ) @@ -14,6 +15,7 @@ type ClashServer interface { ConnectionTracker Mode() string ModeList() []string + SetModeUpdateHook(hook *observable.Subscriber[struct{}]) HistoryStorage() URLTestHistoryStorage } @@ -23,7 +25,7 @@ type URLTestHistory struct { } type URLTestHistoryStorage interface { - SetHook(hook chan<- struct{}) + SetHook(hook *observable.Subscriber[struct{}]) LoadURLTestHistory(tag string) *URLTestHistory DeleteURLTestHistory(tag string) StoreURLTestHistory(tag string, history *URLTestHistory) diff --git a/adapter/inbound.go b/adapter/inbound.go index de30149f9..1941df5b6 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -5,7 +5,6 @@ import ( "net/netip" "time" - "github.com/sagernet/sing-box/common/process" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -85,7 +84,7 @@ type InboundContext struct { DestinationAddresses []netip.Addr SourceGeoIPCode string GeoIPCode string - ProcessInfo *process.Info + ProcessInfo *ConnectionOwner QueryType uint16 FakeIP bool diff --git a/adapter/platform.go b/adapter/platform.go new file mode 100644 index 000000000..61dc7b440 --- /dev/null +++ b/adapter/platform.go @@ -0,0 +1,68 @@ +package adapter + +import ( + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/logger" +) + +type PlatformInterface interface { + Initialize(networkManager NetworkManager) error + + UsePlatformAutoDetectInterfaceControl() bool + AutoDetectInterfaceControl(fd int) error + + UsePlatformInterface() bool + OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) + + UsePlatformDefaultInterfaceMonitor() bool + CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor + + UsePlatformNetworkInterfaces() bool + NetworkInterfaces() ([]NetworkInterface, error) + + UnderNetworkExtension() bool + NetworkExtensionIncludeAllNetworks() bool + + ClearDNSCache() + RequestPermissionForWIFIState() error + ReadWIFIState() WIFIState + SystemCertificates() []string + + UsePlatformConnectionOwnerFinder() bool + FindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error) + + UsePlatformNotification() bool + SendNotification(notification *Notification) error +} + +type FindConnectionOwnerRequest struct { + IpProtocol int32 + SourceAddress string + SourcePort int32 + DestinationAddress string + DestinationPort int32 +} + +type ConnectionOwner struct { + ProcessID uint32 + UserId int32 + UserName string + ProcessPath string + AndroidPackageName string +} + +type Notification struct { + Identifier string + TypeName string + TypeID int32 + Title string + Subtitle string + Body string + OpenURL string +} + +type SystemProxyStatus struct { + Available bool + Enabled bool +} diff --git a/box.go b/box.go index a84437efa..1c168820c 100644 --- a/box.go +++ b/box.go @@ -22,7 +22,6 @@ import ( "github.com/sagernet/sing-box/dns/transport/local" "github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/experimental/cachefile" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/protocol/direct" @@ -139,7 +138,7 @@ func New(options Options) (*Box, error) { if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { needV2RayAPI = true } - platformInterface := service.FromContext[platform.Interface](ctx) + platformInterface := service.FromContext[adapter.PlatformInterface](ctx) var defaultLogWriter io.Writer if platformInterface != nil { defaultLogWriter = io.Discard @@ -527,3 +526,7 @@ func (s *Box) Inbound() adapter.InboundManager { func (s *Box) Outbound() adapter.OutboundManager { return s.outbound } + +func (s *Box) LogFactory() log.Factory { + return s.logFactory +} diff --git a/common/certificate/store.go b/common/certificate/store.go index ee1272782..82ce8e29a 100644 --- a/common/certificate/store.go +++ b/common/certificate/store.go @@ -12,7 +12,6 @@ import ( "github.com/sagernet/fswatch" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -36,7 +35,7 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific switch options.Store { case C.CertificateStoreSystem, "": systemPool = x509.NewCertPool() - platformInterface := service.FromContext[platform.Interface](ctx) + platformInterface := service.FromContext[adapter.PlatformInterface](ctx) var systemValid bool if platformInterface != nil { for _, cert := range platformInterface.SystemCertificates() { diff --git a/common/dialer/default.go b/common/dialer/default.go index 23754e0a3..d38ad4871 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -12,7 +12,6 @@ import ( "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/listener" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/control" @@ -49,7 +48,7 @@ type DefaultDialer struct { func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) { networkManager := service.FromContext[adapter.NetworkManager](ctx) - platformInterface := service.FromContext[platform.Interface](ctx) + platformInterface := service.FromContext[adapter.PlatformInterface](ctx) var ( dialer net.Dialer diff --git a/common/process/searcher.go b/common/process/searcher.go index d525b3c15..1af2c2bd0 100644 --- a/common/process/searcher.go +++ b/common/process/searcher.go @@ -5,6 +5,7 @@ import ( "net/netip" "os/user" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-tun" E "github.com/sagernet/sing/common/exceptions" @@ -12,7 +13,7 @@ import ( ) type Searcher interface { - FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) + FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) } var ErrNotFound = E.New("process not found") @@ -22,15 +23,7 @@ type Config struct { PackageManager tun.PackageManager } -type Info struct { - ProcessID uint32 - ProcessPath string - PackageName string - User string - UserId int32 -} - -func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { +func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { info, err := searcher.FindProcessInfo(ctx, network, source, destination) if err != nil { return nil, err @@ -38,7 +31,7 @@ func FindProcessInfo(searcher Searcher, ctx context.Context, network string, sou if info.UserId != -1 { osUser, _ := user.LookupId(F.ToString(info.UserId)) if osUser != nil { - info.User = osUser.Username + info.UserName = osUser.Username } } return info, nil diff --git a/common/process/searcher_android.go b/common/process/searcher_android.go index e1835b476..ac9550ce1 100644 --- a/common/process/searcher_android.go +++ b/common/process/searcher_android.go @@ -4,6 +4,7 @@ import ( "context" "net/netip" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-tun" ) @@ -17,22 +18,22 @@ func NewSearcher(config Config) (Searcher, error) { return &androidSearcher{config.PackageManager}, nil } -func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { +func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { _, uid, err := resolveSocketByNetlink(network, source, destination) if err != nil { return nil, err } if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded { - return &Info{ - UserId: int32(uid), - PackageName: sharedPackage, + return &adapter.ConnectionOwner{ + UserId: int32(uid), + AndroidPackageName: sharedPackage, }, nil } if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded { - return &Info{ - UserId: int32(uid), - PackageName: packageName, + return &adapter.ConnectionOwner{ + UserId: int32(uid), + AndroidPackageName: packageName, }, nil } - return &Info{UserId: int32(uid)}, nil + return &adapter.ConnectionOwner{UserId: int32(uid)}, nil } diff --git a/common/process/searcher_darwin.go b/common/process/searcher_darwin.go index 5c1addd57..03428cc8e 100644 --- a/common/process/searcher_darwin.go +++ b/common/process/searcher_darwin.go @@ -10,6 +10,7 @@ import ( "syscall" "unsafe" + "github.com/sagernet/sing-box/adapter" N "github.com/sagernet/sing/common/network" "golang.org/x/sys/unix" @@ -23,12 +24,12 @@ func NewSearcher(_ Config) (Searcher, error) { return &darwinSearcher{}, nil } -func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { +func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { processName, err := findProcessName(network, source.Addr(), int(source.Port())) if err != nil { return nil, err } - return &Info{ProcessPath: processName, UserId: -1}, nil + return &adapter.ConnectionOwner{ProcessPath: processName, UserId: -1}, nil } var structSize = func() int { diff --git a/common/process/searcher_linux.go b/common/process/searcher_linux.go index 39470205a..86d37d7c6 100644 --- a/common/process/searcher_linux.go +++ b/common/process/searcher_linux.go @@ -6,6 +6,7 @@ import ( "context" "net/netip" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/log" ) @@ -19,7 +20,7 @@ func NewSearcher(config Config) (Searcher, error) { return &linuxSearcher{config.Logger}, nil } -func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { +func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { inode, uid, err := resolveSocketByNetlink(network, source, destination) if err != nil { return nil, err @@ -28,7 +29,7 @@ func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, sou if err != nil { s.logger.DebugContext(ctx, "find process path: ", err) } - return &Info{ + return &adapter.ConnectionOwner{ UserId: int32(uid), ProcessPath: processPath, }, nil diff --git a/common/process/searcher_windows.go b/common/process/searcher_windows.go index b7d89dda6..ac95e0ce6 100644 --- a/common/process/searcher_windows.go +++ b/common/process/searcher_windows.go @@ -5,6 +5,7 @@ import ( "net/netip" "syscall" + "github.com/sagernet/sing-box/adapter" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/winiphlpapi" @@ -27,16 +28,16 @@ func initWin32API() error { return winiphlpapi.LoadExtendedTable() } -func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { +func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { pid, err := winiphlpapi.FindPid(network, source) if err != nil { return nil, err } path, err := getProcessPath(pid) if err != nil { - return &Info{ProcessID: pid, UserId: -1}, err + return &adapter.ConnectionOwner{ProcessID: pid, UserId: -1}, err } - return &Info{ProcessID: pid, ProcessPath: path, UserId: -1}, nil + return &adapter.ConnectionOwner{ProcessID: pid, ProcessPath: path, UserId: -1}, nil } func getProcessPath(pid uint32) (string, error) { diff --git a/common/urltest/urltest.go b/common/urltest/urltest.go index 016f4a501..29d790e4d 100644 --- a/common/urltest/urltest.go +++ b/common/urltest/urltest.go @@ -14,6 +14,7 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" + "github.com/sagernet/sing/common/observable" ) var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil) @@ -21,7 +22,7 @@ var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil) type HistoryStorage struct { access sync.RWMutex delayHistory map[string]*adapter.URLTestHistory - updateHook chan<- struct{} + updateHook *observable.Subscriber[struct{}] } func NewHistoryStorage() *HistoryStorage { @@ -30,7 +31,7 @@ func NewHistoryStorage() *HistoryStorage { } } -func (s *HistoryStorage) SetHook(hook chan<- struct{}) { +func (s *HistoryStorage) SetHook(hook *observable.Subscriber[struct{}]) { s.updateHook = hook } @@ -60,10 +61,7 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTes func (s *HistoryStorage) notifyUpdated() { updateHook := s.updateHook if updateHook != nil { - select { - case updateHook <- struct{}{}: - default: - } + updateHook.Emit(struct{}{}) } } diff --git a/daemon/deprecated.go b/daemon/deprecated.go new file mode 100644 index 000000000..6f23db999 --- /dev/null +++ b/daemon/deprecated.go @@ -0,0 +1,29 @@ +package daemon + +import ( + "sync" + + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing/common" +) + +var _ deprecated.Manager = (*deprecatedManager)(nil) + +type deprecatedManager struct { + access sync.Mutex + notes []deprecated.Note +} + +func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) { + m.access.Lock() + defer m.access.Unlock() + m.notes = common.Uniq(append(m.notes, feature)) +} + +func (m *deprecatedManager) Get() []deprecated.Note { + m.access.Lock() + defer m.access.Unlock() + notes := m.notes + m.notes = nil + return notes +} diff --git a/daemon/helper.pb.go b/daemon/helper.pb.go new file mode 100644 index 000000000..9a2641c52 --- /dev/null +++ b/daemon/helper.pb.go @@ -0,0 +1,702 @@ +package daemon + +import ( + reflect "reflect" + sync "sync" + unsafe "unsafe" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SubscribeHelperRequestRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AcceptGetWIFIStateRequests bool `protobuf:"varint,1,opt,name=acceptGetWIFIStateRequests,proto3" json:"acceptGetWIFIStateRequests,omitempty"` + AcceptFindConnectionOwnerRequests bool `protobuf:"varint,2,opt,name=acceptFindConnectionOwnerRequests,proto3" json:"acceptFindConnectionOwnerRequests,omitempty"` + AcceptSendNotificationRequests bool `protobuf:"varint,3,opt,name=acceptSendNotificationRequests,proto3" json:"acceptSendNotificationRequests,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscribeHelperRequestRequest) Reset() { + *x = SubscribeHelperRequestRequest{} + mi := &file_daemon_helper_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscribeHelperRequestRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscribeHelperRequestRequest) ProtoMessage() {} + +func (x *SubscribeHelperRequestRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_helper_proto_msgTypes[0] + 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 SubscribeHelperRequestRequest.ProtoReflect.Descriptor instead. +func (*SubscribeHelperRequestRequest) Descriptor() ([]byte, []int) { + return file_daemon_helper_proto_rawDescGZIP(), []int{0} +} + +func (x *SubscribeHelperRequestRequest) GetAcceptGetWIFIStateRequests() bool { + if x != nil { + return x.AcceptGetWIFIStateRequests + } + return false +} + +func (x *SubscribeHelperRequestRequest) GetAcceptFindConnectionOwnerRequests() bool { + if x != nil { + return x.AcceptFindConnectionOwnerRequests + } + return false +} + +func (x *SubscribeHelperRequestRequest) GetAcceptSendNotificationRequests() bool { + if x != nil { + return x.AcceptSendNotificationRequests + } + return false +} + +type HelperRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + // Types that are valid to be assigned to Request: + // + // *HelperRequest_GetWIFIState + // *HelperRequest_FindConnectionOwner + // *HelperRequest_SendNotification + Request isHelperRequest_Request `protobuf_oneof:"request"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HelperRequest) Reset() { + *x = HelperRequest{} + mi := &file_daemon_helper_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HelperRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelperRequest) ProtoMessage() {} + +func (x *HelperRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_helper_proto_msgTypes[1] + 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 HelperRequest.ProtoReflect.Descriptor instead. +func (*HelperRequest) Descriptor() ([]byte, []int) { + return file_daemon_helper_proto_rawDescGZIP(), []int{1} +} + +func (x *HelperRequest) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *HelperRequest) GetRequest() isHelperRequest_Request { + if x != nil { + return x.Request + } + return nil +} + +func (x *HelperRequest) GetGetWIFIState() *emptypb.Empty { + if x != nil { + if x, ok := x.Request.(*HelperRequest_GetWIFIState); ok { + return x.GetWIFIState + } + } + return nil +} + +func (x *HelperRequest) GetFindConnectionOwner() *FindConnectionOwnerRequest { + if x != nil { + if x, ok := x.Request.(*HelperRequest_FindConnectionOwner); ok { + return x.FindConnectionOwner + } + } + return nil +} + +func (x *HelperRequest) GetSendNotification() *Notification { + if x != nil { + if x, ok := x.Request.(*HelperRequest_SendNotification); ok { + return x.SendNotification + } + } + return nil +} + +type isHelperRequest_Request interface { + isHelperRequest_Request() +} + +type HelperRequest_GetWIFIState struct { + GetWIFIState *emptypb.Empty `protobuf:"bytes,2,opt,name=getWIFIState,proto3,oneof"` +} + +type HelperRequest_FindConnectionOwner struct { + FindConnectionOwner *FindConnectionOwnerRequest `protobuf:"bytes,3,opt,name=findConnectionOwner,proto3,oneof"` +} + +type HelperRequest_SendNotification struct { + SendNotification *Notification `protobuf:"bytes,4,opt,name=sendNotification,proto3,oneof"` +} + +func (*HelperRequest_GetWIFIState) isHelperRequest_Request() {} + +func (*HelperRequest_FindConnectionOwner) isHelperRequest_Request() {} + +func (*HelperRequest_SendNotification) isHelperRequest_Request() {} + +type FindConnectionOwnerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + IpProtocol int32 `protobuf:"varint,1,opt,name=ipProtocol,proto3" json:"ipProtocol,omitempty"` + SourceAddress string `protobuf:"bytes,2,opt,name=sourceAddress,proto3" json:"sourceAddress,omitempty"` + SourcePort int32 `protobuf:"varint,3,opt,name=sourcePort,proto3" json:"sourcePort,omitempty"` + DestinationAddress string `protobuf:"bytes,4,opt,name=destinationAddress,proto3" json:"destinationAddress,omitempty"` + DestinationPort int32 `protobuf:"varint,5,opt,name=destinationPort,proto3" json:"destinationPort,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FindConnectionOwnerRequest) Reset() { + *x = FindConnectionOwnerRequest{} + mi := &file_daemon_helper_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FindConnectionOwnerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FindConnectionOwnerRequest) ProtoMessage() {} + +func (x *FindConnectionOwnerRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_helper_proto_msgTypes[2] + 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 FindConnectionOwnerRequest.ProtoReflect.Descriptor instead. +func (*FindConnectionOwnerRequest) Descriptor() ([]byte, []int) { + return file_daemon_helper_proto_rawDescGZIP(), []int{2} +} + +func (x *FindConnectionOwnerRequest) GetIpProtocol() int32 { + if x != nil { + return x.IpProtocol + } + return 0 +} + +func (x *FindConnectionOwnerRequest) GetSourceAddress() string { + if x != nil { + return x.SourceAddress + } + return "" +} + +func (x *FindConnectionOwnerRequest) GetSourcePort() int32 { + if x != nil { + return x.SourcePort + } + return 0 +} + +func (x *FindConnectionOwnerRequest) GetDestinationAddress() string { + if x != nil { + return x.DestinationAddress + } + return "" +} + +func (x *FindConnectionOwnerRequest) GetDestinationPort() int32 { + if x != nil { + return x.DestinationPort + } + return 0 +} + +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_helper_proto_msgTypes[3] + 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_helper_proto_msgTypes[3] + 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_helper_proto_rawDescGZIP(), []int{3} +} + +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 HelperResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + // Types that are valid to be assigned to Response: + // + // *HelperResponse_WifiState + // *HelperResponse_Error + // *HelperResponse_ConnectionOwner + Response isHelperResponse_Response `protobuf_oneof:"response"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HelperResponse) Reset() { + *x = HelperResponse{} + mi := &file_daemon_helper_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HelperResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelperResponse) ProtoMessage() {} + +func (x *HelperResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_helper_proto_msgTypes[4] + 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 HelperResponse.ProtoReflect.Descriptor instead. +func (*HelperResponse) Descriptor() ([]byte, []int) { + return file_daemon_helper_proto_rawDescGZIP(), []int{4} +} + +func (x *HelperResponse) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *HelperResponse) GetResponse() isHelperResponse_Response { + if x != nil { + return x.Response + } + return nil +} + +func (x *HelperResponse) GetWifiState() *WIFIState { + if x != nil { + if x, ok := x.Response.(*HelperResponse_WifiState); ok { + return x.WifiState + } + } + return nil +} + +func (x *HelperResponse) GetError() string { + if x != nil { + if x, ok := x.Response.(*HelperResponse_Error); ok { + return x.Error + } + } + return "" +} + +func (x *HelperResponse) GetConnectionOwner() *ConnectionOwner { + if x != nil { + if x, ok := x.Response.(*HelperResponse_ConnectionOwner); ok { + return x.ConnectionOwner + } + } + return nil +} + +type isHelperResponse_Response interface { + isHelperResponse_Response() +} + +type HelperResponse_WifiState struct { + WifiState *WIFIState `protobuf:"bytes,2,opt,name=wifiState,proto3,oneof"` +} + +type HelperResponse_Error struct { + Error string `protobuf:"bytes,3,opt,name=error,proto3,oneof"` +} + +type HelperResponse_ConnectionOwner struct { + ConnectionOwner *ConnectionOwner `protobuf:"bytes,4,opt,name=connectionOwner,proto3,oneof"` +} + +func (*HelperResponse_WifiState) isHelperResponse_Response() {} + +func (*HelperResponse_Error) isHelperResponse_Response() {} + +func (*HelperResponse_ConnectionOwner) isHelperResponse_Response() {} + +type ConnectionOwner struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int32 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` + UserName string `protobuf:"bytes,2,opt,name=userName,proto3" json:"userName,omitempty"` + ProcessPath string `protobuf:"bytes,3,opt,name=processPath,proto3" json:"processPath,omitempty"` + AndroidPackageName string `protobuf:"bytes,4,opt,name=androidPackageName,proto3" json:"androidPackageName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectionOwner) Reset() { + *x = ConnectionOwner{} + mi := &file_daemon_helper_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectionOwner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectionOwner) ProtoMessage() {} + +func (x *ConnectionOwner) ProtoReflect() protoreflect.Message { + mi := &file_daemon_helper_proto_msgTypes[5] + 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 ConnectionOwner.ProtoReflect.Descriptor instead. +func (*ConnectionOwner) Descriptor() ([]byte, []int) { + return file_daemon_helper_proto_rawDescGZIP(), []int{5} +} + +func (x *ConnectionOwner) GetUserId() int32 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ConnectionOwner) GetUserName() string { + if x != nil { + return x.UserName + } + return "" +} + +func (x *ConnectionOwner) GetProcessPath() string { + if x != nil { + return x.ProcessPath + } + return "" +} + +func (x *ConnectionOwner) GetAndroidPackageName() string { + if x != nil { + return x.AndroidPackageName + } + return "" +} + +type WIFIState struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ssid string `protobuf:"bytes,1,opt,name=ssid,proto3" json:"ssid,omitempty"` + Bssid string `protobuf:"bytes,2,opt,name=bssid,proto3" json:"bssid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WIFIState) Reset() { + *x = WIFIState{} + mi := &file_daemon_helper_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WIFIState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WIFIState) ProtoMessage() {} + +func (x *WIFIState) ProtoReflect() protoreflect.Message { + mi := &file_daemon_helper_proto_msgTypes[6] + 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 WIFIState.ProtoReflect.Descriptor instead. +func (*WIFIState) Descriptor() ([]byte, []int) { + return file_daemon_helper_proto_rawDescGZIP(), []int{6} +} + +func (x *WIFIState) GetSsid() string { + if x != nil { + return x.Ssid + } + return "" +} + +func (x *WIFIState) GetBssid() string { + if x != nil { + return x.Bssid + } + return "" +} + +var File_daemon_helper_proto protoreflect.FileDescriptor + +const file_daemon_helper_proto_rawDesc = "" + + "\n" + + "\x13daemon/helper.proto\x12\x06daemon\x1a\x1bgoogle/protobuf/empty.proto\"\xf5\x01\n" + + "\x1dSubscribeHelperRequestRequest\x12>\n" + + "\x1aacceptGetWIFIStateRequests\x18\x01 \x01(\bR\x1aacceptGetWIFIStateRequests\x12L\n" + + "!acceptFindConnectionOwnerRequests\x18\x02 \x01(\bR!acceptFindConnectionOwnerRequests\x12F\n" + + "\x1eacceptSendNotificationRequests\x18\x03 \x01(\bR\x1eacceptSendNotificationRequests\"\x84\x02\n" + + "\rHelperRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12<\n" + + "\fgetWIFIState\x18\x02 \x01(\v2\x16.google.protobuf.EmptyH\x00R\fgetWIFIState\x12V\n" + + "\x13findConnectionOwner\x18\x03 \x01(\v2\".daemon.FindConnectionOwnerRequestH\x00R\x13findConnectionOwner\x12B\n" + + "\x10sendNotification\x18\x04 \x01(\v2\x14.daemon.NotificationH\x00R\x10sendNotificationB\t\n" + + "\arequest\"\xdc\x01\n" + + "\x1aFindConnectionOwnerRequest\x12\x1e\n" + + "\n" + + "ipProtocol\x18\x01 \x01(\x05R\n" + + "ipProtocol\x12$\n" + + "\rsourceAddress\x18\x02 \x01(\tR\rsourceAddress\x12\x1e\n" + + "\n" + + "sourcePort\x18\x03 \x01(\x05R\n" + + "sourcePort\x12.\n" + + "\x12destinationAddress\x18\x04 \x01(\tR\x12destinationAddress\x12(\n" + + "\x0fdestinationPort\x18\x05 \x01(\x05R\x0fdestinationPort\"\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\"\xbc\x01\n" + + "\x0eHelperResponse\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x121\n" + + "\twifiState\x18\x02 \x01(\v2\x11.daemon.WIFIStateH\x00R\twifiState\x12\x16\n" + + "\x05error\x18\x03 \x01(\tH\x00R\x05error\x12C\n" + + "\x0fconnectionOwner\x18\x04 \x01(\v2\x17.daemon.ConnectionOwnerH\x00R\x0fconnectionOwnerB\n" + + "\n" + + "\bresponse\"\x97\x01\n" + + "\x0fConnectionOwner\x12\x16\n" + + "\x06userId\x18\x01 \x01(\x05R\x06userId\x12\x1a\n" + + "\buserName\x18\x02 \x01(\tR\buserName\x12 \n" + + "\vprocessPath\x18\x03 \x01(\tR\vprocessPath\x12.\n" + + "\x12androidPackageName\x18\x04 \x01(\tR\x12androidPackageName\"5\n" + + "\tWIFIState\x12\x12\n" + + "\x04ssid\x18\x01 \x01(\tR\x04ssid\x12\x14\n" + + "\x05bssid\x18\x02 \x01(\tR\x05bssidB%Z#github.com/sagernet/sing-box/daemonb\x06proto3" + +var ( + file_daemon_helper_proto_rawDescOnce sync.Once + file_daemon_helper_proto_rawDescData []byte +) + +func file_daemon_helper_proto_rawDescGZIP() []byte { + file_daemon_helper_proto_rawDescOnce.Do(func() { + file_daemon_helper_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_daemon_helper_proto_rawDesc), len(file_daemon_helper_proto_rawDesc))) + }) + return file_daemon_helper_proto_rawDescData +} + +var ( + file_daemon_helper_proto_msgTypes = make([]protoimpl.MessageInfo, 7) + file_daemon_helper_proto_goTypes = []any{ + (*SubscribeHelperRequestRequest)(nil), // 0: daemon.SubscribeHelperRequestRequest + (*HelperRequest)(nil), // 1: daemon.HelperRequest + (*FindConnectionOwnerRequest)(nil), // 2: daemon.FindConnectionOwnerRequest + (*Notification)(nil), // 3: daemon.Notification + (*HelperResponse)(nil), // 4: daemon.HelperResponse + (*ConnectionOwner)(nil), // 5: daemon.ConnectionOwner + (*WIFIState)(nil), // 6: daemon.WIFIState + (*emptypb.Empty)(nil), // 7: google.protobuf.Empty + } +) + +var file_daemon_helper_proto_depIdxs = []int32{ + 7, // 0: daemon.HelperRequest.getWIFIState:type_name -> google.protobuf.Empty + 2, // 1: daemon.HelperRequest.findConnectionOwner:type_name -> daemon.FindConnectionOwnerRequest + 3, // 2: daemon.HelperRequest.sendNotification:type_name -> daemon.Notification + 6, // 3: daemon.HelperResponse.wifiState:type_name -> daemon.WIFIState + 5, // 4: daemon.HelperResponse.connectionOwner:type_name -> daemon.ConnectionOwner + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_daemon_helper_proto_init() } +func file_daemon_helper_proto_init() { + if File_daemon_helper_proto != nil { + return + } + file_daemon_helper_proto_msgTypes[1].OneofWrappers = []any{ + (*HelperRequest_GetWIFIState)(nil), + (*HelperRequest_FindConnectionOwner)(nil), + (*HelperRequest_SendNotification)(nil), + } + file_daemon_helper_proto_msgTypes[4].OneofWrappers = []any{ + (*HelperResponse_WifiState)(nil), + (*HelperResponse_Error)(nil), + (*HelperResponse_ConnectionOwner)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_helper_proto_rawDesc), len(file_daemon_helper_proto_rawDesc)), + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_daemon_helper_proto_goTypes, + DependencyIndexes: file_daemon_helper_proto_depIdxs, + MessageInfos: file_daemon_helper_proto_msgTypes, + }.Build() + File_daemon_helper_proto = out.File + file_daemon_helper_proto_goTypes = nil + file_daemon_helper_proto_depIdxs = nil +} diff --git a/daemon/helper.proto b/daemon/helper.proto new file mode 100644 index 000000000..8cf28075b --- /dev/null +++ b/daemon/helper.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; + +package daemon; +option go_package = "github.com/sagernet/sing-box/daemon"; + +import "google/protobuf/empty.proto"; + +message SubscribeHelperRequestRequest { + bool acceptGetWIFIStateRequests = 1; + bool acceptFindConnectionOwnerRequests = 2; + bool acceptSendNotificationRequests = 3; +} + +message HelperRequest { + int64 id = 1; + oneof request { + google.protobuf.Empty getWIFIState = 2; + FindConnectionOwnerRequest findConnectionOwner = 3; + Notification sendNotification = 4; + } +} + +message FindConnectionOwnerRequest { + int32 ipProtocol = 1; + string sourceAddress = 2; + int32 sourcePort = 3; + string destinationAddress = 4; + int32 destinationPort = 5; +} + +message Notification { + string identifier = 1; + string typeName = 2; + int32 typeId = 3; + string title = 4; + string subtitle = 5; + string body = 6; + string openURL = 7; +} + +message HelperResponse { + int64 id = 1; + oneof response { + WIFIState wifiState = 2; + string error = 3; + ConnectionOwner connectionOwner = 4; + } +} + +message ConnectionOwner { + int32 userId = 1; + string userName = 2; + string processPath = 3; + string androidPackageName = 4; +} + +message WIFIState { + string ssid = 1; + string bssid = 2; +} + diff --git a/daemon/instance.go b/daemon/instance.go new file mode 100644 index 000000000..9bdce84cf --- /dev/null +++ b/daemon/instance.go @@ -0,0 +1,133 @@ +package daemon + +import ( + "bytes" + "context" + + "github.com/sagernet/sing-box" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/urltest" + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/pause" +) + +type Instance struct { + ctx context.Context + cancel context.CancelFunc + instance *box.Box + clashServer adapter.ClashServer + cacheFile adapter.CacheFile + pauseManager pause.Manager + urlTestHistoryStorage *urltest.HistoryStorage +} + +func (s *StartedService) CheckConfig(configContent string) error { + options, err := parseConfig(s.ctx, configContent) + if err != nil { + return err + } + ctx, cancel := context.WithCancel(s.ctx) + defer cancel() + instance, err := box.New(box.Options{ + Context: ctx, + Options: options, + }) + if err == nil { + instance.Close() + } + return err +} + +func (s *StartedService) FormatConfig(configContent string) (string, error) { + options, err := parseConfig(s.ctx, configContent) + if err != nil { + return "", err + } + var buffer bytes.Buffer + encoder := json.NewEncoder(&buffer) + encoder.SetIndent("", " ") + err = encoder.Encode(options) + if err != nil { + return "", err + } + return buffer.String(), nil +} + +type OverrideOptions struct { + AutoRedirect bool + IncludePackage []string + ExcludePackage []string +} + +func (s *StartedService) newInstance(profileContent string, overrideOptions *OverrideOptions) (*Instance, error) { + ctx := s.ctx + service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager)) + ctx, cancel := context.WithCancel(include.Context(ctx)) + options, err := parseConfig(ctx, profileContent) + if err != nil { + cancel() + return nil, err + } + if overrideOptions != nil { + for _, inbound := range options.Inbounds { + if tunInboundOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN { + tunInboundOptions.AutoRedirect = overrideOptions.AutoRedirect + tunInboundOptions.IncludePackage = append(tunInboundOptions.IncludePackage, overrideOptions.IncludePackage...) + tunInboundOptions.ExcludePackage = append(tunInboundOptions.ExcludePackage, overrideOptions.ExcludePackage...) + break + } + } + } + urlTestHistoryStorage := urltest.NewHistoryStorage() + ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) + i := &Instance{ + ctx: ctx, + cancel: cancel, + urlTestHistoryStorage: urlTestHistoryStorage, + } + boxInstance, err := box.New(box.Options{ + Context: ctx, + Options: options, + PlatformLogWriter: s, + }) + if err != nil { + cancel() + return nil, err + } + i.instance = boxInstance + i.clashServer = service.FromContext[adapter.ClashServer](ctx) + i.pauseManager = service.FromContext[pause.Manager](ctx) + i.cacheFile = service.FromContext[adapter.CacheFile](ctx) + return i, nil +} + +func (i *Instance) Start() error { + return i.instance.Start() +} + +func (i *Instance) Close() error { + i.cancel() + i.urlTestHistoryStorage.Close() + return i.instance.Close() +} + +func (i *Instance) Box() *box.Box { + return i.instance +} + +func (i *Instance) PauseManager() pause.Manager { + return i.pauseManager +} + +func parseConfig(ctx context.Context, configContent string) (option.Options, error) { + options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent)) + if err != nil { + return option.Options{}, E.Cause(err, "decode config") + } + return options, nil +} diff --git a/daemon/platform.go b/daemon/platform.go new file mode 100644 index 000000000..37906aff0 --- /dev/null +++ b/daemon/platform.go @@ -0,0 +1,9 @@ +package daemon + +type PlatformHandler interface { + ServiceStop() error + ServiceReload() error + SystemProxyStatus() (*SystemProxyStatus, error) + SetSystemProxyEnabled(enabled bool) error + WriteDebugMessage(message string) +} diff --git a/daemon/started_service.go b/daemon/started_service.go new file mode 100644 index 000000000..8b926b27c --- /dev/null +++ b/daemon/started_service.go @@ -0,0 +1,830 @@ +package daemon + +import ( + "context" + "os" + "runtime" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/conntrack" + "github.com/sagernet/sing-box/common/urltest" + "github.com/sagernet/sing-box/experimental/clashapi" + "github.com/sagernet/sing-box/experimental/clashapi/trafficontrol" + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/protocol/group" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/batch" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/memory" + "github.com/sagernet/sing/common/observable" + "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" + + "github.com/gofrs/uuid/v5" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/emptypb" +) + +var _ StartedServiceServer = (*StartedService)(nil) + +type StartedService struct { + ctx context.Context + // platform adapter.PlatformInterface + handler PlatformHandler + debug bool + logMaxLines int + // workingDirectory string + // tempDirectory string + // userID int + // groupID int + // systemProxyEnabled bool + serviceAccess sync.RWMutex + serviceStatus *ServiceStatus + serviceStatusSubscriber *observable.Subscriber[*ServiceStatus] + serviceStatusObserver *observable.Observer[*ServiceStatus] + logAccess sync.RWMutex + logLines list.List[*log.Entry] + logSubscriber *observable.Subscriber[*log.Entry] + logObserver *observable.Observer[*log.Entry] + instance *Instance + urlTestSubscriber *observable.Subscriber[struct{}] + urlTestObserver *observable.Observer[struct{}] + urlTestHistoryStorage *urltest.HistoryStorage + clashModeSubscriber *observable.Subscriber[struct{}] + clashModeObserver *observable.Observer[struct{}] +} + +type ServiceOptions struct { + Context context.Context + // Platform adapter.PlatformInterface + Handler PlatformHandler + Debug bool + LogMaxLines int + // WorkingDirectory string + // TempDirectory string + // UserID int + // GroupID int + // SystemProxyEnabled bool +} + +func NewStartedService(options ServiceOptions) *StartedService { + s := &StartedService{ + ctx: options.Context, + // platform: options.Platform, + handler: options.Handler, + debug: options.Debug, + logMaxLines: options.LogMaxLines, + // workingDirectory: options.WorkingDirectory, + // tempDirectory: options.TempDirectory, + // userID: options.UserID, + // groupID: options.GroupID, + // systemProxyEnabled: options.SystemProxyEnabled, + serviceStatus: &ServiceStatus{Status: ServiceStatus_IDLE}, + serviceStatusSubscriber: observable.NewSubscriber[*ServiceStatus](4), + logSubscriber: observable.NewSubscriber[*log.Entry](128), + urlTestSubscriber: observable.NewSubscriber[struct{}](1), + urlTestHistoryStorage: urltest.NewHistoryStorage(), + clashModeSubscriber: observable.NewSubscriber[struct{}](1), + } + 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) + return s +} + +func (s *StartedService) resetLogs() { + s.logAccess.Lock() + s.logLines = list.List[*log.Entry]{} + s.logAccess.Unlock() + s.logSubscriber.Emit(nil) +} + +func (s *StartedService) updateStatus(newStatus ServiceStatus_Type) { + statusObject := &ServiceStatus{Status: newStatus} + s.serviceStatusSubscriber.Emit(statusObject) + s.serviceStatus = statusObject +} + +func (s *StartedService) updateStatusError(err error) error { + statusObject := &ServiceStatus{Status: ServiceStatus_FATAL, ErrorMessage: err.Error()} + s.serviceStatusSubscriber.Emit(statusObject) + s.serviceStatus = statusObject + s.serviceAccess.Unlock() + return err +} + +func (s *StartedService) waitForStarted(ctx context.Context) error { + s.serviceAccess.RLock() + currentStatus := s.serviceStatus.Status + s.serviceAccess.RUnlock() + + switch currentStatus { + case ServiceStatus_STARTED: + return nil + case ServiceStatus_STARTING: + default: + return os.ErrInvalid + } + + subscription, done, err := s.serviceStatusObserver.Subscribe() + if err != nil { + return err + } + defer s.serviceStatusObserver.UnSubscribe(subscription) + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-s.ctx.Done(): + return s.ctx.Err() + case status := <-subscription: + switch status.Status { + case ServiceStatus_STARTED: + return nil + case ServiceStatus_FATAL: + return E.New(status.ErrorMessage) + case ServiceStatus_IDLE, ServiceStatus_STOPPING: + return os.ErrInvalid + } + case <-done: + return os.ErrClosed + } + } +} + +func (s *StartedService) StartOrReloadService(profileContent string, options *OverrideOptions) error { + s.serviceAccess.Lock() + switch s.serviceStatus.Status { + case ServiceStatus_IDLE, ServiceStatus_STARTED, ServiceStatus_STARTING: + default: + s.serviceAccess.Unlock() + return os.ErrInvalid + } + oldInstance := s.instance + if oldInstance != nil { + s.updateStatus(ServiceStatus_STOPPING) + s.serviceAccess.Unlock() + _ = oldInstance.Close() + s.serviceAccess.Lock() + } + s.updateStatus(ServiceStatus_STARTING) + s.resetLogs() + instance, err := s.newInstance(profileContent, options) + if err != nil { + return s.updateStatusError(err) + } + s.instance = instance + instance.urlTestHistoryStorage.SetHook(s.urlTestSubscriber) + if instance.clashServer != nil { + instance.clashServer.SetModeUpdateHook(s.clashModeSubscriber) + } + s.serviceAccess.Unlock() + err = instance.Start() + s.serviceAccess.Lock() + if s.serviceStatus.Status != ServiceStatus_STARTING { + s.serviceAccess.Unlock() + return nil + } + if err != nil { + return s.updateStatusError(err) + } + s.updateStatus(ServiceStatus_STARTED) + s.serviceAccess.Unlock() + runtime.GC() + return nil +} + +func (s *StartedService) CloseService() error { + s.serviceAccess.Lock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.Unlock() + return os.ErrInvalid + } + s.updateStatus(ServiceStatus_STOPPING) + if s.instance != nil { + err := s.instance.Close() + if err != nil { + return s.updateStatusError(err) + } + } + s.instance = nil + s.updateStatus(ServiceStatus_IDLE) + s.serviceAccess.Unlock() + runtime.GC() + return nil +} + +func (s *StartedService) SetError(err error) { + s.serviceAccess.Lock() + s.updateStatusError(err) + s.WriteMessage(log.LevelError, err.Error()) +} + +func (s *StartedService) StopService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + err := s.handler.ServiceStop() + if err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) ReloadService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + err := s.handler.ServiceReload() + if err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) SubscribeServiceStatus(empty *emptypb.Empty, server grpc.ServerStreamingServer[ServiceStatus]) error { + subscription, done, err := s.serviceStatusObserver.Subscribe() + if err != nil { + return err + } + defer s.serviceStatusObserver.UnSubscribe(subscription) + err = server.Send(s.serviceStatus) + if err != nil { + return err + } + for { + select { + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case newStatus := <-subscription: + err = server.Send(newStatus) + if err != nil { + return err + } + case <-done: + return nil + } + } +} + +func (s *StartedService) SubscribeLog(empty *emptypb.Empty, server grpc.ServerStreamingServer[Log]) error { + var savedLines []*log.Entry + s.logAccess.Lock() + savedLines = make([]*log.Entry, 0, s.logLines.Len()) + for element := s.logLines.Front(); element != nil; element = element.Next() { + savedLines = append(savedLines, element.Value) + } + s.logAccess.Unlock() + subscription, done, err := s.logObserver.Subscribe() + if err != nil { + return err + } + defer s.logObserver.UnSubscribe(subscription) + err = server.Send(&Log{ + Messages: common.Map(savedLines, func(it *log.Entry) *Log_Message { + return &Log_Message{ + Level: LogLevel(it.Level), + Message: it.Message, + } + }), + Reset_: true, + }) + if err != nil { + return err + } + for { + select { + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case message := <-subscription: + var rawMessage Log + if message == nil { + rawMessage.Reset_ = true + } else { + rawMessage.Messages = append(rawMessage.Messages, &Log_Message{ + Level: LogLevel(message.Level), + Message: message.Message, + }) + } + fetch: + for { + select { + case message = <-subscription: + if message == nil { + rawMessage.Messages = nil + rawMessage.Reset_ = true + } else { + rawMessage.Messages = append(rawMessage.Messages, &Log_Message{ + Level: LogLevel(message.Level), + Message: message.Message, + }) + } + default: + break fetch + } + } + err = server.Send(&rawMessage) + if err != nil { + return err + } + case <-done: + return nil + } + } +} + +func (s *StartedService) GetDefaultLogLevel(ctx context.Context, empty *emptypb.Empty) (*DefaultLogLevel, error) { + s.serviceAccess.RLock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + logLevel := s.instance.instance.LogFactory().Level() + s.serviceAccess.RUnlock() + return &DefaultLogLevel{Level: LogLevel(logLevel)}, nil +} + +func (s *StartedService) SubscribeStatus(request *SubscribeStatusRequest, server grpc.ServerStreamingServer[Status]) error { + interval := time.Duration(request.Interval) + if interval <= 0 { + interval = time.Second // Default to 1 second + } + ticker := time.NewTicker(interval) + defer ticker.Stop() + status := s.readStatus() + uploadTotal := status.UplinkTotal + downloadTotal := status.DownlinkTotal + for { + err := server.Send(status) + if err != nil { + return err + } + select { + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-ticker.C: + } + status = s.readStatus() + upload := status.UplinkTotal - uploadTotal + download := status.DownlinkTotal - downloadTotal + uploadTotal = status.UplinkTotal + downloadTotal = status.DownlinkTotal + status.Uplink = upload + status.Downlink = download + } +} + +func (s *StartedService) readStatus() *Status { + var status Status + status.Memory = memory.Inuse() + status.Goroutines = int32(runtime.NumGoroutine()) + status.ConnectionsOut = int32(conntrack.Count()) + s.serviceAccess.RLock() + nowService := s.instance + s.serviceAccess.RUnlock() + if nowService != nil { + if clashServer := nowService.clashServer; clashServer != nil { + status.TrafficAvailable = true + trafficManager := clashServer.(*clashapi.Server).TrafficManager() + status.UplinkTotal, status.DownlinkTotal = trafficManager.Total() + status.ConnectionsIn = int32(trafficManager.ConnectionsLen()) + } + } + return &status +} + +func (s *StartedService) SubscribeGroups(empty *emptypb.Empty, server grpc.ServerStreamingServer[Groups]) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + subscription, done, err := s.urlTestObserver.Subscribe() + if err != nil { + return err + } + defer s.urlTestObserver.UnSubscribe(subscription) + for { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return os.ErrInvalid + } + groups := s.readGroups() + s.serviceAccess.RUnlock() + err = server.Send(groups) + if err != nil { + return err + } + select { + case <-subscription: + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-done: + return nil + } + } +} + +func (s *StartedService) readGroups() *Groups { + historyStorage := s.instance.urlTestHistoryStorage + boxService := s.instance + outbounds := boxService.instance.Outbound().Outbounds() + var iGroups []adapter.OutboundGroup + for _, it := range outbounds { + if group, isGroup := it.(adapter.OutboundGroup); isGroup { + iGroups = append(iGroups, group) + } + } + var gs Groups + for _, iGroup := range iGroups { + var g Group + g.Tag = iGroup.Tag() + g.Type = iGroup.Type() + _, g.Selectable = iGroup.(*group.Selector) + g.Selected = iGroup.Now() + if boxService.cacheFile != nil { + if isExpand, loaded := boxService.cacheFile.LoadGroupExpand(g.Tag); loaded { + g.IsExpand = isExpand + } + } + + for _, itemTag := range iGroup.All() { + itemOutbound, isLoaded := boxService.instance.Outbound().Outbound(itemTag) + if !isLoaded { + continue + } + + var item GroupItem + item.Tag = itemTag + item.Type = itemOutbound.Type() + if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil { + item.UrlTestTime = history.Time.Unix() + item.UrlTestDelay = int32(history.Delay) + } + g.Items = append(g.Items, &item) + } + if len(g.Items) < 2 { + continue + } + gs.Group = append(gs.Group, &g) + } + return &gs +} + +func (s *StartedService) GetClashModeStatus(ctx context.Context, empty *emptypb.Empty) (*ClashModeStatus, error) { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + clashServer := s.instance.clashServer + s.serviceAccess.RUnlock() + if clashServer == nil { + return nil, os.ErrInvalid + } + return &ClashModeStatus{ + ModeList: clashServer.ModeList(), + CurrentMode: clashServer.Mode(), + }, nil +} + +func (s *StartedService) SubscribeClashMode(empty *emptypb.Empty, server grpc.ServerStreamingServer[ClashMode]) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + subscription, done, err := s.clashModeObserver.Subscribe() + if err != nil { + return err + } + defer s.clashModeObserver.UnSubscribe(subscription) + for { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return os.ErrInvalid + } + message := &ClashMode{Mode: s.instance.clashServer.Mode()} + s.serviceAccess.RUnlock() + err = server.Send(message) + if err != nil { + return err + } + select { + case <-subscription: + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-done: + return nil + } + } +} + +func (s *StartedService) SetClashMode(ctx context.Context, request *ClashMode) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + clashServer := s.instance.clashServer + s.serviceAccess.RUnlock() + clashServer.(*clashapi.Server).SetMode(request.Mode) + return &emptypb.Empty{}, nil +} + +func (s *StartedService) URLTest(ctx context.Context, request *URLTestRequest) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance + s.serviceAccess.RUnlock() + groupTag := request.OutboundTag + abstractOutboundGroup, isLoaded := boxService.instance.Outbound().Outbound(groupTag) + if !isLoaded { + return nil, E.New("outbound group not found: ", groupTag) + } + outboundGroup, isOutboundGroup := abstractOutboundGroup.(adapter.OutboundGroup) + if !isOutboundGroup { + return nil, E.New("outbound is not a group: ", groupTag) + } + urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest) + if isURLTest { + go urlTest.CheckOutbounds() + } else { + historyStorage := boxService.urlTestHistoryStorage + + outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { + itOutbound, _ := boxService.instance.Outbound().Outbound(it) + return itOutbound + }), func(it adapter.Outbound) bool { + if it == nil { + return false + } + _, isGroup := it.(adapter.OutboundGroup) + if isGroup { + return false + } + return true + }) + b, _ := batch.New(boxService.ctx, batch.WithConcurrencyNum[any](10)) + for _, detour := range outbounds { + outboundToTest := detour + outboundTag := outboundToTest.Tag() + b.Go(outboundTag, func() (any, error) { + t, err := urltest.URLTest(boxService.ctx, "", outboundToTest) + if err != nil { + historyStorage.DeleteURLTestHistory(outboundTag) + } else { + historyStorage.StoreURLTestHistory(outboundTag, &adapter.URLTestHistory{ + Time: time.Now(), + Delay: t, + }) + } + return nil, nil + }) + } + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) SelectOutbound(ctx context.Context, request *SelectOutboundRequest) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance.instance + s.serviceAccess.RUnlock() + outboundGroup, isLoaded := boxService.Outbound().Outbound(request.GroupTag) + if !isLoaded { + return nil, E.New("selector not found: ", request.GroupTag) + } + selector, isSelector := outboundGroup.(*group.Selector) + if !isSelector { + return nil, E.New("outbound is not a selector: ", request.GroupTag) + } + if !selector.SelectOutbound(request.OutboundTag) { + return nil, E.New("outbound not found in selector: ", request.OutboundTag) + } + s.urlTestObserver.Emit(struct{}{}) + return &emptypb.Empty{}, nil +} + +func (s *StartedService) SetGroupExpand(ctx context.Context, request *SetGroupExpandRequest) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance + s.serviceAccess.RUnlock() + if boxService.cacheFile != nil { + err := boxService.cacheFile.StoreGroupExpand(request.GroupTag, request.IsExpand) + if err != nil { + return nil, err + } + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) GetSystemProxyStatus(ctx context.Context, empty *emptypb.Empty) (*SystemProxyStatus, error) { + return s.handler.SystemProxyStatus() +} + +func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) { + err := s.handler.SetSystemProxyEnabled(request.Enabled) + if err != nil { + return nil, err + } + return nil, err +} + +func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[Connections]) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + ticker := time.NewTicker(time.Duration(request.Interval)) + defer ticker.Stop() + trafficManager := boxService.clashServer.(*clashapi.Server).TrafficManager() + var ( + connections = make(map[uuid.UUID]*Connection) + outConnections []*Connection + ) + for { + outConnections = outConnections[:0] + for _, connection := range trafficManager.Connections() { + outConnections = append(outConnections, newConnection(connections, connection, false)) + } + for _, connection := range trafficManager.ClosedConnections() { + outConnections = append(outConnections, newConnection(connections, connection, true)) + } + err := server.Send(&Connections{Connections: outConnections}) + if err != nil { + return err + } + select { + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-ticker.C: + } + } +} + +func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol.TrackerMetadata, isClosed bool) *Connection { + if oldConnection, loaded := connections[metadata.ID]; loaded { + if isClosed { + if oldConnection.ClosedAt == 0 { + oldConnection.Uplink = 0 + oldConnection.Downlink = 0 + oldConnection.ClosedAt = metadata.ClosedAt.UnixMilli() + } + return oldConnection + } + lastUplink := oldConnection.UplinkTotal + lastDownlink := oldConnection.DownlinkTotal + uplinkTotal := metadata.Upload.Load() + downlinkTotal := metadata.Download.Load() + oldConnection.Uplink = uplinkTotal - lastUplink + oldConnection.Downlink = downlinkTotal - lastDownlink + oldConnection.UplinkTotal = uplinkTotal + oldConnection.DownlinkTotal = downlinkTotal + return oldConnection + } + var rule string + if metadata.Rule != nil { + rule = metadata.Rule.String() + } + uplinkTotal := metadata.Upload.Load() + downlinkTotal := metadata.Download.Load() + uplink := uplinkTotal + downlink := downlinkTotal + var closedAt int64 + if !metadata.ClosedAt.IsZero() { + closedAt = metadata.ClosedAt.UnixMilli() + uplink = 0 + downlink = 0 + } + connection := &Connection{ + Id: metadata.ID.String(), + Inbound: metadata.Metadata.Inbound, + InboundType: metadata.Metadata.InboundType, + IpVersion: int32(metadata.Metadata.IPVersion), + Network: metadata.Metadata.Network, + Source: metadata.Metadata.Source.String(), + Destination: metadata.Metadata.Destination.String(), + Domain: metadata.Metadata.Domain, + Protocol: metadata.Metadata.Protocol, + User: metadata.Metadata.User, + FromOutbound: metadata.Metadata.Outbound, + CreatedAt: metadata.CreatedAt.UnixMilli(), + ClosedAt: closedAt, + Uplink: uplink, + Downlink: downlink, + UplinkTotal: uplinkTotal, + DownlinkTotal: downlinkTotal, + Rule: rule, + Outbound: metadata.Outbound, + OutboundType: metadata.OutboundType, + ChainList: metadata.Chain, + } + connections[metadata.ID] = connection + return connection +} + +func (s *StartedService) CloseConnection(ctx context.Context, request *CloseConnectionRequest) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance + s.serviceAccess.RUnlock() + targetConn := boxService.clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(request.Id)) + if targetConn != nil { + targetConn.Close() + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) CloseAllConnections(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + conntrack.Close() + return &emptypb.Empty{}, nil +} + +func (s *StartedService) GetDeprecatedWarnings(ctx context.Context, empty *emptypb.Empty) (*DeprecatedWarnings, error) { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance + s.serviceAccess.RUnlock() + notes := service.FromContext[deprecated.Manager](boxService.ctx).(*deprecatedManager).Get() + return &DeprecatedWarnings{ + Warnings: common.Map(notes, func(it deprecated.Note) *DeprecatedWarning { + return &DeprecatedWarning{ + Message: it.Message(), + Impending: it.Impending(), + MigrationLink: it.MigrationLink, + } + }), + }, nil +} + +func (s *StartedService) SubscribeHelperEvents(empty *emptypb.Empty, server grpc.ServerStreamingServer[HelperRequest]) error { + return os.ErrInvalid +} + +func (s *StartedService) SendHelperResponse(ctx context.Context, response *HelperResponse) (*emptypb.Empty, error) { + return nil, os.ErrInvalid +} + +func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() { +} + +func (s *StartedService) WriteMessage(level log.Level, message string) { + item := &log.Entry{Level: level, Message: message} + s.logSubscriber.Emit(item) + s.logAccess.Lock() + s.logLines.PushBack(item) + if s.logLines.Len() > s.logMaxLines { + s.logLines.Remove(s.logLines.Front()) + } + s.logAccess.Unlock() + if s.debug { + s.handler.WriteDebugMessage(message) + } +} + +func (s *StartedService) Instance() *Instance { + s.serviceAccess.RLock() + defer s.serviceAccess.RUnlock() + return s.instance +} diff --git a/daemon/started_service.pb.go b/daemon/started_service.pb.go new file mode 100644 index 000000000..90cff9984 --- /dev/null +++ b/daemon/started_service.pb.go @@ -0,0 +1,1906 @@ +package daemon + +import ( + reflect "reflect" + sync "sync" + unsafe "unsafe" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type LogLevel int32 + +const ( + LogLevel_PANIC LogLevel = 0 + LogLevel_FATAL LogLevel = 1 + LogLevel_ERROR LogLevel = 2 + LogLevel_WARN LogLevel = 3 + LogLevel_INFO LogLevel = 4 + LogLevel_DEBUG LogLevel = 5 + LogLevel_TRACE LogLevel = 6 +) + +// Enum value maps for LogLevel. +var ( + LogLevel_name = map[int32]string{ + 0: "PANIC", + 1: "FATAL", + 2: "ERROR", + 3: "WARN", + 4: "INFO", + 5: "DEBUG", + 6: "TRACE", + } + LogLevel_value = map[string]int32{ + "PANIC": 0, + "FATAL": 1, + "ERROR": 2, + "WARN": 3, + "INFO": 4, + "DEBUG": 5, + "TRACE": 6, + } +) + +func (x LogLevel) Enum() *LogLevel { + p := new(LogLevel) + *p = x + return p +} + +func (x LogLevel) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (LogLevel) Descriptor() protoreflect.EnumDescriptor { + return file_daemon_started_service_proto_enumTypes[0].Descriptor() +} + +func (LogLevel) Type() protoreflect.EnumType { + return &file_daemon_started_service_proto_enumTypes[0] +} + +func (x LogLevel) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use LogLevel.Descriptor instead. +func (LogLevel) EnumDescriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{0} +} + +type ConnectionFilter int32 + +const ( + ConnectionFilter_ALL ConnectionFilter = 0 + ConnectionFilter_ACTIVE ConnectionFilter = 1 + ConnectionFilter_CLOSED ConnectionFilter = 2 +) + +// Enum value maps for ConnectionFilter. +var ( + ConnectionFilter_name = map[int32]string{ + 0: "ALL", + 1: "ACTIVE", + 2: "CLOSED", + } + ConnectionFilter_value = map[string]int32{ + "ALL": 0, + "ACTIVE": 1, + "CLOSED": 2, + } +) + +func (x ConnectionFilter) Enum() *ConnectionFilter { + p := new(ConnectionFilter) + *p = x + return p +} + +func (x ConnectionFilter) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConnectionFilter) Descriptor() protoreflect.EnumDescriptor { + return file_daemon_started_service_proto_enumTypes[1].Descriptor() +} + +func (ConnectionFilter) Type() protoreflect.EnumType { + return &file_daemon_started_service_proto_enumTypes[1] +} + +func (x ConnectionFilter) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConnectionFilter.Descriptor instead. +func (ConnectionFilter) EnumDescriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{1} +} + +type ConnectionSortBy int32 + +const ( + ConnectionSortBy_DATE ConnectionSortBy = 0 + ConnectionSortBy_TRAFFIC ConnectionSortBy = 1 + ConnectionSortBy_TOTAL_TRAFFIC ConnectionSortBy = 2 +) + +// Enum value maps for ConnectionSortBy. +var ( + ConnectionSortBy_name = map[int32]string{ + 0: "DATE", + 1: "TRAFFIC", + 2: "TOTAL_TRAFFIC", + } + ConnectionSortBy_value = map[string]int32{ + "DATE": 0, + "TRAFFIC": 1, + "TOTAL_TRAFFIC": 2, + } +) + +func (x ConnectionSortBy) Enum() *ConnectionSortBy { + p := new(ConnectionSortBy) + *p = x + return p +} + +func (x ConnectionSortBy) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConnectionSortBy) Descriptor() protoreflect.EnumDescriptor { + return file_daemon_started_service_proto_enumTypes[2].Descriptor() +} + +func (ConnectionSortBy) Type() protoreflect.EnumType { + return &file_daemon_started_service_proto_enumTypes[2] +} + +func (x ConnectionSortBy) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConnectionSortBy.Descriptor instead. +func (ConnectionSortBy) EnumDescriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{2} +} + +type ServiceStatus_Type int32 + +const ( + ServiceStatus_IDLE ServiceStatus_Type = 0 + ServiceStatus_STARTING ServiceStatus_Type = 1 + ServiceStatus_STARTED ServiceStatus_Type = 2 + ServiceStatus_STOPPING ServiceStatus_Type = 3 + ServiceStatus_FATAL ServiceStatus_Type = 4 +) + +// Enum value maps for ServiceStatus_Type. +var ( + ServiceStatus_Type_name = map[int32]string{ + 0: "IDLE", + 1: "STARTING", + 2: "STARTED", + 3: "STOPPING", + 4: "FATAL", + } + ServiceStatus_Type_value = map[string]int32{ + "IDLE": 0, + "STARTING": 1, + "STARTED": 2, + "STOPPING": 3, + "FATAL": 4, + } +) + +func (x ServiceStatus_Type) Enum() *ServiceStatus_Type { + p := new(ServiceStatus_Type) + *p = x + return p +} + +func (x ServiceStatus_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ServiceStatus_Type) Descriptor() protoreflect.EnumDescriptor { + return file_daemon_started_service_proto_enumTypes[3].Descriptor() +} + +func (ServiceStatus_Type) Type() protoreflect.EnumType { + return &file_daemon_started_service_proto_enumTypes[3] +} + +func (x ServiceStatus_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ServiceStatus_Type.Descriptor instead. +func (ServiceStatus_Type) EnumDescriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{0, 0} +} + +type ServiceStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status ServiceStatus_Type `protobuf:"varint,1,opt,name=status,proto3,enum=daemon.ServiceStatus_Type" json:"status,omitempty"` + ErrorMessage string `protobuf:"bytes,2,opt,name=errorMessage,proto3" json:"errorMessage,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ServiceStatus) Reset() { + *x = ServiceStatus{} + mi := &file_daemon_started_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ServiceStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServiceStatus) ProtoMessage() {} + +func (x *ServiceStatus) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[0] + 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 ServiceStatus.ProtoReflect.Descriptor instead. +func (*ServiceStatus) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{0} +} + +func (x *ServiceStatus) GetStatus() ServiceStatus_Type { + if x != nil { + return x.Status + } + return ServiceStatus_IDLE +} + +func (x *ServiceStatus) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + +type ReloadServiceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NewProfileContent string `protobuf:"bytes,1,opt,name=newProfileContent,proto3" json:"newProfileContent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReloadServiceRequest) Reset() { + *x = ReloadServiceRequest{} + mi := &file_daemon_started_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReloadServiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReloadServiceRequest) ProtoMessage() {} + +func (x *ReloadServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[1] + 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 ReloadServiceRequest.ProtoReflect.Descriptor instead. +func (*ReloadServiceRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{1} +} + +func (x *ReloadServiceRequest) GetNewProfileContent() string { + if x != nil { + return x.NewProfileContent + } + return "" +} + +type SubscribeStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Interval int64 `protobuf:"varint,1,opt,name=interval,proto3" json:"interval,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscribeStatusRequest) Reset() { + *x = SubscribeStatusRequest{} + mi := &file_daemon_started_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscribeStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscribeStatusRequest) ProtoMessage() {} + +func (x *SubscribeStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[2] + 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 SubscribeStatusRequest.ProtoReflect.Descriptor instead. +func (*SubscribeStatusRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{2} +} + +func (x *SubscribeStatusRequest) GetInterval() int64 { + if x != nil { + return x.Interval + } + return 0 +} + +type Log struct { + state protoimpl.MessageState `protogen:"open.v1"` + Messages []*Log_Message `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"` + Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Log) Reset() { + *x = Log{} + mi := &file_daemon_started_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Log) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Log) ProtoMessage() {} + +func (x *Log) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[3] + 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 Log.ProtoReflect.Descriptor instead. +func (*Log) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{3} +} + +func (x *Log) GetMessages() []*Log_Message { + if x != nil { + return x.Messages + } + return nil +} + +func (x *Log) GetReset_() bool { + if x != nil { + return x.Reset_ + } + return false +} + +type DefaultLogLevel struct { + state protoimpl.MessageState `protogen:"open.v1"` + Level LogLevel `protobuf:"varint,1,opt,name=level,proto3,enum=daemon.LogLevel" json:"level,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DefaultLogLevel) Reset() { + *x = DefaultLogLevel{} + mi := &file_daemon_started_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DefaultLogLevel) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DefaultLogLevel) ProtoMessage() {} + +func (x *DefaultLogLevel) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[4] + 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 DefaultLogLevel.ProtoReflect.Descriptor instead. +func (*DefaultLogLevel) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{4} +} + +func (x *DefaultLogLevel) GetLevel() LogLevel { + if x != nil { + return x.Level + } + return LogLevel_PANIC +} + +type Status struct { + state protoimpl.MessageState `protogen:"open.v1"` + Memory uint64 `protobuf:"varint,1,opt,name=memory,proto3" json:"memory,omitempty"` + Goroutines int32 `protobuf:"varint,2,opt,name=goroutines,proto3" json:"goroutines,omitempty"` + ConnectionsIn int32 `protobuf:"varint,3,opt,name=connectionsIn,proto3" json:"connectionsIn,omitempty"` + ConnectionsOut int32 `protobuf:"varint,4,opt,name=connectionsOut,proto3" json:"connectionsOut,omitempty"` + TrafficAvailable bool `protobuf:"varint,5,opt,name=trafficAvailable,proto3" json:"trafficAvailable,omitempty"` + Uplink int64 `protobuf:"varint,6,opt,name=uplink,proto3" json:"uplink,omitempty"` + Downlink int64 `protobuf:"varint,7,opt,name=downlink,proto3" json:"downlink,omitempty"` + UplinkTotal int64 `protobuf:"varint,8,opt,name=uplinkTotal,proto3" json:"uplinkTotal,omitempty"` + DownlinkTotal int64 `protobuf:"varint,9,opt,name=downlinkTotal,proto3" json:"downlinkTotal,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Status) Reset() { + *x = Status{} + mi := &file_daemon_started_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Status) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Status) ProtoMessage() {} + +func (x *Status) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[5] + 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 Status.ProtoReflect.Descriptor instead. +func (*Status) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{5} +} + +func (x *Status) GetMemory() uint64 { + if x != nil { + return x.Memory + } + return 0 +} + +func (x *Status) GetGoroutines() int32 { + if x != nil { + return x.Goroutines + } + return 0 +} + +func (x *Status) GetConnectionsIn() int32 { + if x != nil { + return x.ConnectionsIn + } + return 0 +} + +func (x *Status) GetConnectionsOut() int32 { + if x != nil { + return x.ConnectionsOut + } + return 0 +} + +func (x *Status) GetTrafficAvailable() bool { + if x != nil { + return x.TrafficAvailable + } + return false +} + +func (x *Status) GetUplink() int64 { + if x != nil { + return x.Uplink + } + return 0 +} + +func (x *Status) GetDownlink() int64 { + if x != nil { + return x.Downlink + } + return 0 +} + +func (x *Status) GetUplinkTotal() int64 { + if x != nil { + return x.UplinkTotal + } + return 0 +} + +func (x *Status) GetDownlinkTotal() int64 { + if x != nil { + return x.DownlinkTotal + } + return 0 +} + +type Groups struct { + state protoimpl.MessageState `protogen:"open.v1"` + Group []*Group `protobuf:"bytes,1,rep,name=group,proto3" json:"group,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Groups) Reset() { + *x = Groups{} + mi := &file_daemon_started_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Groups) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Groups) ProtoMessage() {} + +func (x *Groups) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[6] + 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 Groups.ProtoReflect.Descriptor instead. +func (*Groups) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{6} +} + +func (x *Groups) GetGroup() []*Group { + if x != nil { + return x.Group + } + return nil +} + +type Group struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + Selectable bool `protobuf:"varint,3,opt,name=selectable,proto3" json:"selectable,omitempty"` + Selected string `protobuf:"bytes,4,opt,name=selected,proto3" json:"selected,omitempty"` + IsExpand bool `protobuf:"varint,5,opt,name=isExpand,proto3" json:"isExpand,omitempty"` + Items []*GroupItem `protobuf:"bytes,6,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Group) Reset() { + *x = Group{} + mi := &file_daemon_started_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Group) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Group) ProtoMessage() {} + +func (x *Group) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[7] + 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 Group.ProtoReflect.Descriptor instead. +func (*Group) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{7} +} + +func (x *Group) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + +func (x *Group) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Group) GetSelectable() bool { + if x != nil { + return x.Selectable + } + return false +} + +func (x *Group) GetSelected() string { + if x != nil { + return x.Selected + } + return "" +} + +func (x *Group) GetIsExpand() bool { + if x != nil { + return x.IsExpand + } + return false +} + +func (x *Group) GetItems() []*GroupItem { + if x != nil { + return x.Items + } + return nil +} + +type GroupItem struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + UrlTestTime int64 `protobuf:"varint,3,opt,name=urlTestTime,proto3" json:"urlTestTime,omitempty"` + UrlTestDelay int32 `protobuf:"varint,4,opt,name=urlTestDelay,proto3" json:"urlTestDelay,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GroupItem) Reset() { + *x = GroupItem{} + mi := &file_daemon_started_service_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GroupItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GroupItem) ProtoMessage() {} + +func (x *GroupItem) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[8] + 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 GroupItem.ProtoReflect.Descriptor instead. +func (*GroupItem) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{8} +} + +func (x *GroupItem) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + +func (x *GroupItem) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *GroupItem) GetUrlTestTime() int64 { + if x != nil { + return x.UrlTestTime + } + return 0 +} + +func (x *GroupItem) GetUrlTestDelay() int32 { + if x != nil { + return x.UrlTestDelay + } + return 0 +} + +type URLTestRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + OutboundTag string `protobuf:"bytes,1,opt,name=outboundTag,proto3" json:"outboundTag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *URLTestRequest) Reset() { + *x = URLTestRequest{} + mi := &file_daemon_started_service_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *URLTestRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*URLTestRequest) ProtoMessage() {} + +func (x *URLTestRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[9] + 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 URLTestRequest.ProtoReflect.Descriptor instead. +func (*URLTestRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{9} +} + +func (x *URLTestRequest) GetOutboundTag() string { + if x != nil { + return x.OutboundTag + } + return "" +} + +type SelectOutboundRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + GroupTag string `protobuf:"bytes,1,opt,name=groupTag,proto3" json:"groupTag,omitempty"` + OutboundTag string `protobuf:"bytes,2,opt,name=outboundTag,proto3" json:"outboundTag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SelectOutboundRequest) Reset() { + *x = SelectOutboundRequest{} + mi := &file_daemon_started_service_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SelectOutboundRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SelectOutboundRequest) ProtoMessage() {} + +func (x *SelectOutboundRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[10] + 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 SelectOutboundRequest.ProtoReflect.Descriptor instead. +func (*SelectOutboundRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{10} +} + +func (x *SelectOutboundRequest) GetGroupTag() string { + if x != nil { + return x.GroupTag + } + return "" +} + +func (x *SelectOutboundRequest) GetOutboundTag() string { + if x != nil { + return x.OutboundTag + } + return "" +} + +type SetGroupExpandRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + GroupTag string `protobuf:"bytes,1,opt,name=groupTag,proto3" json:"groupTag,omitempty"` + IsExpand bool `protobuf:"varint,2,opt,name=isExpand,proto3" json:"isExpand,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetGroupExpandRequest) Reset() { + *x = SetGroupExpandRequest{} + mi := &file_daemon_started_service_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetGroupExpandRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetGroupExpandRequest) ProtoMessage() {} + +func (x *SetGroupExpandRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[11] + 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 SetGroupExpandRequest.ProtoReflect.Descriptor instead. +func (*SetGroupExpandRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{11} +} + +func (x *SetGroupExpandRequest) GetGroupTag() string { + if x != nil { + return x.GroupTag + } + return "" +} + +func (x *SetGroupExpandRequest) GetIsExpand() bool { + if x != nil { + return x.IsExpand + } + return false +} + +type ClashMode struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mode string `protobuf:"bytes,3,opt,name=mode,proto3" json:"mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClashMode) Reset() { + *x = ClashMode{} + mi := &file_daemon_started_service_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClashMode) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClashMode) ProtoMessage() {} + +func (x *ClashMode) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[12] + 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 ClashMode.ProtoReflect.Descriptor instead. +func (*ClashMode) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{12} +} + +func (x *ClashMode) GetMode() string { + if x != nil { + return x.Mode + } + return "" +} + +type ClashModeStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + ModeList []string `protobuf:"bytes,1,rep,name=modeList,proto3" json:"modeList,omitempty"` + CurrentMode string `protobuf:"bytes,2,opt,name=currentMode,proto3" json:"currentMode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClashModeStatus) Reset() { + *x = ClashModeStatus{} + mi := &file_daemon_started_service_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClashModeStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClashModeStatus) ProtoMessage() {} + +func (x *ClashModeStatus) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[13] + 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 ClashModeStatus.ProtoReflect.Descriptor instead. +func (*ClashModeStatus) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{13} +} + +func (x *ClashModeStatus) GetModeList() []string { + if x != nil { + return x.ModeList + } + return nil +} + +func (x *ClashModeStatus) GetCurrentMode() string { + if x != nil { + return x.CurrentMode + } + return "" +} + +type SystemProxyStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + Available bool `protobuf:"varint,1,opt,name=available,proto3" json:"available,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SystemProxyStatus) Reset() { + *x = SystemProxyStatus{} + mi := &file_daemon_started_service_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SystemProxyStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SystemProxyStatus) ProtoMessage() {} + +func (x *SystemProxyStatus) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[14] + 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 SystemProxyStatus.ProtoReflect.Descriptor instead. +func (*SystemProxyStatus) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{14} +} + +func (x *SystemProxyStatus) GetAvailable() bool { + if x != nil { + return x.Available + } + return false +} + +func (x *SystemProxyStatus) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +type SetSystemProxyEnabledRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetSystemProxyEnabledRequest) Reset() { + *x = SetSystemProxyEnabledRequest{} + mi := &file_daemon_started_service_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetSystemProxyEnabledRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetSystemProxyEnabledRequest) ProtoMessage() {} + +func (x *SetSystemProxyEnabledRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[15] + 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 SetSystemProxyEnabledRequest.ProtoReflect.Descriptor instead. +func (*SetSystemProxyEnabledRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{15} +} + +func (x *SetSystemProxyEnabledRequest) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +type SubscribeConnectionsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Interval int64 `protobuf:"varint,1,opt,name=interval,proto3" json:"interval,omitempty"` + Filter ConnectionFilter `protobuf:"varint,2,opt,name=filter,proto3,enum=daemon.ConnectionFilter" json:"filter,omitempty"` + SortBy ConnectionSortBy `protobuf:"varint,3,opt,name=sortBy,proto3,enum=daemon.ConnectionSortBy" json:"sortBy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscribeConnectionsRequest) Reset() { + *x = SubscribeConnectionsRequest{} + mi := &file_daemon_started_service_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscribeConnectionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscribeConnectionsRequest) ProtoMessage() {} + +func (x *SubscribeConnectionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[16] + 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 SubscribeConnectionsRequest.ProtoReflect.Descriptor instead. +func (*SubscribeConnectionsRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{16} +} + +func (x *SubscribeConnectionsRequest) GetInterval() int64 { + if x != nil { + return x.Interval + } + return 0 +} + +func (x *SubscribeConnectionsRequest) GetFilter() ConnectionFilter { + if x != nil { + return x.Filter + } + return ConnectionFilter_ALL +} + +func (x *SubscribeConnectionsRequest) GetSortBy() ConnectionSortBy { + if x != nil { + return x.SortBy + } + return ConnectionSortBy_DATE +} + +type Connections struct { + state protoimpl.MessageState `protogen:"open.v1"` + Connections []*Connection `protobuf:"bytes,1,rep,name=connections,proto3" json:"connections,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Connections) Reset() { + *x = Connections{} + mi := &file_daemon_started_service_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Connections) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Connections) ProtoMessage() {} + +func (x *Connections) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[17] + 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 Connections.ProtoReflect.Descriptor instead. +func (*Connections) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{17} +} + +func (x *Connections) GetConnections() []*Connection { + if x != nil { + return x.Connections + } + return nil +} + +type Connection struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Inbound string `protobuf:"bytes,2,opt,name=inbound,proto3" json:"inbound,omitempty"` + InboundType string `protobuf:"bytes,3,opt,name=inboundType,proto3" json:"inboundType,omitempty"` + IpVersion int32 `protobuf:"varint,4,opt,name=ipVersion,proto3" json:"ipVersion,omitempty"` + Network string `protobuf:"bytes,5,opt,name=network,proto3" json:"network,omitempty"` + Source string `protobuf:"bytes,6,opt,name=source,proto3" json:"source,omitempty"` + Destination string `protobuf:"bytes,7,opt,name=destination,proto3" json:"destination,omitempty"` + Domain string `protobuf:"bytes,8,opt,name=domain,proto3" json:"domain,omitempty"` + Protocol string `protobuf:"bytes,9,opt,name=protocol,proto3" json:"protocol,omitempty"` + User string `protobuf:"bytes,10,opt,name=user,proto3" json:"user,omitempty"` + FromOutbound string `protobuf:"bytes,11,opt,name=fromOutbound,proto3" json:"fromOutbound,omitempty"` + CreatedAt int64 `protobuf:"varint,12,opt,name=createdAt,proto3" json:"createdAt,omitempty"` + ClosedAt int64 `protobuf:"varint,13,opt,name=closedAt,proto3" json:"closedAt,omitempty"` + Uplink int64 `protobuf:"varint,14,opt,name=uplink,proto3" json:"uplink,omitempty"` + Downlink int64 `protobuf:"varint,15,opt,name=downlink,proto3" json:"downlink,omitempty"` + UplinkTotal int64 `protobuf:"varint,16,opt,name=uplinkTotal,proto3" json:"uplinkTotal,omitempty"` + DownlinkTotal int64 `protobuf:"varint,17,opt,name=downlinkTotal,proto3" json:"downlinkTotal,omitempty"` + Rule string `protobuf:"bytes,18,opt,name=rule,proto3" json:"rule,omitempty"` + Outbound string `protobuf:"bytes,19,opt,name=outbound,proto3" json:"outbound,omitempty"` + OutboundType string `protobuf:"bytes,20,opt,name=outboundType,proto3" json:"outboundType,omitempty"` + ChainList []string `protobuf:"bytes,21,rep,name=chainList,proto3" json:"chainList,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Connection) Reset() { + *x = Connection{} + mi := &file_daemon_started_service_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Connection) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Connection) ProtoMessage() {} + +func (x *Connection) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[18] + 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 Connection.ProtoReflect.Descriptor instead. +func (*Connection) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{18} +} + +func (x *Connection) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Connection) GetInbound() string { + if x != nil { + return x.Inbound + } + return "" +} + +func (x *Connection) GetInboundType() string { + if x != nil { + return x.InboundType + } + return "" +} + +func (x *Connection) GetIpVersion() int32 { + if x != nil { + return x.IpVersion + } + return 0 +} + +func (x *Connection) GetNetwork() string { + if x != nil { + return x.Network + } + return "" +} + +func (x *Connection) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *Connection) GetDestination() string { + if x != nil { + return x.Destination + } + return "" +} + +func (x *Connection) GetDomain() string { + if x != nil { + return x.Domain + } + return "" +} + +func (x *Connection) GetProtocol() string { + if x != nil { + return x.Protocol + } + return "" +} + +func (x *Connection) GetUser() string { + if x != nil { + return x.User + } + return "" +} + +func (x *Connection) GetFromOutbound() string { + if x != nil { + return x.FromOutbound + } + return "" +} + +func (x *Connection) GetCreatedAt() int64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *Connection) GetClosedAt() int64 { + if x != nil { + return x.ClosedAt + } + return 0 +} + +func (x *Connection) GetUplink() int64 { + if x != nil { + return x.Uplink + } + return 0 +} + +func (x *Connection) GetDownlink() int64 { + if x != nil { + return x.Downlink + } + return 0 +} + +func (x *Connection) GetUplinkTotal() int64 { + if x != nil { + return x.UplinkTotal + } + return 0 +} + +func (x *Connection) GetDownlinkTotal() int64 { + if x != nil { + return x.DownlinkTotal + } + return 0 +} + +func (x *Connection) GetRule() string { + if x != nil { + return x.Rule + } + return "" +} + +func (x *Connection) GetOutbound() string { + if x != nil { + return x.Outbound + } + return "" +} + +func (x *Connection) GetOutboundType() string { + if x != nil { + return x.OutboundType + } + return "" +} + +func (x *Connection) GetChainList() []string { + if x != nil { + return x.ChainList + } + return nil +} + +type CloseConnectionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CloseConnectionRequest) Reset() { + *x = CloseConnectionRequest{} + mi := &file_daemon_started_service_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CloseConnectionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CloseConnectionRequest) ProtoMessage() {} + +func (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[19] + 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 CloseConnectionRequest.ProtoReflect.Descriptor instead. +func (*CloseConnectionRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{19} +} + +func (x *CloseConnectionRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type DeprecatedWarnings struct { + state protoimpl.MessageState `protogen:"open.v1"` + Warnings []*DeprecatedWarning `protobuf:"bytes,1,rep,name=warnings,proto3" json:"warnings,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeprecatedWarnings) Reset() { + *x = DeprecatedWarnings{} + mi := &file_daemon_started_service_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeprecatedWarnings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeprecatedWarnings) ProtoMessage() {} + +func (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[20] + 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 DeprecatedWarnings.ProtoReflect.Descriptor instead. +func (*DeprecatedWarnings) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{20} +} + +func (x *DeprecatedWarnings) GetWarnings() []*DeprecatedWarning { + if x != nil { + return x.Warnings + } + return nil +} + +type DeprecatedWarning struct { + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + Impending bool `protobuf:"varint,2,opt,name=impending,proto3" json:"impending,omitempty"` + MigrationLink string `protobuf:"bytes,3,opt,name=migrationLink,proto3" json:"migrationLink,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeprecatedWarning) Reset() { + *x = DeprecatedWarning{} + mi := &file_daemon_started_service_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeprecatedWarning) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeprecatedWarning) ProtoMessage() {} + +func (x *DeprecatedWarning) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[21] + 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 DeprecatedWarning.ProtoReflect.Descriptor instead. +func (*DeprecatedWarning) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{21} +} + +func (x *DeprecatedWarning) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *DeprecatedWarning) GetImpending() bool { + if x != nil { + return x.Impending + } + return false +} + +func (x *DeprecatedWarning) GetMigrationLink() string { + if x != nil { + return x.MigrationLink + } + return "" +} + +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"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Log_Message) Reset() { + *x = Log_Message{} + mi := &file_daemon_started_service_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Log_Message) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Log_Message) ProtoMessage() {} + +func (x *Log_Message) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[22] + 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 Log_Message.ProtoReflect.Descriptor instead. +func (*Log_Message) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{3, 0} +} + +func (x *Log_Message) GetLevel() LogLevel { + if x != nil { + return x.Level + } + return LogLevel_PANIC +} + +func (x *Log_Message) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_daemon_started_service_proto protoreflect.FileDescriptor + +const file_daemon_started_service_proto_rawDesc = "" + + "\n" + + "\x1cdaemon/started_service.proto\x12\x06daemon\x1a\x1bgoogle/protobuf/empty.proto\x1a\x13daemon/helper.proto\"\xad\x01\n" + + "\rServiceStatus\x122\n" + + "\x06status\x18\x01 \x01(\x0e2\x1a.daemon.ServiceStatus.TypeR\x06status\x12\"\n" + + "\ferrorMessage\x18\x02 \x01(\tR\ferrorMessage\"D\n" + + "\x04Type\x12\b\n" + + "\x04IDLE\x10\x00\x12\f\n" + + "\bSTARTING\x10\x01\x12\v\n" + + "\aSTARTED\x10\x02\x12\f\n" + + "\bSTOPPING\x10\x03\x12\t\n" + + "\x05FATAL\x10\x04\"D\n" + + "\x14ReloadServiceRequest\x12,\n" + + "\x11newProfileContent\x18\x01 \x01(\tR\x11newProfileContent\"4\n" + + "\x16SubscribeStatusRequest\x12\x1a\n" + + "\binterval\x18\x01 \x01(\x03R\binterval\"\x99\x01\n" + + "\x03Log\x12/\n" + + "\bmessages\x18\x01 \x03(\v2\x13.daemon.Log.MessageR\bmessages\x12\x14\n" + + "\x05reset\x18\x02 \x01(\bR\x05reset\x1aK\n" + + "\aMessage\x12&\n" + + "\x05level\x18\x01 \x01(\x0e2\x10.daemon.LogLevelR\x05level\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\"9\n" + + "\x0fDefaultLogLevel\x12&\n" + + "\x05level\x18\x01 \x01(\x0e2\x10.daemon.LogLevelR\x05level\"\xb6\x02\n" + + "\x06Status\x12\x16\n" + + "\x06memory\x18\x01 \x01(\x04R\x06memory\x12\x1e\n" + + "\n" + + "goroutines\x18\x02 \x01(\x05R\n" + + "goroutines\x12$\n" + + "\rconnectionsIn\x18\x03 \x01(\x05R\rconnectionsIn\x12&\n" + + "\x0econnectionsOut\x18\x04 \x01(\x05R\x0econnectionsOut\x12*\n" + + "\x10trafficAvailable\x18\x05 \x01(\bR\x10trafficAvailable\x12\x16\n" + + "\x06uplink\x18\x06 \x01(\x03R\x06uplink\x12\x1a\n" + + "\bdownlink\x18\a \x01(\x03R\bdownlink\x12 \n" + + "\vuplinkTotal\x18\b \x01(\x03R\vuplinkTotal\x12$\n" + + "\rdownlinkTotal\x18\t \x01(\x03R\rdownlinkTotal\"-\n" + + "\x06Groups\x12#\n" + + "\x05group\x18\x01 \x03(\v2\r.daemon.GroupR\x05group\"\xae\x01\n" + + "\x05Group\x12\x10\n" + + "\x03tag\x18\x01 \x01(\tR\x03tag\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12\x1e\n" + + "\n" + + "selectable\x18\x03 \x01(\bR\n" + + "selectable\x12\x1a\n" + + "\bselected\x18\x04 \x01(\tR\bselected\x12\x1a\n" + + "\bisExpand\x18\x05 \x01(\bR\bisExpand\x12'\n" + + "\x05items\x18\x06 \x03(\v2\x11.daemon.GroupItemR\x05items\"w\n" + + "\tGroupItem\x12\x10\n" + + "\x03tag\x18\x01 \x01(\tR\x03tag\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12 \n" + + "\vurlTestTime\x18\x03 \x01(\x03R\vurlTestTime\x12\"\n" + + "\furlTestDelay\x18\x04 \x01(\x05R\furlTestDelay\"2\n" + + "\x0eURLTestRequest\x12 \n" + + "\voutboundTag\x18\x01 \x01(\tR\voutboundTag\"U\n" + + "\x15SelectOutboundRequest\x12\x1a\n" + + "\bgroupTag\x18\x01 \x01(\tR\bgroupTag\x12 \n" + + "\voutboundTag\x18\x02 \x01(\tR\voutboundTag\"O\n" + + "\x15SetGroupExpandRequest\x12\x1a\n" + + "\bgroupTag\x18\x01 \x01(\tR\bgroupTag\x12\x1a\n" + + "\bisExpand\x18\x02 \x01(\bR\bisExpand\"\x1f\n" + + "\tClashMode\x12\x12\n" + + "\x04mode\x18\x03 \x01(\tR\x04mode\"O\n" + + "\x0fClashModeStatus\x12\x1a\n" + + "\bmodeList\x18\x01 \x03(\tR\bmodeList\x12 \n" + + "\vcurrentMode\x18\x02 \x01(\tR\vcurrentMode\"K\n" + + "\x11SystemProxyStatus\x12\x1c\n" + + "\tavailable\x18\x01 \x01(\bR\tavailable\x12\x18\n" + + "\aenabled\x18\x02 \x01(\bR\aenabled\"8\n" + + "\x1cSetSystemProxyEnabledRequest\x12\x18\n" + + "\aenabled\x18\x01 \x01(\bR\aenabled\"\x9d\x01\n" + + "\x1bSubscribeConnectionsRequest\x12\x1a\n" + + "\binterval\x18\x01 \x01(\x03R\binterval\x120\n" + + "\x06filter\x18\x02 \x01(\x0e2\x18.daemon.ConnectionFilterR\x06filter\x120\n" + + "\x06sortBy\x18\x03 \x01(\x0e2\x18.daemon.ConnectionSortByR\x06sortBy\"C\n" + + "\vConnections\x124\n" + + "\vconnections\x18\x01 \x03(\v2\x12.daemon.ConnectionR\vconnections\"\xde\x04\n" + + "\n" + + "Connection\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\ainbound\x18\x02 \x01(\tR\ainbound\x12 \n" + + "\vinboundType\x18\x03 \x01(\tR\vinboundType\x12\x1c\n" + + "\tipVersion\x18\x04 \x01(\x05R\tipVersion\x12\x18\n" + + "\anetwork\x18\x05 \x01(\tR\anetwork\x12\x16\n" + + "\x06source\x18\x06 \x01(\tR\x06source\x12 \n" + + "\vdestination\x18\a \x01(\tR\vdestination\x12\x16\n" + + "\x06domain\x18\b \x01(\tR\x06domain\x12\x1a\n" + + "\bprotocol\x18\t \x01(\tR\bprotocol\x12\x12\n" + + "\x04user\x18\n" + + " \x01(\tR\x04user\x12\"\n" + + "\ffromOutbound\x18\v \x01(\tR\ffromOutbound\x12\x1c\n" + + "\tcreatedAt\x18\f \x01(\x03R\tcreatedAt\x12\x1a\n" + + "\bclosedAt\x18\r \x01(\x03R\bclosedAt\x12\x16\n" + + "\x06uplink\x18\x0e \x01(\x03R\x06uplink\x12\x1a\n" + + "\bdownlink\x18\x0f \x01(\x03R\bdownlink\x12 \n" + + "\vuplinkTotal\x18\x10 \x01(\x03R\vuplinkTotal\x12$\n" + + "\rdownlinkTotal\x18\x11 \x01(\x03R\rdownlinkTotal\x12\x12\n" + + "\x04rule\x18\x12 \x01(\tR\x04rule\x12\x1a\n" + + "\boutbound\x18\x13 \x01(\tR\boutbound\x12\"\n" + + "\foutboundType\x18\x14 \x01(\tR\foutboundType\x12\x1c\n" + + "\tchainList\x18\x15 \x03(\tR\tchainList\"(\n" + + "\x16CloseConnectionRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"K\n" + + "\x12DeprecatedWarnings\x125\n" + + "\bwarnings\x18\x01 \x03(\v2\x19.daemon.DeprecatedWarningR\bwarnings\"q\n" + + "\x11DeprecatedWarning\x12\x18\n" + + "\amessage\x18\x01 \x01(\tR\amessage\x12\x1c\n" + + "\timpending\x18\x02 \x01(\bR\timpending\x12$\n" + + "\rmigrationLink\x18\x03 \x01(\tR\rmigrationLink*U\n" + + "\bLogLevel\x12\t\n" + + "\x05PANIC\x10\x00\x12\t\n" + + "\x05FATAL\x10\x01\x12\t\n" + + "\x05ERROR\x10\x02\x12\b\n" + + "\x04WARN\x10\x03\x12\b\n" + + "\x04INFO\x10\x04\x12\t\n" + + "\x05DEBUG\x10\x05\x12\t\n" + + "\x05TRACE\x10\x06*3\n" + + "\x10ConnectionFilter\x12\a\n" + + "\x03ALL\x10\x00\x12\n" + + "\n" + + "\x06ACTIVE\x10\x01\x12\n" + + "\n" + + "\x06CLOSED\x10\x02*<\n" + + "\x10ConnectionSortBy\x12\b\n" + + "\x04DATE\x10\x00\x12\v\n" + + "\aTRAFFIC\x10\x01\x12\x11\n" + + "\rTOTAL_TRAFFIC\x10\x022\xf8\v\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" + + "\x16SubscribeServiceStatus\x12\x16.google.protobuf.Empty\x1a\x15.daemon.ServiceStatus\"\x000\x01\x127\n" + + "\fSubscribeLog\x12\x16.google.protobuf.Empty\x1a\v.daemon.Log\"\x000\x01\x12G\n" + + "\x12GetDefaultLogLevel\x12\x16.google.protobuf.Empty\x1a\x17.daemon.DefaultLogLevel\"\x00\x12E\n" + + "\x0fSubscribeStatus\x12\x1e.daemon.SubscribeStatusRequest\x1a\x0e.daemon.Status\"\x000\x01\x12=\n" + + "\x0fSubscribeGroups\x12\x16.google.protobuf.Empty\x1a\x0e.daemon.Groups\"\x000\x01\x12G\n" + + "\x12GetClashModeStatus\x12\x16.google.protobuf.Empty\x1a\x17.daemon.ClashModeStatus\"\x00\x12C\n" + + "\x12SubscribeClashMode\x12\x16.google.protobuf.Empty\x1a\x11.daemon.ClashMode\"\x000\x01\x12;\n" + + "\fSetClashMode\x12\x11.daemon.ClashMode\x1a\x16.google.protobuf.Empty\"\x00\x12;\n" + + "\aURLTest\x12\x16.daemon.URLTestRequest\x1a\x16.google.protobuf.Empty\"\x00\x12I\n" + + "\x0eSelectOutbound\x12\x1d.daemon.SelectOutboundRequest\x1a\x16.google.protobuf.Empty\"\x00\x12I\n" + + "\x0eSetGroupExpand\x12\x1d.daemon.SetGroupExpandRequest\x1a\x16.google.protobuf.Empty\"\x00\x12K\n" + + "\x14GetSystemProxyStatus\x12\x16.google.protobuf.Empty\x1a\x19.daemon.SystemProxyStatus\"\x00\x12W\n" + + "\x15SetSystemProxyEnabled\x12$.daemon.SetSystemProxyEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12T\n" + + "\x14SubscribeConnections\x12#.daemon.SubscribeConnectionsRequest\x1a\x13.daemon.Connections\"\x000\x01\x12K\n" + + "\x0fCloseConnection\x12\x1e.daemon.CloseConnectionRequest\x1a\x16.google.protobuf.Empty\"\x00\x12G\n" + + "\x13CloseAllConnections\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12M\n" + + "\x15GetDeprecatedWarnings\x12\x16.google.protobuf.Empty\x1a\x1a.daemon.DeprecatedWarnings\"\x00\x12J\n" + + "\x15SubscribeHelperEvents\x12\x16.google.protobuf.Empty\x1a\x15.daemon.HelperRequest\"\x000\x01\x12F\n" + + "\x12SendHelperResponse\x12\x16.daemon.HelperResponse\x1a\x16.google.protobuf.Empty\"\x00B%Z#github.com/sagernet/sing-box/daemonb\x06proto3" + +var ( + file_daemon_started_service_proto_rawDescOnce sync.Once + file_daemon_started_service_proto_rawDescData []byte +) + +func file_daemon_started_service_proto_rawDescGZIP() []byte { + file_daemon_started_service_proto_rawDescOnce.Do(func() { + file_daemon_started_service_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc))) + }) + return file_daemon_started_service_proto_rawDescData +} + +var ( + file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4) + file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 23) + file_daemon_started_service_proto_goTypes = []any{ + (LogLevel)(0), // 0: daemon.LogLevel + (ConnectionFilter)(0), // 1: daemon.ConnectionFilter + (ConnectionSortBy)(0), // 2: daemon.ConnectionSortBy + (ServiceStatus_Type)(0), // 3: daemon.ServiceStatus.Type + (*ServiceStatus)(nil), // 4: daemon.ServiceStatus + (*ReloadServiceRequest)(nil), // 5: daemon.ReloadServiceRequest + (*SubscribeStatusRequest)(nil), // 6: daemon.SubscribeStatusRequest + (*Log)(nil), // 7: daemon.Log + (*DefaultLogLevel)(nil), // 8: daemon.DefaultLogLevel + (*Status)(nil), // 9: daemon.Status + (*Groups)(nil), // 10: daemon.Groups + (*Group)(nil), // 11: daemon.Group + (*GroupItem)(nil), // 12: daemon.GroupItem + (*URLTestRequest)(nil), // 13: daemon.URLTestRequest + (*SelectOutboundRequest)(nil), // 14: daemon.SelectOutboundRequest + (*SetGroupExpandRequest)(nil), // 15: daemon.SetGroupExpandRequest + (*ClashMode)(nil), // 16: daemon.ClashMode + (*ClashModeStatus)(nil), // 17: daemon.ClashModeStatus + (*SystemProxyStatus)(nil), // 18: daemon.SystemProxyStatus + (*SetSystemProxyEnabledRequest)(nil), // 19: daemon.SetSystemProxyEnabledRequest + (*SubscribeConnectionsRequest)(nil), // 20: daemon.SubscribeConnectionsRequest + (*Connections)(nil), // 21: daemon.Connections + (*Connection)(nil), // 22: daemon.Connection + (*CloseConnectionRequest)(nil), // 23: daemon.CloseConnectionRequest + (*DeprecatedWarnings)(nil), // 24: daemon.DeprecatedWarnings + (*DeprecatedWarning)(nil), // 25: daemon.DeprecatedWarning + (*Log_Message)(nil), // 26: daemon.Log.Message + (*emptypb.Empty)(nil), // 27: google.protobuf.Empty + (*HelperResponse)(nil), // 28: daemon.HelperResponse + (*HelperRequest)(nil), // 29: daemon.HelperRequest + } +) + +var file_daemon_started_service_proto_depIdxs = []int32{ + 3, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type + 26, // 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 + 1, // 5: daemon.SubscribeConnectionsRequest.filter:type_name -> daemon.ConnectionFilter + 2, // 6: daemon.SubscribeConnectionsRequest.sortBy:type_name -> daemon.ConnectionSortBy + 22, // 7: daemon.Connections.connections:type_name -> daemon.Connection + 25, // 8: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning + 0, // 9: daemon.Log.Message.level:type_name -> daemon.LogLevel + 27, // 10: daemon.StartedService.StopService:input_type -> google.protobuf.Empty + 27, // 11: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty + 27, // 12: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty + 27, // 13: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty + 27, // 14: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty + 6, // 15: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest + 27, // 16: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty + 27, // 17: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty + 27, // 18: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty + 16, // 19: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode + 13, // 20: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest + 14, // 21: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest + 15, // 22: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest + 27, // 23: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty + 19, // 24: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest + 20, // 25: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest + 23, // 26: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest + 27, // 27: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty + 27, // 28: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty + 27, // 29: daemon.StartedService.SubscribeHelperEvents:input_type -> google.protobuf.Empty + 28, // 30: daemon.StartedService.SendHelperResponse:input_type -> daemon.HelperResponse + 27, // 31: daemon.StartedService.StopService:output_type -> google.protobuf.Empty + 27, // 32: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty + 4, // 33: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus + 7, // 34: daemon.StartedService.SubscribeLog:output_type -> daemon.Log + 8, // 35: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel + 9, // 36: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status + 10, // 37: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups + 17, // 38: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus + 16, // 39: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode + 27, // 40: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty + 27, // 41: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty + 27, // 42: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty + 27, // 43: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty + 18, // 44: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus + 27, // 45: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty + 21, // 46: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections + 27, // 47: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty + 27, // 48: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty + 24, // 49: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings + 29, // 50: daemon.StartedService.SubscribeHelperEvents:output_type -> daemon.HelperRequest + 27, // 51: daemon.StartedService.SendHelperResponse:output_type -> google.protobuf.Empty + 31, // [31:52] is the sub-list for method output_type + 10, // [10:31] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name +} + +func init() { file_daemon_started_service_proto_init() } +func file_daemon_started_service_proto_init() { + if File_daemon_started_service_proto != nil { + return + } + file_daemon_helper_proto_init() + 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: 4, + NumMessages: 23, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_daemon_started_service_proto_goTypes, + DependencyIndexes: file_daemon_started_service_proto_depIdxs, + EnumInfos: file_daemon_started_service_proto_enumTypes, + MessageInfos: file_daemon_started_service_proto_msgTypes, + }.Build() + File_daemon_started_service_proto = out.File + file_daemon_started_service_proto_goTypes = nil + file_daemon_started_service_proto_depIdxs = nil +} diff --git a/daemon/started_service.proto b/daemon/started_service.proto new file mode 100644 index 000000000..17e9f6832 --- /dev/null +++ b/daemon/started_service.proto @@ -0,0 +1,204 @@ +syntax = "proto3"; + +package daemon; +option go_package = "github.com/sagernet/sing-box/daemon"; + +import "google/protobuf/empty.proto"; +import "daemon/helper.proto"; + +service StartedService { + rpc StopService(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc ReloadService(google.protobuf.Empty) returns (google.protobuf.Empty); + + rpc SubscribeServiceStatus(google.protobuf.Empty) returns(stream ServiceStatus) {} + rpc SubscribeLog(google.protobuf.Empty) returns(stream Log) {} + rpc GetDefaultLogLevel(google.protobuf.Empty) returns(DefaultLogLevel) {} + rpc SubscribeStatus(SubscribeStatusRequest) returns(stream Status) {} + rpc SubscribeGroups(google.protobuf.Empty) returns(stream Groups) {} + + rpc GetClashModeStatus(google.protobuf.Empty) returns(ClashModeStatus) {} + rpc SubscribeClashMode(google.protobuf.Empty) returns(stream ClashMode) {} + rpc SetClashMode(ClashMode) returns(google.protobuf.Empty) {} + + rpc URLTest(URLTestRequest) returns(google.protobuf.Empty) {} + rpc SelectOutbound(SelectOutboundRequest) returns (google.protobuf.Empty) {} + rpc SetGroupExpand(SetGroupExpandRequest) returns (google.protobuf.Empty) {} + + rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {} + rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {} + + rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream Connections) {} + rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {} + rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {} + rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {} + + rpc SubscribeHelperEvents(google.protobuf.Empty) returns(stream HelperRequest) {} + rpc SendHelperResponse(HelperResponse) returns(google.protobuf.Empty) {} +} + +message ServiceStatus { + enum Type { + IDLE = 0; + STARTING = 1; + STARTED = 2; + STOPPING = 3; + FATAL = 4; + } + Type status = 1; + string errorMessage = 2; +} + +message ReloadServiceRequest { + string newProfileContent = 1; +} + +message SubscribeStatusRequest { + int64 interval = 1; +} + +enum LogLevel { + PANIC = 0; + FATAL = 1; + ERROR = 2; + WARN = 3; + INFO = 4; + DEBUG = 5; + TRACE = 6; +} + +message Log { + repeated Message messages = 1; + bool reset = 2; + message Message { + LogLevel level = 1; + string message = 2; + } +} + +message DefaultLogLevel { + LogLevel level = 1; +} + +message Status { + uint64 memory = 1; + int32 goroutines = 2; + int32 connectionsIn = 3; + int32 connectionsOut = 4; + bool trafficAvailable = 5; + int64 uplink = 6; + int64 downlink = 7; + int64 uplinkTotal = 8; + int64 downlinkTotal = 9; +} + +message Groups { + repeated Group group = 1; +} + +message Group { + string tag = 1; + string type = 2; + bool selectable = 3; + string selected = 4; + bool isExpand = 5; + repeated GroupItem items = 6; +} + +message GroupItem { + string tag = 1; + string type = 2; + int64 urlTestTime = 3; + int32 urlTestDelay = 4; +} + +message URLTestRequest { + string outboundTag = 1; +} + +message SelectOutboundRequest { + string groupTag = 1; + string outboundTag = 2; +} + +message SetGroupExpandRequest { + string groupTag = 1; + bool isExpand = 2; +} + +message ClashMode { + string mode = 3; +} + +message ClashModeStatus { + repeated string modeList = 1; + string currentMode = 2; +} + +message SystemProxyStatus { + bool available = 1; + bool enabled = 2; +} + +message SetSystemProxyEnabledRequest { + bool enabled = 1; +} + +message SubscribeConnectionsRequest { + int64 interval = 1; + ConnectionFilter filter = 2; + ConnectionSortBy sortBy = 3; +} + +enum ConnectionFilter { + ALL = 0; + ACTIVE = 1; + CLOSED = 2; +} + +enum ConnectionSortBy { + DATE = 0; + TRAFFIC = 1; + TOTAL_TRAFFIC = 2; +} + +message Connections { + repeated Connection connections = 1; +} + +message Connection { + string id = 1; + string inbound = 2; + string inboundType = 3; + int32 ipVersion = 4; + string network = 5; + string source = 6; + string destination = 7; + string domain = 8; + string protocol = 9; + string user = 10; + string fromOutbound = 11; + int64 createdAt = 12; + int64 closedAt = 13; + int64 uplink = 14; + int64 downlink = 15; + int64 uplinkTotal = 16; + int64 downlinkTotal = 17; + string rule = 18; + string outbound = 19; + string outboundType = 20; + repeated string chainList = 21; +} + +message CloseConnectionRequest { + string id = 1; +} + +message DeprecatedWarnings { + repeated DeprecatedWarning warnings = 1; +} + +message DeprecatedWarning { + string message = 1; + bool impending = 2; + string migrationLink = 3; +} \ No newline at end of file diff --git a/daemon/started_service_grpc.pb.go b/daemon/started_service_grpc.pb.go new file mode 100644 index 000000000..4807b25c4 --- /dev/null +++ b/daemon/started_service_grpc.pb.go @@ -0,0 +1,919 @@ +package daemon + +import ( + context "context" + + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +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_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_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections" + StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection" + StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections" + StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings" + StartedService_SubscribeHelperEvents_FullMethodName = "/daemon.StartedService/SubscribeHelperEvents" + StartedService_SendHelperResponse_FullMethodName = "/daemon.StartedService/SendHelperResponse" +) + +// StartedServiceClient is the client API for StartedService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type StartedServiceClient interface { + StopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + ReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error) + SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error) + GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error) + SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error) + SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error) + GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error) + SubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error) + SetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error) + URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error) + SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error) + CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error) + SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error) + SendHelperResponse(ctx context.Context, in *HelperResponse, opts ...grpc.CallOption) (*emptypb.Empty, error) +} + +type startedServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewStartedServiceClient(cc grpc.ClientConnInterface) StartedServiceClient { + return &startedServiceClient{cc} +} + +func (c *startedServiceClient) StopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_StopService_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) ReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_ReloadService_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[0], StartedService_SubscribeServiceStatus_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, ServiceStatus]{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_SubscribeServiceStatusClient = grpc.ServerStreamingClient[ServiceStatus] + +func (c *startedServiceClient) SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[1], StartedService_SubscribeLog_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, Log]{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_SubscribeLogClient = grpc.ServerStreamingClient[Log] + +func (c *startedServiceClient) GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DefaultLogLevel) + err := c.cc.Invoke(ctx, StartedService_GetDefaultLogLevel_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[2], StartedService_SubscribeStatus_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[SubscribeStatusRequest, Status]{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_SubscribeStatusClient = grpc.ServerStreamingClient[Status] + +func (c *startedServiceClient) SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[3], StartedService_SubscribeGroups_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, Groups]{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_SubscribeGroupsClient = grpc.ServerStreamingClient[Groups] + +func (c *startedServiceClient) GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ClashModeStatus) + err := c.cc.Invoke(ctx, StartedService_GetClashModeStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[4], StartedService_SubscribeClashMode_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, ClashMode]{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_SubscribeClashModeClient = grpc.ServerStreamingClient[ClashMode] + +func (c *startedServiceClient) SetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SetClashMode_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_URLTest_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SelectOutbound_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SetGroupExpand_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SystemProxyStatus) + err := c.cc.Invoke(ctx, StartedService_GetSystemProxyStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SetSystemProxyEnabled_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[SubscribeConnectionsRequest, Connections]{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_SubscribeConnectionsClient = grpc.ServerStreamingClient[Connections] + +func (c *startedServiceClient) CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_CloseConnection_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_CloseAllConnections_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeprecatedWarnings) + err := c.cc.Invoke(ctx, StartedService_GetDeprecatedWarnings_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[6], StartedService_SubscribeHelperEvents_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, HelperRequest]{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_SubscribeHelperEventsClient = grpc.ServerStreamingClient[HelperRequest] + +func (c *startedServiceClient) SendHelperResponse(ctx context.Context, in *HelperResponse, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SendHelperResponse_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// StartedServiceServer is the server API for StartedService service. +// All implementations must embed UnimplementedStartedServiceServer +// for forward compatibility. +type StartedServiceServer interface { + StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error + SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error + GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) + SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error + SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error + GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) + SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error + SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error) + URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error) + SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error) + SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) + GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) + SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) + SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error + CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) + CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) + SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error + SendHelperResponse(context.Context, *HelperResponse) (*emptypb.Empty, error) + mustEmbedUnimplementedStartedServiceServer() +} + +// UnimplementedStartedServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedStartedServiceServer struct{} + +func (UnimplementedStartedServiceServer) StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method StopService not implemented") +} + +func (UnimplementedStartedServiceServer) ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ReloadService not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeServiceStatus not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeLog not implemented") +} + +func (UnimplementedStartedServiceServer) GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetDefaultLogLevel not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeStatus not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeGroups not implemented") +} + +func (UnimplementedStartedServiceServer) GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetClashModeStatus not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeClashMode not implemented") +} + +func (UnimplementedStartedServiceServer) SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetClashMode not implemented") +} + +func (UnimplementedStartedServiceServer) URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method URLTest not implemented") +} + +func (UnimplementedStartedServiceServer) SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SelectOutbound not implemented") +} + +func (UnimplementedStartedServiceServer) SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetGroupExpand not implemented") +} + +func (UnimplementedStartedServiceServer) GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetSystemProxyStatus not implemented") +} + +func (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetSystemProxyEnabled not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeConnections not implemented") +} + +func (UnimplementedStartedServiceServer) CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CloseConnection not implemented") +} + +func (UnimplementedStartedServiceServer) CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CloseAllConnections not implemented") +} + +func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetDeprecatedWarnings not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeHelperEvents not implemented") +} + +func (UnimplementedStartedServiceServer) SendHelperResponse(context.Context, *HelperResponse) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendHelperResponse not implemented") +} +func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {} +func (UnimplementedStartedServiceServer) testEmbeddedByValue() {} + +// UnsafeStartedServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to StartedServiceServer will +// result in compilation errors. +type UnsafeStartedServiceServer interface { + mustEmbedUnimplementedStartedServiceServer() +} + +func RegisterStartedServiceServer(s grpc.ServiceRegistrar, srv StartedServiceServer) { + // If the following call pancis, it indicates UnimplementedStartedServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&StartedService_ServiceDesc, srv) +} + +func _StartedService_StopService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).StopService(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_StopService_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).StopService(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_ReloadService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).ReloadService(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_ReloadService_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).ReloadService(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeServiceStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeServiceStatus(m, &grpc.GenericServerStream[emptypb.Empty, ServiceStatus]{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_SubscribeServiceStatusServer = grpc.ServerStreamingServer[ServiceStatus] + +func _StartedService_SubscribeLog_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeLog(m, &grpc.GenericServerStream[emptypb.Empty, Log]{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_SubscribeLogServer = grpc.ServerStreamingServer[Log] + +func _StartedService_GetDefaultLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetDefaultLogLevel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetDefaultLogLevel_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetDefaultLogLevel(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SubscribeStatusRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeStatus(m, &grpc.GenericServerStream[SubscribeStatusRequest, Status]{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_SubscribeStatusServer = grpc.ServerStreamingServer[Status] + +func _StartedService_SubscribeGroups_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeGroups(m, &grpc.GenericServerStream[emptypb.Empty, Groups]{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_SubscribeGroupsServer = grpc.ServerStreamingServer[Groups] + +func _StartedService_GetClashModeStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetClashModeStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetClashModeStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetClashModeStatus(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeClashMode_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeClashMode(m, &grpc.GenericServerStream[emptypb.Empty, ClashMode]{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_SubscribeClashModeServer = grpc.ServerStreamingServer[ClashMode] + +func _StartedService_SetClashMode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClashMode) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SetClashMode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SetClashMode_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SetClashMode(ctx, req.(*ClashMode)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_URLTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(URLTestRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).URLTest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_URLTest_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).URLTest(ctx, req.(*URLTestRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SelectOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SelectOutboundRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SelectOutbound(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SelectOutbound_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SelectOutbound(ctx, req.(*SelectOutboundRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SetGroupExpand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetGroupExpandRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SetGroupExpand(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SetGroupExpand_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SetGroupExpand(ctx, req.(*SetGroupExpandRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_GetSystemProxyStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetSystemProxyStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetSystemProxyStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetSystemProxyStatus(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SetSystemProxyEnabled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetSystemProxyEnabledRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SetSystemProxyEnabled_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, req.(*SetSystemProxyEnabledRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SubscribeConnectionsRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeConnections(m, &grpc.GenericServerStream[SubscribeConnectionsRequest, Connections]{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_SubscribeConnectionsServer = grpc.ServerStreamingServer[Connections] + +func _StartedService_CloseConnection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CloseConnectionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).CloseConnection(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_CloseConnection_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).CloseConnection(ctx, req.(*CloseConnectionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_CloseAllConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).CloseAllConnections(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_CloseAllConnections_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).CloseAllConnections(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_GetDeprecatedWarnings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetDeprecatedWarnings_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeHelperEvents_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeHelperEvents(m, &grpc.GenericServerStream[emptypb.Empty, HelperRequest]{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_SubscribeHelperEventsServer = grpc.ServerStreamingServer[HelperRequest] + +func _StartedService_SendHelperResponse_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelperResponse) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SendHelperResponse(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SendHelperResponse_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SendHelperResponse(ctx, req.(*HelperResponse)) + } + return interceptor(ctx, in, info, handler) +} + +// 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) +var StartedService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "daemon.StartedService", + HandlerType: (*StartedServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "StopService", + Handler: _StartedService_StopService_Handler, + }, + { + MethodName: "ReloadService", + Handler: _StartedService_ReloadService_Handler, + }, + { + MethodName: "GetDefaultLogLevel", + Handler: _StartedService_GetDefaultLogLevel_Handler, + }, + { + MethodName: "GetClashModeStatus", + Handler: _StartedService_GetClashModeStatus_Handler, + }, + { + MethodName: "SetClashMode", + Handler: _StartedService_SetClashMode_Handler, + }, + { + MethodName: "URLTest", + Handler: _StartedService_URLTest_Handler, + }, + { + MethodName: "SelectOutbound", + Handler: _StartedService_SelectOutbound_Handler, + }, + { + MethodName: "SetGroupExpand", + Handler: _StartedService_SetGroupExpand_Handler, + }, + { + MethodName: "GetSystemProxyStatus", + Handler: _StartedService_GetSystemProxyStatus_Handler, + }, + { + MethodName: "SetSystemProxyEnabled", + Handler: _StartedService_SetSystemProxyEnabled_Handler, + }, + { + MethodName: "CloseConnection", + Handler: _StartedService_CloseConnection_Handler, + }, + { + MethodName: "CloseAllConnections", + Handler: _StartedService_CloseAllConnections_Handler, + }, + { + MethodName: "GetDeprecatedWarnings", + Handler: _StartedService_GetDeprecatedWarnings_Handler, + }, + { + MethodName: "SendHelperResponse", + Handler: _StartedService_SendHelperResponse_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "SubscribeServiceStatus", + Handler: _StartedService_SubscribeServiceStatus_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeLog", + Handler: _StartedService_SubscribeLog_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeStatus", + Handler: _StartedService_SubscribeStatus_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeGroups", + Handler: _StartedService_SubscribeGroups_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeClashMode", + Handler: _StartedService_SubscribeClashMode_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeConnections", + Handler: _StartedService_SubscribeConnections_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeHelperEvents", + Handler: _StartedService_SubscribeHelperEvents_Handler, + ServerStreams: true, + }, + }, + Metadata: "daemon/started_service.proto", +} diff --git a/dns/router.go b/dns/router.go index 8de1f6a93..1038fdf08 100644 --- a/dns/router.go +++ b/dns/router.go @@ -10,7 +10,6 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" R "github.com/sagernet/sing-box/route/rule" @@ -38,7 +37,7 @@ type Router struct { rules []adapter.DNSRule defaultDomainStrategy C.DomainStrategy dnsReverseMapping freelru.Cache[netip.Addr, string] - platformInterface platform.Interface + platformInterface adapter.PlatformInterface } func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router { diff --git a/docs/configuration/dns/server/index.zh.md b/docs/configuration/dns/server/index.zh.md index 64bdd88a6..d6deef5a3 100644 --- a/docs/configuration/dns/server/index.zh.md +++ b/docs/configuration/dns/server/index.zh.md @@ -1,48 +1,48 @@ ---- -icon: material/alert-decagram ---- - -!!! quote "sing-box 1.12.0 中的更改" - - :material-plus: [type](#type) - -# DNS Server - -### 结构 - -```json -{ - "dns": { - "servers": [ - { - "type": "", - "tag": "" - } - ] - } -} -``` - -#### type - -DNS 服务器的类型。 - -| 类型 | 格式 | -|-----------------|---------------------------| -| empty (default) | [Legacy](./legacy/) | -| `local` | [Local](./local/) | -| `hosts` | [Hosts](./hosts/) | -| `tcp` | [TCP](./tcp/) | -| `udp` | [UDP](./udp/) | -| `tls` | [TLS](./tls/) | -| `quic` | [QUIC](./quic/) | -| `https` | [HTTPS](./https/) | -| `h3` | [HTTP/3](./http3/) | -| `dhcp` | [DHCP](./dhcp/) | -| `fakeip` | [Fake IP](./fakeip/) | -| `tailscale` | [Tailscale](./tailscale/) | -| `resolved` | [Resolved](./resolved/) | - -#### tag - -DNS 服务器的标签。 +--- +icon: material/alert-decagram +--- + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [type](#type) + +# DNS Server + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "", + "tag": "" + } + ] + } +} +``` + +#### type + +DNS 服务器的类型。 + +| 类型 | 格式 | +|-----------------|---------------------------| +| empty (default) | [Legacy](./legacy/) | +| `local` | [Local](./local/) | +| `hosts` | [Hosts](./hosts/) | +| `tcp` | [TCP](./tcp/) | +| `udp` | [UDP](./udp/) | +| `tls` | [TLS](./tls/) | +| `quic` | [QUIC](./quic/) | +| `https` | [HTTPS](./https/) | +| `h3` | [HTTP/3](./http3/) | +| `dhcp` | [DHCP](./dhcp/) | +| `fakeip` | [Fake IP](./fakeip/) | +| `tailscale` | [Tailscale](./tailscale/) | +| `resolved` | [Resolved](./resolved/) | + +#### tag + +DNS 服务器的标签。 diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 3a2d48274..e71031dcb 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -24,6 +24,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/observable" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" "github.com/sagernet/ws" @@ -53,7 +54,7 @@ type Server struct { mode string modeList []string - modeUpdateHook chan<- struct{} + modeUpdateHook *observable.Subscriber[struct{}] externalController bool externalUI string @@ -203,7 +204,7 @@ func (s *Server) ModeList() []string { return s.modeList } -func (s *Server) SetModeUpdateHook(hook chan<- struct{}) { +func (s *Server) SetModeUpdateHook(hook *observable.Subscriber[struct{}]) { s.modeUpdateHook = hook } @@ -221,10 +222,7 @@ func (s *Server) SetMode(newMode string) { } s.mode = newMode if s.modeUpdateHook != nil { - select { - case s.modeUpdateHook <- struct{}{}: - default: - } + s.modeUpdateHook.Emit(struct{}{}) } s.dnsRouter.ClearCache() cacheFile := service.FromContext[adapter.CacheFile](s.ctx) diff --git a/experimental/clashapi/trafficontrol/tracker.go b/experimental/clashapi/trafficontrol/tracker.go index 48d54b253..9cddc8cc3 100644 --- a/experimental/clashapi/trafficontrol/tracker.go +++ b/experimental/clashapi/trafficontrol/tracker.go @@ -45,15 +45,15 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) { if t.Metadata.ProcessInfo != nil { if t.Metadata.ProcessInfo.ProcessPath != "" { processPath = t.Metadata.ProcessInfo.ProcessPath - } else if t.Metadata.ProcessInfo.PackageName != "" { - processPath = t.Metadata.ProcessInfo.PackageName + } else if t.Metadata.ProcessInfo.AndroidPackageName != "" { + processPath = t.Metadata.ProcessInfo.AndroidPackageName } if processPath == "" { if t.Metadata.ProcessInfo.UserId != -1 { processPath = F.ToString(t.Metadata.ProcessInfo.UserId) } - } else if t.Metadata.ProcessInfo.User != "" { - processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.User, ")") + } else if t.Metadata.ProcessInfo.UserName != "" { + processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.UserName, ")") } else if t.Metadata.ProcessInfo.UserId != -1 { processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.UserId, ")") } diff --git a/experimental/libbox/command.go b/experimental/libbox/command.go index 3eb57dd49..e3af6a196 100644 --- a/experimental/libbox/command.go +++ b/experimental/libbox/command.go @@ -3,18 +3,7 @@ package libbox const ( CommandLog int32 = iota CommandStatus - CommandServiceReload - CommandServiceClose - CommandCloseConnections CommandGroup - CommandSelectOutbound - CommandURLTest - CommandGroupExpand CommandClashMode - CommandSetClashMode - CommandGetSystemProxyStatus - CommandSetSystemProxyEnabled CommandConnections - CommandCloseConnection - CommandGetDeprecatedNotes ) diff --git a/experimental/libbox/command_clash_mode.go b/experimental/libbox/command_clash_mode.go deleted file mode 100644 index af69047fd..000000000 --- a/experimental/libbox/command_clash_mode.go +++ /dev/null @@ -1,124 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "io" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/experimental/clashapi" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" -) - -func (c *CommandClient) SetClashMode(newMode string) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandSetClashMode)) - if err != nil { - return err - } - err = varbin.Write(conn, binary.BigEndian, newMode) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleSetClashMode(conn net.Conn) error { - newMode, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - return err - } - service := s.service - if service == nil { - return writeError(conn, E.New("service not ready")) - } - service.clashServer.(*clashapi.Server).SetMode(newMode) - return writeError(conn, nil) -} - -func (c *CommandClient) handleModeConn(conn net.Conn) { - defer conn.Close() - - for { - newMode, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - c.handler.UpdateClashMode(newMode) - } -} - -func (s *CommandServer) handleModeConn(conn net.Conn) error { - ctx := connKeepAlive(conn) - for s.service == nil { - select { - case <-time.After(time.Second): - continue - case <-ctx.Done(): - return ctx.Err() - } - } - err := writeClashModeList(conn, s.service.clashServer) - if err != nil { - return err - } - for { - select { - case <-s.modeUpdate: - err = varbin.Write(conn, binary.BigEndian, s.service.clashServer.Mode()) - if err != nil { - return err - } - case <-ctx.Done(): - return ctx.Err() - } - } -} - -func readClashModeList(reader io.Reader) (modeList []string, currentMode string, err error) { - var modeListLength uint16 - err = binary.Read(reader, binary.BigEndian, &modeListLength) - if err != nil { - return - } - if modeListLength == 0 { - return - } - modeList = make([]string, modeListLength) - for i := 0; i < int(modeListLength); i++ { - modeList[i], err = varbin.ReadValue[string](reader, binary.BigEndian) - if err != nil { - return - } - } - currentMode, err = varbin.ReadValue[string](reader, binary.BigEndian) - return -} - -func writeClashModeList(writer io.Writer, clashServer adapter.ClashServer) error { - modeList := clashServer.ModeList() - err := binary.Write(writer, binary.BigEndian, uint16(len(modeList))) - if err != nil { - return err - } - if len(modeList) > 0 { - for _, mode := range modeList { - err = varbin.Write(writer, binary.BigEndian, mode) - if err != nil { - return err - } - } - err = varbin.Write(writer, binary.BigEndian, clashServer.Mode()) - if err != nil { - return err - } - } - return nil -} diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index fff2dbe2e..f5f6c6e24 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -1,32 +1,49 @@ package libbox import ( - "encoding/binary" + "context" "net" "os" "path/filepath" + "strconv" + "sync" "time" + "github.com/sagernet/sing-box/daemon" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/types/known/emptypb" ) type CommandClient struct { - handler CommandClientHandler - conn net.Conn - options CommandClientOptions + handler CommandClientHandler + grpcConn *grpc.ClientConn + grpcClient daemon.StartedServiceClient + options CommandClientOptions + ctx context.Context + cancel context.CancelFunc + clientMutex sync.RWMutex } type CommandClientOptions struct { - Command int32 + commands []int32 StatusInterval int64 } +func (o *CommandClientOptions) AddCommand(command int32) { + o.commands = append(o.commands, command) +} + type CommandClientHandler interface { Connected() Disconnected(message string) + SetDefaultLogLevel(level int32) ClearLogs() - WriteLogs(messageList StringIterator) + WriteLogs(messageList LogIterator) WriteStatus(message *StatusMessage) WriteGroups(message OutboundGroupIterator) InitializeClashMode(modeList StringIterator, currentMode string) @@ -34,6 +51,17 @@ type CommandClientHandler interface { WriteConnections(message *Connections) } +type LogEntry struct { + Level int32 + Message string +} + +type LogIterator interface { + Len() int32 + HasNext() bool + Next() *LogEntry +} + func NewStandaloneCommandClient() *CommandClient { return new(CommandClient) } @@ -45,24 +73,38 @@ func NewCommandClient(handler CommandClientHandler, options *CommandClientOption } } -func (c *CommandClient) directConnect() (net.Conn, error) { - if !sTVOS { - return net.DialUnix("unix", nil, &net.UnixAddr{ - Name: filepath.Join(sBasePath, "command.sock"), - Net: "unix", - }) - } else { - return net.Dial("tcp", "127.0.0.1:8964") +func unaryClientAuthInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + if sCommandServerSecret != "" { + ctx = metadata.AppendToOutgoingContext(ctx, "x-command-secret", sCommandServerSecret) } + return invoker(ctx, method, req, reply, cc, opts...) } -func (c *CommandClient) directConnectWithRetry() (net.Conn, error) { +func streamClientAuthInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + if sCommandServerSecret != "" { + ctx = metadata.AppendToOutgoingContext(ctx, "x-command-secret", sCommandServerSecret) + } + return streamer(ctx, desc, cc, method, opts...) +} + +func (c *CommandClient) grpcDial() (*grpc.ClientConn, error) { + var target string + if sCommandServerListenPort == 0 { + target = "unix://" + filepath.Join(sBasePath, "command.sock") + } else { + target = net.JoinHostPort("127.0.0.1", strconv.Itoa(int(sCommandServerListenPort))) + } var ( - conn net.Conn + conn *grpc.ClientConn err error ) + clientOptions := []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithUnaryInterceptor(unaryClientAuthInterceptor), + grpc.WithStreamInterceptor(streamClientAuthInterceptor), + } for i := 0; i < 10; i++ { - conn, err = c.directConnect() + conn, err = grpc.NewClient(target, clientOptions...) if err == nil { return conn, nil } @@ -72,79 +114,357 @@ func (c *CommandClient) directConnectWithRetry() (net.Conn, error) { } func (c *CommandClient) Connect() error { - common.Close(c.conn) - conn, err := c.directConnectWithRetry() + c.clientMutex.Lock() + common.Close(common.PtrOrNil(c.grpcConn)) + + conn, err := c.grpcDial() if err != nil { + c.clientMutex.Unlock() return err } - c.conn = conn - err = binary.Write(conn, binary.BigEndian, uint8(c.options.Command)) - if err != nil { - return err - } - switch c.options.Command { - case CommandLog: - err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) - if err != nil { - return E.Cause(err, "write interval") + c.grpcConn = conn + c.grpcClient = daemon.NewStartedServiceClient(conn) + c.ctx, c.cancel = context.WithCancel(context.Background()) + c.clientMutex.Unlock() + + c.handler.Connected() + for _, command := range c.options.commands { + switch command { + case CommandLog: + go c.handleLogStream() + case CommandStatus: + go c.handleStatusStream() + case CommandGroup: + go c.handleGroupStream() + case CommandClashMode: + go c.handleClashModeStream() + case CommandConnections: + go c.handleConnectionsStream() + default: + return E.New("unknown command: ", command) } - c.handler.Connected() - go c.handleLogConn(conn) - case CommandStatus: - err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) - if err != nil { - return E.Cause(err, "write interval") - } - c.handler.Connected() - go c.handleStatusConn(conn) - case CommandGroup: - err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) - if err != nil { - return E.Cause(err, "write interval") - } - c.handler.Connected() - go c.handleGroupConn(conn) - case CommandClashMode: - var ( - modeList []string - currentMode string - ) - modeList, currentMode, err = readClashModeList(conn) - if err != nil { - return err - } - if sFixAndroidStack { - go func() { - c.handler.Connected() - c.handler.InitializeClashMode(newIterator(modeList), currentMode) - if len(modeList) == 0 { - conn.Close() - c.handler.Disconnected(os.ErrInvalid.Error()) - } - }() - } else { - c.handler.Connected() - c.handler.InitializeClashMode(newIterator(modeList), currentMode) - if len(modeList) == 0 { - conn.Close() - c.handler.Disconnected(os.ErrInvalid.Error()) - } - } - if len(modeList) == 0 { - return nil - } - go c.handleModeConn(conn) - case CommandConnections: - err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) - if err != nil { - return E.Cause(err, "write interval") - } - c.handler.Connected() - go c.handleConnectionsConn(conn) } return nil } func (c *CommandClient) Disconnect() error { - return common.Close(c.conn) + c.clientMutex.Lock() + defer c.clientMutex.Unlock() + if c.cancel != nil { + c.cancel() + } + return common.Close(common.PtrOrNil(c.grpcConn)) +} + +func (c *CommandClient) getClientForCall() (daemon.StartedServiceClient, error) { + c.clientMutex.RLock() + if c.grpcClient != nil { + defer c.clientMutex.RUnlock() + return c.grpcClient, nil + } + c.clientMutex.RUnlock() + + c.clientMutex.Lock() + defer c.clientMutex.Unlock() + + if c.grpcClient != nil { + return c.grpcClient, nil + } + + conn, err := c.grpcDial() + if err != nil { + return nil, err + } + c.grpcConn = conn + c.grpcClient = daemon.NewStartedServiceClient(conn) + if c.ctx == nil { + c.ctx, c.cancel = context.WithCancel(context.Background()) + } + return c.grpcClient, nil +} + +func (c *CommandClient) getStreamContext() (daemon.StartedServiceClient, context.Context) { + c.clientMutex.RLock() + defer c.clientMutex.RUnlock() + return c.grpcClient, c.ctx +} + +func (c *CommandClient) handleLogStream() { + client, ctx := c.getStreamContext() + stream, err := client.SubscribeLog(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + defaultLogLevel, err := client.GetDefaultLogLevel(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + c.handler.SetDefaultLogLevel(int32(defaultLogLevel.Level)) + for { + logMessage, err := stream.Recv() + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + if logMessage.Reset_ { + c.handler.ClearLogs() + } + var messages []*LogEntry + for _, msg := range logMessage.Messages { + messages = append(messages, &LogEntry{ + Level: int32(msg.Level), + Message: msg.Message, + }) + } + c.handler.WriteLogs(newIterator(messages)) + } +} + +func (c *CommandClient) handleStatusStream() { + client, ctx := c.getStreamContext() + interval := c.options.StatusInterval + + stream, err := client.SubscribeStatus(ctx, &daemon.SubscribeStatusRequest{ + Interval: interval, + }) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + + for { + status, err := stream.Recv() + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + c.handler.WriteStatus(StatusMessageFromGRPC(status)) + } +} + +func (c *CommandClient) handleGroupStream() { + client, ctx := c.getStreamContext() + + stream, err := client.SubscribeGroups(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + + for { + groups, err := stream.Recv() + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + c.handler.WriteGroups(OutboundGroupIteratorFromGRPC(groups)) + } +} + +func (c *CommandClient) handleClashModeStream() { + client, ctx := c.getStreamContext() + + modeStatus, err := client.GetClashModeStatus(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + + if sFixAndroidStack { + go func() { + c.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode) + if len(modeStatus.ModeList) == 0 { + c.handler.Disconnected(os.ErrInvalid.Error()) + } + }() + } else { + c.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode) + if len(modeStatus.ModeList) == 0 { + c.handler.Disconnected(os.ErrInvalid.Error()) + return + } + } + + if len(modeStatus.ModeList) == 0 { + return + } + + stream, err := client.SubscribeClashMode(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + + for { + mode, err := stream.Recv() + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + c.handler.UpdateClashMode(mode.Mode) + } +} + +func (c *CommandClient) handleConnectionsStream() { + client, ctx := c.getStreamContext() + interval := c.options.StatusInterval + + stream, err := client.SubscribeConnections(ctx, &daemon.SubscribeConnectionsRequest{ + Interval: interval, + }) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + + var connections Connections + for { + conns, err := stream.Recv() + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + connections.input = ConnectionsFromGRPC(conns) + c.handler.WriteConnections(&connections) + } +} + +func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.SelectOutbound(context.Background(), &daemon.SelectOutboundRequest{ + GroupTag: groupTag, + OutboundTag: outboundTag, + }) + return err +} + +func (c *CommandClient) URLTest(groupTag string) error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.URLTest(context.Background(), &daemon.URLTestRequest{ + OutboundTag: groupTag, + }) + return err +} + +func (c *CommandClient) SetClashMode(newMode string) error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.SetClashMode(context.Background(), &daemon.ClashMode{ + Mode: newMode, + }) + return err +} + +func (c *CommandClient) CloseConnection(connId string) error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.CloseConnection(context.Background(), &daemon.CloseConnectionRequest{ + Id: connId, + }) + return err +} + +func (c *CommandClient) CloseConnections() error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.CloseAllConnections(context.Background(), &emptypb.Empty{}) + return err +} + +func (c *CommandClient) ServiceReload() error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.ReloadService(context.Background(), &emptypb.Empty{}) + return err +} + +func (c *CommandClient) ServiceClose() error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.StopService(context.Background(), &emptypb.Empty{}) + return err +} + +func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) { + client, err := c.getClientForCall() + if err != nil { + return nil, err + } + + status, err := client.GetSystemProxyStatus(context.Background(), &emptypb.Empty{}) + if err != nil { + return nil, err + } + return SystemProxyStatusFromGRPC(status), nil +} + +func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.SetSystemProxyEnabled(context.Background(), &daemon.SetSystemProxyEnabledRequest{ + Enabled: isEnabled, + }) + return err +} + +func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) { + client, err := c.getClientForCall() + if err != nil { + return nil, err + } + + warnings, err := client.GetDeprecatedWarnings(context.Background(), &emptypb.Empty{}) + if err != nil { + return nil, err + } + + var notes []*DeprecatedNote + for _, warning := range warnings.Warnings { + notes = append(notes, &DeprecatedNote{ + Description: warning.Message, + MigrationLink: warning.MigrationLink, + }) + } + return newIterator(notes), nil +} + +func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.SetGroupExpand(context.Background(), &daemon.SetGroupExpandRequest{ + GroupTag: groupTag, + IsExpand: isExpand, + }) + return err } diff --git a/experimental/libbox/command_close_connection.go b/experimental/libbox/command_close_connection.go deleted file mode 100644 index 46f7023fa..000000000 --- a/experimental/libbox/command_close_connection.go +++ /dev/null @@ -1,54 +0,0 @@ -package libbox - -import ( - "bufio" - "net" - - "github.com/sagernet/sing-box/experimental/clashapi" - "github.com/sagernet/sing/common/binary" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" - - "github.com/gofrs/uuid/v5" -) - -func (c *CommandClient) CloseConnection(connId string) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandCloseConnection)) - if err != nil { - return err - } - writer := bufio.NewWriter(conn) - err = varbin.Write(writer, binary.BigEndian, connId) - if err != nil { - return err - } - err = writer.Flush() - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleCloseConnection(conn net.Conn) error { - reader := bufio.NewReader(conn) - var connId string - err := varbin.Read(reader, binary.BigEndian, &connId) - if err != nil { - return E.Cause(err, "read connection id") - } - service := s.service - if service == nil { - return writeError(conn, E.New("service not ready")) - } - targetConn := service.clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(connId)) - if targetConn == nil { - return writeError(conn, E.New("connection already closed")) - } - targetConn.Close() - return writeError(conn, nil) -} diff --git a/experimental/libbox/command_connections.go b/experimental/libbox/command_connections.go deleted file mode 100644 index 39d9303cb..000000000 --- a/experimental/libbox/command_connections.go +++ /dev/null @@ -1,269 +0,0 @@ -package libbox - -import ( - "bufio" - "net" - "slices" - "strings" - "time" - - "github.com/sagernet/sing-box/experimental/clashapi" - "github.com/sagernet/sing-box/experimental/clashapi/trafficontrol" - "github.com/sagernet/sing/common/binary" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/varbin" - - "github.com/gofrs/uuid/v5" -) - -func (c *CommandClient) handleConnectionsConn(conn net.Conn) { - defer conn.Close() - reader := bufio.NewReader(conn) - var ( - rawConnections []Connection - connections Connections - ) - for { - rawConnections = nil - err := varbin.Read(reader, binary.BigEndian, &rawConnections) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - connections.input = rawConnections - c.handler.WriteConnections(&connections) - } -} - -func (s *CommandServer) handleConnectionsConn(conn net.Conn) error { - var interval int64 - err := binary.Read(conn, binary.BigEndian, &interval) - if err != nil { - return E.Cause(err, "read interval") - } - ticker := time.NewTicker(time.Duration(interval)) - defer ticker.Stop() - ctx := connKeepAlive(conn) - var trafficManager *trafficontrol.Manager - for { - service := s.service - if service != nil { - trafficManager = service.clashServer.(*clashapi.Server).TrafficManager() - break - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - } - var ( - connections = make(map[uuid.UUID]*Connection) - outConnections []Connection - ) - writer := bufio.NewWriter(conn) - for { - outConnections = outConnections[:0] - for _, connection := range trafficManager.Connections() { - outConnections = append(outConnections, newConnection(connections, connection, false)) - } - for _, connection := range trafficManager.ClosedConnections() { - outConnections = append(outConnections, newConnection(connections, connection, true)) - } - err = varbin.Write(writer, binary.BigEndian, outConnections) - if err != nil { - return err - } - err = writer.Flush() - if err != nil { - return err - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - } -} - -const ( - ConnectionStateAll = iota - ConnectionStateActive - ConnectionStateClosed -) - -type Connections struct { - input []Connection - filtered []Connection -} - -func (c *Connections) FilterState(state int32) { - c.filtered = c.filtered[:0] - switch state { - case ConnectionStateAll: - c.filtered = append(c.filtered, c.input...) - case ConnectionStateActive: - for _, connection := range c.input { - if connection.ClosedAt == 0 { - c.filtered = append(c.filtered, connection) - } - } - case ConnectionStateClosed: - for _, connection := range c.input { - if connection.ClosedAt != 0 { - c.filtered = append(c.filtered, connection) - } - } - } -} - -func (c *Connections) SortByDate() { - slices.SortStableFunc(c.filtered, func(x, y Connection) int { - if x.CreatedAt < y.CreatedAt { - return 1 - } else if x.CreatedAt > y.CreatedAt { - return -1 - } else { - return strings.Compare(y.ID, x.ID) - } - }) -} - -func (c *Connections) SortByTraffic() { - slices.SortStableFunc(c.filtered, func(x, y Connection) int { - xTraffic := x.Uplink + x.Downlink - yTraffic := y.Uplink + y.Downlink - if xTraffic < yTraffic { - return 1 - } else if xTraffic > yTraffic { - return -1 - } else { - return strings.Compare(y.ID, x.ID) - } - }) -} - -func (c *Connections) SortByTrafficTotal() { - slices.SortStableFunc(c.filtered, func(x, y Connection) int { - xTraffic := x.UplinkTotal + x.DownlinkTotal - yTraffic := y.UplinkTotal + y.DownlinkTotal - if xTraffic < yTraffic { - return 1 - } else if xTraffic > yTraffic { - return -1 - } else { - return strings.Compare(y.ID, x.ID) - } - }) -} - -func (c *Connections) Iterator() ConnectionIterator { - return newPtrIterator(c.filtered) -} - -type Connection struct { - ID string - Inbound string - InboundType string - IPVersion int32 - Network string - Source string - Destination string - Domain string - Protocol string - User string - FromOutbound string - CreatedAt int64 - ClosedAt int64 - Uplink int64 - Downlink int64 - UplinkTotal int64 - DownlinkTotal int64 - Rule string - Outbound string - OutboundType string - ChainList []string -} - -func (c *Connection) Chain() StringIterator { - return newIterator(c.ChainList) -} - -func (c *Connection) DisplayDestination() string { - destination := M.ParseSocksaddr(c.Destination) - if destination.IsIP() && c.Domain != "" { - destination = M.Socksaddr{ - Fqdn: c.Domain, - Port: destination.Port, - } - return destination.String() - } - return c.Destination -} - -type ConnectionIterator interface { - Next() *Connection - HasNext() bool -} - -func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol.TrackerMetadata, isClosed bool) Connection { - if oldConnection, loaded := connections[metadata.ID]; loaded { - if isClosed { - if oldConnection.ClosedAt == 0 { - oldConnection.Uplink = 0 - oldConnection.Downlink = 0 - oldConnection.ClosedAt = metadata.ClosedAt.UnixMilli() - } - return *oldConnection - } - lastUplink := oldConnection.UplinkTotal - lastDownlink := oldConnection.DownlinkTotal - uplinkTotal := metadata.Upload.Load() - downlinkTotal := metadata.Download.Load() - oldConnection.Uplink = uplinkTotal - lastUplink - oldConnection.Downlink = downlinkTotal - lastDownlink - oldConnection.UplinkTotal = uplinkTotal - oldConnection.DownlinkTotal = downlinkTotal - return *oldConnection - } - var rule string - if metadata.Rule != nil { - rule = metadata.Rule.String() - } - uplinkTotal := metadata.Upload.Load() - downlinkTotal := metadata.Download.Load() - uplink := uplinkTotal - downlink := downlinkTotal - var closedAt int64 - if !metadata.ClosedAt.IsZero() { - closedAt = metadata.ClosedAt.UnixMilli() - uplink = 0 - downlink = 0 - } - connection := Connection{ - ID: metadata.ID.String(), - Inbound: metadata.Metadata.Inbound, - InboundType: metadata.Metadata.InboundType, - IPVersion: int32(metadata.Metadata.IPVersion), - Network: metadata.Metadata.Network, - Source: metadata.Metadata.Source.String(), - Destination: metadata.Metadata.Destination.String(), - Domain: metadata.Metadata.Domain, - Protocol: metadata.Metadata.Protocol, - User: metadata.Metadata.User, - FromOutbound: metadata.Metadata.Outbound, - CreatedAt: metadata.CreatedAt.UnixMilli(), - ClosedAt: closedAt, - Uplink: uplink, - Downlink: downlink, - UplinkTotal: uplinkTotal, - DownlinkTotal: downlinkTotal, - Rule: rule, - Outbound: metadata.Outbound, - OutboundType: metadata.OutboundType, - ChainList: metadata.Chain, - } - connections[metadata.ID] = &connection - return connection -} diff --git a/experimental/libbox/command_conntrack.go b/experimental/libbox/command_conntrack.go deleted file mode 100644 index cf8389a67..000000000 --- a/experimental/libbox/command_conntrack.go +++ /dev/null @@ -1,28 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - runtimeDebug "runtime/debug" - "time" - - "github.com/sagernet/sing-box/common/conntrack" -) - -func (c *CommandClient) CloseConnections() error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - return binary.Write(conn, binary.BigEndian, uint8(CommandCloseConnections)) -} - -func (s *CommandServer) handleCloseConnections(conn net.Conn) error { - conntrack.Close() - go func() { - time.Sleep(time.Second) - runtimeDebug.FreeOSMemory() - }() - return nil -} diff --git a/experimental/libbox/command_deprecated_report.go b/experimental/libbox/command_deprecated_report.go deleted file mode 100644 index 5772124c1..000000000 --- a/experimental/libbox/command_deprecated_report.go +++ /dev/null @@ -1,46 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - - "github.com/sagernet/sing-box/experimental/deprecated" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" - "github.com/sagernet/sing/service" -) - -func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) { - conn, err := c.directConnect() - if err != nil { - return nil, err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandGetDeprecatedNotes)) - if err != nil { - return nil, err - } - err = readError(conn) - if err != nil { - return nil, err - } - var features []deprecated.Note - err = varbin.Read(conn, binary.BigEndian, &features) - if err != nil { - return nil, err - } - return newIterator(common.Map(features, func(it deprecated.Note) *DeprecatedNote { return (*DeprecatedNote)(&it) })), nil -} - -func (s *CommandServer) handleGetDeprecatedNotes(conn net.Conn) error { - boxService := s.service - if boxService == nil { - return writeError(conn, E.New("service not ready")) - } - err := writeError(conn, nil) - if err != nil { - return err - } - return varbin.Write(conn, binary.BigEndian, service.FromContext[deprecated.Manager](boxService.ctx).(*deprecatedManager).Get()) -} diff --git a/experimental/libbox/command_group.go b/experimental/libbox/command_group.go deleted file mode 100644 index 684cac62c..000000000 --- a/experimental/libbox/command_group.go +++ /dev/null @@ -1,198 +0,0 @@ -package libbox - -import ( - "bufio" - "encoding/binary" - "io" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/protocol/group" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" - "github.com/sagernet/sing/service" -) - -func (c *CommandClient) handleGroupConn(conn net.Conn) { - defer conn.Close() - - for { - groups, err := readGroups(conn) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - c.handler.WriteGroups(groups) - } -} - -func (s *CommandServer) handleGroupConn(conn net.Conn) error { - var interval int64 - err := binary.Read(conn, binary.BigEndian, &interval) - if err != nil { - return E.Cause(err, "read interval") - } - ticker := time.NewTicker(time.Duration(interval)) - defer ticker.Stop() - ctx := connKeepAlive(conn) - writer := bufio.NewWriter(conn) - for { - service := s.service - if service != nil { - err = writeGroups(writer, service) - if err != nil { - return err - } - } else { - err = binary.Write(writer, binary.BigEndian, uint16(0)) - if err != nil { - return err - } - } - err = writer.Flush() - if err != nil { - return err - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-s.urlTestUpdate: - } - } -} - -type OutboundGroup struct { - Tag string - Type string - Selectable bool - Selected string - IsExpand bool - ItemList []*OutboundGroupItem -} - -func (g *OutboundGroup) GetItems() OutboundGroupItemIterator { - return newIterator(g.ItemList) -} - -type OutboundGroupIterator interface { - Next() *OutboundGroup - HasNext() bool -} - -type OutboundGroupItem struct { - Tag string - Type string - URLTestTime int64 - URLTestDelay int32 -} - -type OutboundGroupItemIterator interface { - Next() *OutboundGroupItem - HasNext() bool -} - -func readGroups(reader io.Reader) (OutboundGroupIterator, error) { - groups, err := varbin.ReadValue[[]*OutboundGroup](reader, binary.BigEndian) - if err != nil { - return nil, err - } - return newIterator(groups), nil -} - -func writeGroups(writer io.Writer, boxService *BoxService) error { - historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx) - cacheFile := service.FromContext[adapter.CacheFile](boxService.ctx) - outbounds := boxService.instance.Outbound().Outbounds() - var iGroups []adapter.OutboundGroup - for _, it := range outbounds { - if group, isGroup := it.(adapter.OutboundGroup); isGroup { - iGroups = append(iGroups, group) - } - } - var groups []OutboundGroup - for _, iGroup := range iGroups { - var outboundGroup OutboundGroup - outboundGroup.Tag = iGroup.Tag() - outboundGroup.Type = iGroup.Type() - _, outboundGroup.Selectable = iGroup.(*group.Selector) - outboundGroup.Selected = iGroup.Now() - if cacheFile != nil { - if isExpand, loaded := cacheFile.LoadGroupExpand(outboundGroup.Tag); loaded { - outboundGroup.IsExpand = isExpand - } - } - - for _, itemTag := range iGroup.All() { - itemOutbound, isLoaded := boxService.instance.Outbound().Outbound(itemTag) - if !isLoaded { - continue - } - - var item OutboundGroupItem - item.Tag = itemTag - item.Type = itemOutbound.Type() - if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil { - item.URLTestTime = history.Time.Unix() - item.URLTestDelay = int32(history.Delay) - } - outboundGroup.ItemList = append(outboundGroup.ItemList, &item) - } - if len(outboundGroup.ItemList) < 2 { - continue - } - groups = append(groups, outboundGroup) - } - return varbin.Write(writer, binary.BigEndian, groups) -} - -func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandGroupExpand)) - if err != nil { - return err - } - err = varbin.Write(conn, binary.BigEndian, groupTag) - if err != nil { - return err - } - err = binary.Write(conn, binary.BigEndian, isExpand) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error { - groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - return err - } - var isExpand bool - err = binary.Read(conn, binary.BigEndian, &isExpand) - if err != nil { - return err - } - serviceNow := s.service - if serviceNow == nil { - return writeError(conn, E.New("service not ready")) - } - cacheFile := service.FromContext[adapter.CacheFile](serviceNow.ctx) - if cacheFile != nil { - err = cacheFile.StoreGroupExpand(groupTag, isExpand) - if err != nil { - return writeError(conn, err) - } - } - return writeError(conn, nil) -} diff --git a/experimental/libbox/command_log.go b/experimental/libbox/command_log.go deleted file mode 100644 index 07f6e8391..000000000 --- a/experimental/libbox/command_log.go +++ /dev/null @@ -1,160 +0,0 @@ -package libbox - -import ( - "bufio" - "context" - "io" - "net" - "time" - - "github.com/sagernet/sing/common/binary" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" -) - -func (s *CommandServer) ResetLog() { - s.access.Lock() - defer s.access.Unlock() - s.savedLines.Init() - select { - case s.logReset <- struct{}{}: - default: - } -} - -func (s *CommandServer) WriteMessage(message string) { - s.subscriber.Emit(message) - s.access.Lock() - s.savedLines.PushBack(message) - if s.savedLines.Len() > s.maxLines { - s.savedLines.Remove(s.savedLines.Front()) - } - s.access.Unlock() -} - -func (s *CommandServer) handleLogConn(conn net.Conn) error { - var ( - interval int64 - timer *time.Timer - ) - err := binary.Read(conn, binary.BigEndian, &interval) - if err != nil { - return E.Cause(err, "read interval") - } - timer = time.NewTimer(time.Duration(interval)) - if !timer.Stop() { - <-timer.C - } - var savedLines []string - s.access.Lock() - savedLines = make([]string, 0, s.savedLines.Len()) - for element := s.savedLines.Front(); element != nil; element = element.Next() { - savedLines = append(savedLines, element.Value) - } - s.access.Unlock() - subscription, done, err := s.observer.Subscribe() - if err != nil { - return err - } - defer s.observer.UnSubscribe(subscription) - writer := bufio.NewWriter(conn) - select { - case <-s.logReset: - err = writer.WriteByte(1) - if err != nil { - return err - } - err = writer.Flush() - if err != nil { - return err - } - default: - } - if len(savedLines) > 0 { - err = writer.WriteByte(0) - if err != nil { - return err - } - err = varbin.Write(writer, binary.BigEndian, savedLines) - if err != nil { - return err - } - } - ctx := connKeepAlive(conn) - var logLines []string - for { - err = writer.Flush() - if err != nil { - return err - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-s.logReset: - err = writer.WriteByte(1) - if err != nil { - return err - } - case <-done: - return nil - case logLine := <-subscription: - logLines = logLines[:0] - logLines = append(logLines, logLine) - timer.Reset(time.Duration(interval)) - loopLogs: - for { - select { - case logLine = <-subscription: - logLines = append(logLines, logLine) - case <-timer.C: - break loopLogs - } - } - err = writer.WriteByte(0) - if err != nil { - return err - } - err = varbin.Write(writer, binary.BigEndian, logLines) - if err != nil { - return err - } - } - } -} - -func (c *CommandClient) handleLogConn(conn net.Conn) { - reader := bufio.NewReader(conn) - for { - messageType, err := reader.ReadByte() - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - var messages []string - switch messageType { - case 0: - err = varbin.Read(reader, binary.BigEndian, &messages) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - c.handler.WriteLogs(newIterator(messages)) - case 1: - c.handler.ClearLogs() - } - } -} - -func connKeepAlive(reader io.Reader) context.Context { - ctx, cancel := context.WithCancelCause(context.Background()) - go func() { - for { - _, err := reader.Read(make([]byte, 1)) - if err != nil { - cancel(err) - return - } - } - }() - return ctx -} diff --git a/experimental/libbox/command_power.go b/experimental/libbox/command_power.go deleted file mode 100644 index 009064900..000000000 --- a/experimental/libbox/command_power.go +++ /dev/null @@ -1,59 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - - "github.com/sagernet/sing/common/varbin" -) - -func (c *CommandClient) ServiceReload() error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandServiceReload)) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleServiceReload(conn net.Conn) error { - rErr := s.handler.ServiceReload() - err := binary.Write(conn, binary.BigEndian, rErr != nil) - if err != nil { - return err - } - if rErr != nil { - return varbin.Write(conn, binary.BigEndian, rErr.Error()) - } - return nil -} - -func (c *CommandClient) ServiceClose() error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandServiceClose)) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleServiceClose(conn net.Conn) error { - rErr := s.service.Close() - s.handler.PostServiceClose() - err := binary.Write(conn, binary.BigEndian, rErr != nil) - if err != nil { - return err - } - if rErr != nil { - return varbin.Write(conn, binary.BigEndian, rErr.Error()) - } - return nil -} diff --git a/experimental/libbox/command_select.go b/experimental/libbox/command_select.go deleted file mode 100644 index 6dd74a2d4..000000000 --- a/experimental/libbox/command_select.go +++ /dev/null @@ -1,58 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - - "github.com/sagernet/sing-box/protocol/group" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" -) - -func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandSelectOutbound)) - if err != nil { - return err - } - err = varbin.Write(conn, binary.BigEndian, groupTag) - if err != nil { - return err - } - err = varbin.Write(conn, binary.BigEndian, outboundTag) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleSelectOutbound(conn net.Conn) error { - groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - return err - } - outboundTag, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - return err - } - service := s.service - if service == nil { - return writeError(conn, E.New("service not ready")) - } - outboundGroup, isLoaded := service.instance.Outbound().Outbound(groupTag) - if !isLoaded { - return writeError(conn, E.New("selector not found: ", groupTag)) - } - selector, isSelector := outboundGroup.(*group.Selector) - if !isSelector { - return writeError(conn, E.New("outbound is not a selector: ", groupTag)) - } - if !selector.SelectOutbound(outboundTag) { - return writeError(conn, E.New("outbound not found in selector: ", outboundTag)) - } - return writeError(conn, nil) -} diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index 798a52bdd..5195c0b1a 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -1,182 +1,266 @@ package libbox import ( - "encoding/binary" + "context" + "errors" "net" "os" "path/filepath" - "sync" + "strconv" + "syscall" + "time" - "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/experimental/clashapi" + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/daemon" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/debug" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/observable" - "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" ) type CommandServer struct { - listener net.Listener - handler CommandServerHandler - - access sync.Mutex - savedLines list.List[string] - maxLines int - subscriber *observable.Subscriber[string] - observer *observable.Observer[string] - service *BoxService - - // These channels only work with a single client. if multi-client support is needed, replace with Subscriber/Observer - urlTestUpdate chan struct{} - modeUpdate chan struct{} - logReset chan struct{} - - closedConnections []Connection + *daemon.StartedService + handler CommandServerHandler + platformInterface PlatformInterface + platformWrapper *platformInterfaceWrapper + grpcServer *grpc.Server + listener net.Listener + endPauseTimer *time.Timer } type CommandServerHandler interface { + ServiceStop() error ServiceReload() error - PostServiceClose() - GetSystemProxyStatus() *SystemProxyStatus - SetSystemProxyEnabled(isEnabled bool) error + GetSystemProxyStatus() (*SystemProxyStatus, error) + SetSystemProxyEnabled(enabled bool) error + WriteDebugMessage(message string) } -func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer { +func NewCommandServer(handler CommandServerHandler, platformInterface PlatformInterface) (*CommandServer, error) { + ctx := BaseContext(platformInterface) + platformWrapper := &platformInterfaceWrapper{ + iif: platformInterface, + useProcFS: platformInterface.UseProcFS(), + } + service.MustRegister[adapter.PlatformInterface](ctx, platformWrapper) server := &CommandServer{ - handler: handler, - maxLines: int(maxLines), - subscriber: observable.NewSubscriber[string](128), - urlTestUpdate: make(chan struct{}, 1), - modeUpdate: make(chan struct{}, 1), - logReset: make(chan struct{}, 1), + handler: handler, + platformInterface: platformInterface, + platformWrapper: platformWrapper, } - server.observer = observable.NewObserver[string](server.subscriber, 64) - return server + server.StartedService = daemon.NewStartedService(daemon.ServiceOptions{ + Context: ctx, + // Platform: platformWrapper, + Handler: (*platformHandler)(server), + Debug: sDebug, + LogMaxLines: sLogMaxLines, + // WorkingDirectory: sWorkingPath, + // TempDirectory: sTempPath, + // UserID: sUserID, + // GroupID: sGroupID, + // SystemProxyEnabled: false, + }) + return server, nil } -func (s *CommandServer) SetService(newService *BoxService) { - if newService != nil { - service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate) - newService.clashServer.(*clashapi.Server).SetModeUpdateHook(s.modeUpdate) +func unaryAuthInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + if sCommandServerSecret == "" { + return handler(ctx, req) } - s.service = newService - s.notifyURLTestUpdate() + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, status.Error(codes.Unauthenticated, "missing metadata") + } + values := md.Get("x-command-secret") + if len(values) == 0 { + return nil, status.Error(codes.Unauthenticated, "missing authentication secret") + } + if values[0] != sCommandServerSecret { + return nil, status.Error(codes.Unauthenticated, "invalid authentication secret") + } + return handler(ctx, req) } -func (s *CommandServer) notifyURLTestUpdate() { - select { - case s.urlTestUpdate <- struct{}{}: - default: +func streamAuthInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + if sCommandServerSecret == "" { + return handler(srv, ss) } + md, ok := metadata.FromIncomingContext(ss.Context()) + if !ok { + return status.Error(codes.Unauthenticated, "missing metadata") + } + values := md.Get("x-command-secret") + if len(values) == 0 { + return status.Error(codes.Unauthenticated, "missing authentication secret") + } + if values[0] != sCommandServerSecret { + return status.Error(codes.Unauthenticated, "invalid authentication secret") + } + return handler(srv, ss) } func (s *CommandServer) Start() error { - if !sTVOS { - return s.listenUNIX() - } else { - return s.listenTCP() - } -} - -func (s *CommandServer) listenUNIX() error { - sockPath := filepath.Join(sBasePath, "command.sock") - os.Remove(sockPath) - listener, err := net.ListenUnix("unix", &net.UnixAddr{ - Name: sockPath, - Net: "unix", - }) - if err != nil { - return E.Cause(err, "listen ", sockPath) - } - err = os.Chown(sockPath, sUserID, sGroupID) - if err != nil { - listener.Close() - os.Remove(sockPath) - return E.Cause(err, "chown") - } - s.listener = listener - go s.loopConnection(listener) - return nil -} - -func (s *CommandServer) listenTCP() error { - listener, err := net.Listen("tcp", "127.0.0.1:8964") - if err != nil { - return E.Cause(err, "listen") - } - s.listener = listener - go s.loopConnection(listener) - return nil -} - -func (s *CommandServer) Close() error { - return common.Close( - s.listener, - s.observer, + var ( + listener net.Listener + err error ) -} - -func (s *CommandServer) loopConnection(listener net.Listener) { - for { - conn, err := listener.Accept() - if err != nil { - return - } - go func() { - hErr := s.handleConnection(conn) - if hErr != nil && !E.IsClosed(err) { - if debug.Enabled { - log.Warn("log-server: process connection: ", hErr) - } + if sCommandServerListenPort == 0 { + sockPath := filepath.Join(sBasePath, "command.sock") + os.Remove(sockPath) + for i := 0; i < 30; i++ { + listener, err = net.ListenUnix("unix", &net.UnixAddr{ + Name: sockPath, + Net: "unix", + }) + if err == nil { + break } - }() + if !errors.Is(err, syscall.EROFS) { + break + } + time.Sleep(time.Second) + } + if err != nil { + return E.Cause(err, "listen command server") + } + if sUserID != os.Getuid() { + err = os.Chown(sockPath, sUserID, sGroupID) + if err != nil { + listener.Close() + os.Remove(sockPath) + return E.Cause(err, "chown") + } + } + } else { + listener, err = net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(int(sCommandServerListenPort)))) + if err != nil { + return E.Cause(err, "listen command server") + } + } + s.listener = listener + serverOptions := []grpc.ServerOption{ + grpc.UnaryInterceptor(unaryAuthInterceptor), + grpc.StreamInterceptor(streamAuthInterceptor), + } + s.grpcServer = grpc.NewServer(serverOptions...) + daemon.RegisterStartedServiceServer(s.grpcServer, s.StartedService) + go s.grpcServer.Serve(listener) + return nil +} + +func (s *CommandServer) Close() { + if s.grpcServer != nil { + s.grpcServer.Stop() + } + common.Close(s.listener) +} + +type OverrideOptions struct { + AutoRedirect bool + IncludePackage StringIterator + ExcludePackage StringIterator +} + +func (s *CommandServer) StartOrReloadService(configContent string, options *OverrideOptions) error { + return s.StartedService.StartOrReloadService(configContent, &daemon.OverrideOptions{ + AutoRedirect: options.AutoRedirect, + IncludePackage: iteratorToArray(options.IncludePackage), + ExcludePackage: iteratorToArray(options.ExcludePackage), + }) +} + +func (s *CommandServer) CloseService() error { + return s.StartedService.CloseService() +} + +func (s *CommandServer) WriteMessage(level int32, message string) { + s.StartedService.WriteMessage(log.Level(level), message) +} + +func (s *CommandServer) SetError(message string) { + s.StartedService.SetError(E.New(message)) +} + +func (s *CommandServer) NeedWIFIState() bool { + instance := s.StartedService.Instance() + if instance == nil || instance.Box() == nil { + return false + } + return instance.Box().Router().NeedWIFIState() +} + +func (s *CommandServer) Pause() { + instance := s.StartedService.Instance() + if instance == nil || instance.PauseManager() == nil { + return + } + instance.PauseManager().DevicePause() + if C.IsIos { + if s.endPauseTimer == nil { + s.endPauseTimer = time.AfterFunc(time.Minute, instance.PauseManager().DeviceWake) + } else { + s.endPauseTimer.Reset(time.Minute) + } } } -func (s *CommandServer) handleConnection(conn net.Conn) error { - defer conn.Close() - var command uint8 - err := binary.Read(conn, binary.BigEndian, &command) - if err != nil { - return E.Cause(err, "read command") +func (s *CommandServer) Wake() { + instance := s.StartedService.Instance() + if instance == nil || instance.PauseManager() == nil { + return } - switch int32(command) { - case CommandLog: - return s.handleLogConn(conn) - case CommandStatus: - return s.handleStatusConn(conn) - case CommandServiceReload: - return s.handleServiceReload(conn) - case CommandServiceClose: - return s.handleServiceClose(conn) - case CommandCloseConnections: - return s.handleCloseConnections(conn) - case CommandGroup: - return s.handleGroupConn(conn) - case CommandSelectOutbound: - return s.handleSelectOutbound(conn) - case CommandURLTest: - return s.handleURLTest(conn) - case CommandGroupExpand: - return s.handleSetGroupExpand(conn) - case CommandClashMode: - return s.handleModeConn(conn) - case CommandSetClashMode: - return s.handleSetClashMode(conn) - case CommandGetSystemProxyStatus: - return s.handleGetSystemProxyStatus(conn) - case CommandSetSystemProxyEnabled: - return s.handleSetSystemProxyEnabled(conn) - case CommandConnections: - return s.handleConnectionsConn(conn) - case CommandCloseConnection: - return s.handleCloseConnection(conn) - case CommandGetDeprecatedNotes: - return s.handleGetDeprecatedNotes(conn) - default: - return E.New("unknown command: ", command) + if !C.IsIos { + instance.PauseManager().DeviceWake() } } + +func (s *CommandServer) ResetNetwork() { + instance := s.StartedService.Instance() + if instance == nil || instance.Box() == nil { + return + } + instance.Box().Router().ResetNetwork() +} + +func (s *CommandServer) UpdateWIFIState() { + instance := s.StartedService.Instance() + if instance == nil || instance.Box() == nil { + return + } + instance.Box().Network().UpdateWIFIState() +} + +type platformHandler CommandServer + +func (h *platformHandler) ServiceStop() error { + return (*CommandServer)(h).handler.ServiceStop() +} + +func (h *platformHandler) ServiceReload() error { + return (*CommandServer)(h).handler.ServiceReload() +} + +func (h *platformHandler) SystemProxyStatus() (*daemon.SystemProxyStatus, error) { + status, err := (*CommandServer)(h).handler.GetSystemProxyStatus() + if err != nil { + return nil, err + } + return &daemon.SystemProxyStatus{ + Enabled: status.Enabled, + Available: status.Available, + }, nil +} + +func (h *platformHandler) SetSystemProxyEnabled(enabled bool) error { + return (*CommandServer)(h).handler.SetSystemProxyEnabled(enabled) +} + +func (h *platformHandler) WriteDebugMessage(message string) { + (*CommandServer)(h).handler.WriteDebugMessage(message) +} diff --git a/experimental/libbox/command_shared.go b/experimental/libbox/command_shared.go deleted file mode 100644 index b98c2e5d3..000000000 --- a/experimental/libbox/command_shared.go +++ /dev/null @@ -1,39 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "io" - - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" -) - -func readError(reader io.Reader) error { - var hasError bool - err := binary.Read(reader, binary.BigEndian, &hasError) - if err != nil { - return err - } - if hasError { - errorMessage, err := varbin.ReadValue[string](reader, binary.BigEndian) - if err != nil { - return err - } - return E.New(errorMessage) - } - return nil -} - -func writeError(writer io.Writer, wErr error) error { - err := binary.Write(writer, binary.BigEndian, wErr != nil) - if err != nil { - return err - } - if wErr != nil { - err = varbin.Write(writer, binary.BigEndian, wErr.Error()) - if err != nil { - return err - } - } - return nil -} diff --git a/experimental/libbox/command_status.go b/experimental/libbox/command_status.go deleted file mode 100644 index f8709ef04..000000000 --- a/experimental/libbox/command_status.go +++ /dev/null @@ -1,85 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - "runtime" - "time" - - "github.com/sagernet/sing-box/common/conntrack" - "github.com/sagernet/sing-box/experimental/clashapi" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/memory" -) - -type StatusMessage struct { - Memory int64 - Goroutines int32 - ConnectionsIn int32 - ConnectionsOut int32 - TrafficAvailable bool - Uplink int64 - Downlink int64 - UplinkTotal int64 - DownlinkTotal int64 -} - -func (s *CommandServer) readStatus() StatusMessage { - var message StatusMessage - message.Memory = int64(memory.Inuse()) - message.Goroutines = int32(runtime.NumGoroutine()) - message.ConnectionsOut = int32(conntrack.Count()) - - if s.service != nil { - message.TrafficAvailable = true - trafficManager := s.service.clashServer.(*clashapi.Server).TrafficManager() - message.UplinkTotal, message.DownlinkTotal = trafficManager.Total() - message.ConnectionsIn = int32(trafficManager.ConnectionsLen()) - } - - return message -} - -func (s *CommandServer) handleStatusConn(conn net.Conn) error { - var interval int64 - err := binary.Read(conn, binary.BigEndian, &interval) - if err != nil { - return E.Cause(err, "read interval") - } - ticker := time.NewTicker(time.Duration(interval)) - defer ticker.Stop() - ctx := connKeepAlive(conn) - status := s.readStatus() - uploadTotal := status.UplinkTotal - downloadTotal := status.DownlinkTotal - for { - err = binary.Write(conn, binary.BigEndian, status) - if err != nil { - return err - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - status = s.readStatus() - upload := status.UplinkTotal - uploadTotal - download := status.DownlinkTotal - downloadTotal - uploadTotal = status.UplinkTotal - downloadTotal = status.DownlinkTotal - status.Uplink = upload - status.Downlink = download - } -} - -func (c *CommandClient) handleStatusConn(conn net.Conn) { - for { - var message StatusMessage - err := binary.Read(conn, binary.BigEndian, &message) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - c.handler.WriteStatus(&message) - } -} diff --git a/experimental/libbox/command_system_proxy.go b/experimental/libbox/command_system_proxy.go deleted file mode 100644 index 8a534ae8c..000000000 --- a/experimental/libbox/command_system_proxy.go +++ /dev/null @@ -1,80 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" -) - -type SystemProxyStatus struct { - Available bool - Enabled bool -} - -func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) { - conn, err := c.directConnectWithRetry() - if err != nil { - return nil, err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandGetSystemProxyStatus)) - if err != nil { - return nil, err - } - var status SystemProxyStatus - err = binary.Read(conn, binary.BigEndian, &status.Available) - if err != nil { - return nil, err - } - if status.Available { - err = binary.Read(conn, binary.BigEndian, &status.Enabled) - if err != nil { - return nil, err - } - } - return &status, nil -} - -func (s *CommandServer) handleGetSystemProxyStatus(conn net.Conn) error { - status := s.handler.GetSystemProxyStatus() - err := binary.Write(conn, binary.BigEndian, status.Available) - if err != nil { - return err - } - if status.Available { - err = binary.Write(conn, binary.BigEndian, status.Enabled) - if err != nil { - return err - } - } - return nil -} - -func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandSetSystemProxyEnabled)) - if err != nil { - return err - } - err = binary.Write(conn, binary.BigEndian, isEnabled) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleSetSystemProxyEnabled(conn net.Conn) error { - var isEnabled bool - err := binary.Read(conn, binary.BigEndian, &isEnabled) - if err != nil { - return err - } - err = s.handler.SetSystemProxyEnabled(isEnabled) - if err != nil { - return writeError(conn, err) - } - return writeError(conn, nil) -} diff --git a/experimental/libbox/command_types.go b/experimental/libbox/command_types.go new file mode 100644 index 000000000..9383d04b2 --- /dev/null +++ b/experimental/libbox/command_types.go @@ -0,0 +1,276 @@ +package libbox + +import ( + "slices" + "strings" + + "github.com/sagernet/sing-box/daemon" + M "github.com/sagernet/sing/common/metadata" +) + +type StatusMessage struct { + Memory int64 + Goroutines int32 + ConnectionsIn int32 + ConnectionsOut int32 + TrafficAvailable bool + Uplink int64 + Downlink int64 + UplinkTotal int64 + DownlinkTotal int64 +} + +type SystemProxyStatus struct { + Available bool + Enabled bool +} + +type OutboundGroup struct { + Tag string + Type string + Selectable bool + Selected string + IsExpand bool + ItemList []*OutboundGroupItem +} + +func (g *OutboundGroup) GetItems() OutboundGroupItemIterator { + return newIterator(g.ItemList) +} + +type OutboundGroupIterator interface { + Next() *OutboundGroup + HasNext() bool +} + +type OutboundGroupItem struct { + Tag string + Type string + URLTestTime int64 + URLTestDelay int32 +} + +type OutboundGroupItemIterator interface { + Next() *OutboundGroupItem + HasNext() bool +} + +const ( + ConnectionStateAll = iota + ConnectionStateActive + ConnectionStateClosed +) + +type Connections struct { + input []Connection + filtered []Connection +} + +func (c *Connections) FilterState(state int32) { + c.filtered = c.filtered[:0] + switch state { + case ConnectionStateAll: + c.filtered = append(c.filtered, c.input...) + case ConnectionStateActive: + for _, connection := range c.input { + if connection.ClosedAt == 0 { + c.filtered = append(c.filtered, connection) + } + } + case ConnectionStateClosed: + for _, connection := range c.input { + if connection.ClosedAt != 0 { + c.filtered = append(c.filtered, connection) + } + } + } +} + +func (c *Connections) SortByDate() { + slices.SortStableFunc(c.filtered, func(x, y Connection) int { + if x.CreatedAt < y.CreatedAt { + return 1 + } else if x.CreatedAt > y.CreatedAt { + return -1 + } else { + return strings.Compare(y.ID, x.ID) + } + }) +} + +func (c *Connections) SortByTraffic() { + slices.SortStableFunc(c.filtered, func(x, y Connection) int { + xTraffic := x.Uplink + x.Downlink + yTraffic := y.Uplink + y.Downlink + if xTraffic < yTraffic { + return 1 + } else if xTraffic > yTraffic { + return -1 + } else { + return strings.Compare(y.ID, x.ID) + } + }) +} + +func (c *Connections) SortByTrafficTotal() { + slices.SortStableFunc(c.filtered, func(x, y Connection) int { + xTraffic := x.UplinkTotal + x.DownlinkTotal + yTraffic := y.UplinkTotal + y.DownlinkTotal + if xTraffic < yTraffic { + return 1 + } else if xTraffic > yTraffic { + return -1 + } else { + return strings.Compare(y.ID, x.ID) + } + }) +} + +func (c *Connections) Iterator() ConnectionIterator { + return newPtrIterator(c.filtered) +} + +type Connection struct { + ID string + Inbound string + InboundType string + IPVersion int32 + Network string + Source string + Destination string + Domain string + Protocol string + User string + FromOutbound string + CreatedAt int64 + ClosedAt int64 + Uplink int64 + Downlink int64 + UplinkTotal int64 + DownlinkTotal int64 + Rule string + Outbound string + OutboundType string + ChainList []string +} + +func (c *Connection) Chain() StringIterator { + return newIterator(c.ChainList) +} + +func (c *Connection) DisplayDestination() string { + destination := M.ParseSocksaddr(c.Destination) + if destination.IsIP() && c.Domain != "" { + destination = M.Socksaddr{ + Fqdn: c.Domain, + Port: destination.Port, + } + return destination.String() + } + return c.Destination +} + +type ConnectionIterator interface { + Next() *Connection + HasNext() bool +} + +func StatusMessageFromGRPC(status *daemon.Status) *StatusMessage { + if status == nil { + return nil + } + return &StatusMessage{ + Memory: int64(status.Memory), + Goroutines: status.Goroutines, + ConnectionsIn: status.ConnectionsIn, + ConnectionsOut: status.ConnectionsOut, + TrafficAvailable: status.TrafficAvailable, + Uplink: status.Uplink, + Downlink: status.Downlink, + UplinkTotal: status.UplinkTotal, + DownlinkTotal: status.DownlinkTotal, + } +} + +func OutboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator { + if groups == nil || len(groups.Group) == 0 { + return newIterator([]*OutboundGroup{}) + } + var libboxGroups []*OutboundGroup + for _, g := range groups.Group { + libboxGroup := &OutboundGroup{ + Tag: g.Tag, + Type: g.Type, + Selectable: g.Selectable, + Selected: g.Selected, + IsExpand: g.IsExpand, + } + for _, item := range g.Items { + libboxGroup.ItemList = append(libboxGroup.ItemList, &OutboundGroupItem{ + Tag: item.Tag, + Type: item.Type, + URLTestTime: item.UrlTestTime, + URLTestDelay: item.UrlTestDelay, + }) + } + libboxGroups = append(libboxGroups, libboxGroup) + } + return newIterator(libboxGroups) +} + +func ConnectionFromGRPC(conn *daemon.Connection) Connection { + return Connection{ + ID: conn.Id, + Inbound: conn.Inbound, + InboundType: conn.InboundType, + IPVersion: conn.IpVersion, + Network: conn.Network, + Source: conn.Source, + Destination: conn.Destination, + Domain: conn.Domain, + Protocol: conn.Protocol, + User: conn.User, + FromOutbound: conn.FromOutbound, + CreatedAt: conn.CreatedAt, + ClosedAt: conn.ClosedAt, + Uplink: conn.Uplink, + Downlink: conn.Downlink, + UplinkTotal: conn.UplinkTotal, + DownlinkTotal: conn.DownlinkTotal, + Rule: conn.Rule, + Outbound: conn.Outbound, + OutboundType: conn.OutboundType, + ChainList: conn.ChainList, + } +} + +func ConnectionsFromGRPC(connections *daemon.Connections) []Connection { + if connections == nil || len(connections.Connections) == 0 { + return nil + } + var libboxConnections []Connection + for _, conn := range connections.Connections { + libboxConnections = append(libboxConnections, ConnectionFromGRPC(conn)) + } + return libboxConnections +} + +func SystemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxyStatus { + if status == nil { + return nil + } + return &SystemProxyStatus{ + Available: status.Available, + Enabled: status.Enabled, + } +} + +func SystemProxyStatusToGRPC(status *SystemProxyStatus) *daemon.SystemProxyStatus { + if status == nil { + return nil + } + return &daemon.SystemProxyStatus{ + Available: status.Available, + Enabled: status.Enabled, + } +} diff --git a/experimental/libbox/command_urltest.go b/experimental/libbox/command_urltest.go deleted file mode 100644 index 907d16998..000000000 --- a/experimental/libbox/command_urltest.go +++ /dev/null @@ -1,86 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/protocol/group" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/batch" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" - "github.com/sagernet/sing/service" -) - -func (c *CommandClient) URLTest(groupTag string) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandURLTest)) - if err != nil { - return err - } - err = varbin.Write(conn, binary.BigEndian, groupTag) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleURLTest(conn net.Conn) error { - groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - return err - } - serviceNow := s.service - if serviceNow == nil { - return nil - } - abstractOutboundGroup, isLoaded := serviceNow.instance.Outbound().Outbound(groupTag) - if !isLoaded { - return writeError(conn, E.New("outbound group not found: ", groupTag)) - } - outboundGroup, isOutboundGroup := abstractOutboundGroup.(adapter.OutboundGroup) - if !isOutboundGroup { - return writeError(conn, E.New("outbound is not a group: ", groupTag)) - } - urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest) - if isURLTest { - go urlTest.CheckOutbounds() - } else { - historyStorage := service.PtrFromContext[urltest.HistoryStorage](serviceNow.ctx) - outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { - itOutbound, _ := serviceNow.instance.Outbound().Outbound(it) - return itOutbound - }), func(it adapter.Outbound) bool { - if it == nil { - return false - } - _, isGroup := it.(adapter.OutboundGroup) - return !isGroup - }) - b, _ := batch.New(serviceNow.ctx, batch.WithConcurrencyNum[any](10)) - for _, detour := range outbounds { - outboundToTest := detour - outboundTag := outboundToTest.Tag() - b.Go(outboundTag, func() (any, error) { - t, err := urltest.URLTest(serviceNow.ctx, "", outboundToTest) - if err != nil { - historyStorage.DeleteURLTestHistory(outboundTag) - } else { - historyStorage.StoreURLTestHistory(outboundTag, &adapter.URLTestHistory{ - Time: time.Now(), - Delay: t, - }) - } - return nil, nil - }) - } - } - return writeError(conn, nil) -} diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 5df7d141b..946eea261 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -3,19 +3,16 @@ package libbox import ( "bytes" "context" - "net/netip" "os" - "github.com/sagernet/sing-box" + box "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/process" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" @@ -55,7 +52,7 @@ func CheckConfig(configContent string) error { } ctx, cancel := context.WithCancel(ctx) defer cancel() - ctx = service.ContextWith[platform.Interface](ctx, (*platformInterfaceStub)(nil)) + ctx = service.ContextWith[adapter.PlatformInterface](ctx, (*platformInterfaceStub)(nil)) instance, err := box.New(box.Options{ Context: ctx, Options: options, @@ -80,7 +77,11 @@ func (s *platformInterfaceStub) AutoDetectInterfaceControl(fd int) error { return nil } -func (s *platformInterfaceStub) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { +func (s *platformInterfaceStub) UsePlatformInterface() bool { + return false +} + +func (s *platformInterfaceStub) OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { return nil, os.ErrInvalid } @@ -92,7 +93,11 @@ func (s *platformInterfaceStub) CreateDefaultInterfaceMonitor(logger logger.Logg return (*interfaceMonitorStub)(nil) } -func (s *platformInterfaceStub) Interfaces() ([]adapter.NetworkInterface, error) { +func (s *platformInterfaceStub) UsePlatformNetworkInterfaces() bool { + return false +} + +func (s *platformInterfaceStub) NetworkInterfaces() ([]adapter.NetworkInterface, error) { return nil, os.ErrInvalid } @@ -100,15 +105,15 @@ func (s *platformInterfaceStub) UnderNetworkExtension() bool { return false } -func (s *platformInterfaceStub) IncludeAllNetworks() bool { +func (s *platformInterfaceStub) NetworkExtensionIncludeAllNetworks() bool { return false } func (s *platformInterfaceStub) ClearDNSCache() { } -func (s *platformInterfaceStub) UsePlatformWIFIMonitor() bool { - return false +func (s *platformInterfaceStub) RequestPermissionForWIFIState() error { + return nil } func (s *platformInterfaceStub) ReadWIFIState() adapter.WIFIState { @@ -119,11 +124,27 @@ func (s *platformInterfaceStub) SystemCertificates() []string { return nil } -func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) { +func (s *platformInterfaceStub) UsePlatformConnectionOwnerFinder() bool { + return false +} + +func (s *platformInterfaceStub) FindConnectionOwner(request *adapter.FindConnectionOwnerRequest) (*adapter.ConnectionOwner, error) { return nil, os.ErrInvalid } -func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error { +func (s *platformInterfaceStub) UsePlatformNotification() bool { + return false +} + +func (s *platformInterfaceStub) SendNotification(notification *adapter.Notification) error { + return nil +} + +func (s *platformInterfaceStub) UsePlatformLocalDNSTransport() bool { + return false +} + +func (s *platformInterfaceStub) LocalDNSTransport() dns.TransportConstructorFunc[option.LocalDNSServerOptions] { return nil } diff --git a/experimental/libbox/deprecated.go b/experimental/libbox/deprecated.go index f85b77471..0c2f8d8ab 100644 --- a/experimental/libbox/deprecated.go +++ b/experimental/libbox/deprecated.go @@ -1,33 +1,9 @@ package libbox import ( - "sync" - "github.com/sagernet/sing-box/experimental/deprecated" - "github.com/sagernet/sing/common" ) -var _ deprecated.Manager = (*deprecatedManager)(nil) - -type deprecatedManager struct { - access sync.Mutex - notes []deprecated.Note -} - -func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) { - m.access.Lock() - defer m.access.Unlock() - m.notes = common.Uniq(append(m.notes, feature)) -} - -func (m *deprecatedManager) Get() []deprecated.Note { - m.access.Lock() - defer m.access.Unlock() - notes := m.notes - m.notes = nil - return notes -} - var _ = deprecated.Note(DeprecatedNote{}) type DeprecatedNote struct { diff --git a/experimental/libbox/http.go b/experimental/libbox/http.go index e037de009..9f4b2915c 100644 --- a/experimental/libbox/http.go +++ b/experimental/libbox/http.go @@ -77,22 +77,27 @@ func NewHTTPClient() HTTPClient { } func (c *httpClient) ModernTLS() { - c.tls.MinVersion = tls.VersionTLS12 - c.tls.CipherSuites = common.Map(tls.CipherSuites(), func(it *tls.CipherSuite) uint16 { return it.ID }) + c.setTLSVersion(tls.VersionTLS12, 0, func(suite *tls.CipherSuite) bool { return true }) } func (c *httpClient) RestrictedTLS() { - c.tls.MinVersion = tls.VersionTLS13 - c.tls.CipherSuites = common.Map(common.Filter(tls.CipherSuites(), func(it *tls.CipherSuite) bool { - return common.Contains(it.SupportedVersions, uint16(tls.VersionTLS13)) - }), func(it *tls.CipherSuite) uint16 { + c.setTLSVersion(tls.VersionTLS13, 0, func(suite *tls.CipherSuite) bool { + return common.Contains(suite.SupportedVersions, uint16(tls.VersionTLS13)) + }) +} + +func (c *httpClient) setTLSVersion(minVersion, maxVersion uint16, filter func(*tls.CipherSuite) bool) { + c.tls.MinVersion = minVersion + if maxVersion != 0 { + c.tls.MaxVersion = maxVersion + } + c.tls.CipherSuites = common.Map(common.Filter(tls.CipherSuites(), filter), func(it *tls.CipherSuite) uint16 { return it.ID }) } func (c *httpClient) PinnedTLS12() { - c.tls.MinVersion = tls.VersionTLS12 - c.tls.MaxVersion = tls.VersionTLS12 + c.setTLSVersion(tls.VersionTLS12, tls.VersionTLS12, func(suite *tls.CipherSuite) bool { return true }) } func (c *httpClient) PinnedSHA256(sumHex string) { @@ -178,9 +183,7 @@ func (r *httpRequest) SetUserAgent(userAgent string) { } func (r *httpRequest) SetContent(content []byte) { - buffer := bytes.Buffer{} - buffer.Write(content) - r.request.Body = io.NopCloser(bytes.NewReader(buffer.Bytes())) + r.request.Body = io.NopCloser(bytes.NewReader(content)) r.request.ContentLength = int64(len(content)) } diff --git a/experimental/libbox/iterator.go b/experimental/libbox/iterator.go index b71ab886f..32cbbddb2 100644 --- a/experimental/libbox/iterator.go +++ b/experimental/libbox/iterator.go @@ -8,6 +8,12 @@ type StringIterator interface { Next() string } +type Int32Iterator interface { + Len() int32 + HasNext() bool + Next() int32 +} + var _ StringIterator = (*iterator[string])(nil) type iterator[T any] struct { diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index 00f63abd8..2deedb2e5 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -1,7 +1,7 @@ package libbox import ( - "github.com/sagernet/sing-tun" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index affcad387..22345201f 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -10,7 +10,6 @@ type PlatformInterface interface { UsePlatformAutoDetectInterfaceControl() bool AutoDetectInterfaceControl(fd int32) error OpenTun(options TunOptions) (int32, error) - WriteLog(message string) UseProcFS() bool FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error) PackageNameByUid(uid int32) (string, error) @@ -26,11 +25,6 @@ type PlatformInterface interface { SendNotification(notification *Notification) error } -type TunInterface interface { - FileDescriptor() int32 - Close() error -} - type InterfaceUpdateListener interface { UpdateDefaultInterface(interfaceName string, interfaceIndex int32, isExpensive bool, isConstrained bool) } diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go deleted file mode 100644 index d5b6d3c7d..000000000 --- a/experimental/libbox/platform/interface.go +++ /dev/null @@ -1,36 +0,0 @@ -package platform - -import ( - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/process" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common/logger" -) - -type Interface interface { - Initialize(networkManager adapter.NetworkManager) error - UsePlatformAutoDetectInterfaceControl() bool - AutoDetectInterfaceControl(fd int) error - OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) - CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor - Interfaces() ([]adapter.NetworkInterface, error) - UnderNetworkExtension() bool - IncludeAllNetworks() bool - ClearDNSCache() - UsePlatformWIFIMonitor() bool - ReadWIFIState() adapter.WIFIState - SystemCertificates() []string - process.Searcher - SendNotification(notification *Notification) error -} - -type Notification struct { - Identifier string - TypeName string - TypeID int32 - Title string - Subtitle string - Body string - OpenURL string -} diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index c272825f6..40c7a1492 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -1,123 +1,28 @@ package libbox import ( - "context" + "crypto/rand" + "encoding/hex" + "errors" + "net" "net/netip" - "os" "runtime" - runtimeDebug "runtime/debug" + "strconv" "sync" "syscall" - "time" - "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/process" - "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/experimental/libbox/internal/procfs" - "github.com/sagernet/sing-box/experimental/libbox/platform" - "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/service" - "github.com/sagernet/sing/service/pause" ) -type BoxService struct { - ctx context.Context - cancel context.CancelFunc - urlTestHistoryStorage adapter.URLTestHistoryStorage - instance *box.Box - clashServer adapter.ClashServer - pauseManager pause.Manager - - iOSPauseFields -} - -func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { - ctx := BaseContext(platformInterface) - service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager)) - options, err := parseConfig(ctx, configContent) - if err != nil { - return nil, err - } - runtimeDebug.FreeOSMemory() - ctx, cancel := context.WithCancel(ctx) - urlTestHistoryStorage := urltest.NewHistoryStorage() - ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) - platformWrapper := &platformInterfaceWrapper{ - iif: platformInterface, - useProcFS: platformInterface.UseProcFS(), - } - service.MustRegister[platform.Interface](ctx, platformWrapper) - instance, err := box.New(box.Options{ - Context: ctx, - Options: options, - PlatformLogWriter: platformWrapper, - }) - if err != nil { - cancel() - return nil, E.Cause(err, "create service") - } - runtimeDebug.FreeOSMemory() - return &BoxService{ - ctx: ctx, - cancel: cancel, - instance: instance, - urlTestHistoryStorage: urlTestHistoryStorage, - pauseManager: service.FromContext[pause.Manager](ctx), - clashServer: service.FromContext[adapter.ClashServer](ctx), - }, nil -} - -func (s *BoxService) Start() error { - if sFixAndroidStack { - var err error - done := make(chan struct{}) - go func() { - err = s.instance.Start() - close(done) - }() - <-done - return err - } else { - return s.instance.Start() - } -} - -func (s *BoxService) Close() error { - s.cancel() - s.urlTestHistoryStorage.Close() - var err error - done := make(chan struct{}) - go func() { - err = s.instance.Close() - close(done) - }() - select { - case <-done: - return err - case <-time.After(C.FatalStopTimeout): - os.Exit(1) - return nil - } -} - -func (s *BoxService) NeedWIFIState() bool { - return s.instance.Network().NeedWIFIState() -} - -var ( - _ platform.Interface = (*platformInterfaceWrapper)(nil) - _ log.PlatformWriter = (*platformInterfaceWrapper)(nil) -) +var _ adapter.PlatformInterface = (*platformInterfaceWrapper)(nil) type platformInterfaceWrapper struct { iif PlatformInterface @@ -143,7 +48,11 @@ func (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error { return w.iif.AutoDetectInterfaceControl(int32(fd)) } -func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { +func (w *platformInterfaceWrapper) UsePlatformInterface() bool { + return true +} + +func (w *platformInterfaceWrapper) OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 { return nil, E.New("platform: unsupported uid options") } @@ -172,6 +81,10 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions return tun.New(*options) } +func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool { + return true +} + func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor { return &platformDefaultInterfaceMonitor{ platformInterfaceWrapper: w, @@ -179,7 +92,11 @@ func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.L } } -func (w *platformInterfaceWrapper) Interfaces() ([]adapter.NetworkInterface, error) { +func (w *platformInterfaceWrapper) UsePlatformNetworkInterfaces() bool { + return true +} + +func (w *platformInterfaceWrapper) NetworkInterfaces() ([]adapter.NetworkInterface, error) { interfaceIterator, err := w.iif.GetInterfaces() if err != nil { return nil, err @@ -216,7 +133,7 @@ func (w *platformInterfaceWrapper) UnderNetworkExtension() bool { return w.iif.UnderNetworkExtension() } -func (w *platformInterfaceWrapper) IncludeAllNetworks() bool { +func (w *platformInterfaceWrapper) NetworkExtensionIncludeAllNetworks() bool { return w.iif.IncludeAllNetworks() } @@ -224,8 +141,8 @@ func (w *platformInterfaceWrapper) ClearDNSCache() { w.iif.ClearDNSCache() } -func (w *platformInterfaceWrapper) UsePlatformWIFIMonitor() bool { - return true +func (w *platformInterfaceWrapper) RequestPermissionForWIFIState() error { + return nil } func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState { @@ -240,41 +157,82 @@ func (w *platformInterfaceWrapper) SystemCertificates() []string { return iteratorToArray[string](w.iif.SystemCertificates()) } -func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) { +func (w *platformInterfaceWrapper) UsePlatformConnectionOwnerFinder() bool { + return true +} + +func (w *platformInterfaceWrapper) FindConnectionOwner(request *adapter.FindConnectionOwnerRequest) (*adapter.ConnectionOwner, error) { var uid int32 if w.useProcFS { + var source netip.AddrPort + var destination netip.AddrPort + sourceAddr, _ := netip.ParseAddr(request.SourceAddress) + source = netip.AddrPortFrom(sourceAddr, uint16(request.SourcePort)) + destAddr, _ := netip.ParseAddr(request.DestinationAddress) + destination = netip.AddrPortFrom(destAddr, uint16(request.DestinationPort)) + + var network string + switch request.IpProtocol { + case int32(syscall.IPPROTO_TCP): + network = "tcp" + case int32(syscall.IPPROTO_UDP): + network = "udp" + default: + return nil, E.New("unknown protocol: ", request.IpProtocol) + } + uid = procfs.ResolveSocketByProcSearch(network, source, destination) if uid == -1 { return nil, E.New("procfs: not found") } } else { - var ipProtocol int32 - switch N.NetworkName(network) { - case N.NetworkTCP: - ipProtocol = syscall.IPPROTO_TCP - case N.NetworkUDP: - ipProtocol = syscall.IPPROTO_UDP - default: - return nil, E.New("unknown network: ", network) - } var err error - uid, err = w.iif.FindConnectionOwner(ipProtocol, source.Addr().String(), int32(source.Port()), destination.Addr().String(), int32(destination.Port())) + uid, err = w.iif.FindConnectionOwner(request.IpProtocol, request.SourceAddress, request.SourcePort, request.DestinationAddress, request.DestinationPort) if err != nil { return nil, err } } packageName, _ := w.iif.PackageNameByUid(uid) - return &process.Info{UserId: uid, PackageName: packageName}, nil + return &adapter.ConnectionOwner{ + UserId: uid, + AndroidPackageName: packageName, + }, nil } func (w *platformInterfaceWrapper) DisableColors() bool { return runtime.GOOS != "android" } -func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) { - w.iif.WriteLog(message) +func (w *platformInterfaceWrapper) UsePlatformNotification() bool { + return true } -func (w *platformInterfaceWrapper) SendNotification(notification *platform.Notification) error { +func (w *platformInterfaceWrapper) SendNotification(notification *adapter.Notification) error { return w.iif.SendNotification((*Notification)(notification)) } + +func AvailablePort(startPort int32) (int32, error) { + for port := int(startPort); ; port++ { + if port > 65535 { + return 0, E.New("no available port found") + } + listener, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port)))) + if err != nil { + if errors.Is(err, syscall.EADDRINUSE) { + continue + } + return 0, E.Cause(err, "find available port") + } + err = listener.Close() + if err != nil { + return 0, E.Cause(err, "close listener") + } + return int32(port), nil + } +} + +func RandomHex(length int32) *StringBox { + bytes := make([]byte, length) + common.Must1(rand.Read(bytes)) + return wrapString(hex.EncodeToString(bytes)) +} diff --git a/experimental/libbox/service_error.go b/experimental/libbox/service_error.go deleted file mode 100644 index bb0593bf6..000000000 --- a/experimental/libbox/service_error.go +++ /dev/null @@ -1,32 +0,0 @@ -package libbox - -import ( - "os" - "path/filepath" -) - -func serviceErrorPath() string { - return filepath.Join(sWorkingPath, "network_extension_error") -} - -func ClearServiceError() { - os.Remove(serviceErrorPath()) -} - -func ReadServiceError() (*StringBox, error) { - data, err := os.ReadFile(serviceErrorPath()) - if err == nil { - os.Remove(serviceErrorPath()) - } - return wrapString(string(data)), err -} - -func WriteServiceError(message string) error { - errorFile, err := os.Create(serviceErrorPath()) - if err != nil { - return err - } - errorFile.WriteString(message) - errorFile.Chown(sUserID, sGroupID) - return errorFile.Close() -} diff --git a/experimental/libbox/service_pause.go b/experimental/libbox/service_pause.go deleted file mode 100644 index 9c888454f..000000000 --- a/experimental/libbox/service_pause.go +++ /dev/null @@ -1,36 +0,0 @@ -package libbox - -import ( - "time" - - C "github.com/sagernet/sing-box/constant" -) - -type iOSPauseFields struct { - endPauseTimer *time.Timer -} - -func (s *BoxService) Pause() { - s.pauseManager.DevicePause() - if C.IsIos { - if s.endPauseTimer == nil { - s.endPauseTimer = time.AfterFunc(time.Minute, s.pauseManager.DeviceWake) - } else { - s.endPauseTimer.Reset(time.Minute) - } - } -} - -func (s *BoxService) Wake() { - if !C.IsIos { - s.pauseManager.DeviceWake() - } -} - -func (s *BoxService) ResetNetwork() { - s.instance.Router().ResetNetwork() -} - -func (s *BoxService) UpdateWIFIState() { - s.instance.Network().UpdateWIFIState() -} diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index ad898fee7..b4b6ee8e9 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -2,9 +2,7 @@ package libbox import ( "os" - "os/user" "runtime/debug" - "strconv" "time" C "github.com/sagernet/sing-box/constant" @@ -14,55 +12,53 @@ import ( ) var ( - sBasePath string - sWorkingPath string - sTempPath string - sUserID int - sGroupID int - sTVOS bool - sFixAndroidStack bool + sBasePath string + sWorkingPath string + sTempPath string + sUserID int + sGroupID int + sFixAndroidStack bool + sCommandServerListenPort uint16 + sCommandServerSecret string + sLogMaxLines int + sDebug bool ) func init() { debug.SetPanicOnFault(true) + debug.SetTraceback("all") } type SetupOptions struct { - BasePath string - WorkingPath string - TempPath string - Username string - IsTVOS bool - FixAndroidStack bool + BasePath string + WorkingPath string + TempPath string + FixAndroidStack bool + CommandServerListenPort int32 + CommandServerSecret string + LogMaxLines int + Debug bool } func Setup(options *SetupOptions) error { sBasePath = options.BasePath sWorkingPath = options.WorkingPath sTempPath = options.TempPath - if options.Username != "" { - sUser, err := user.Lookup(options.Username) - if err != nil { - return err - } - sUserID, _ = strconv.Atoi(sUser.Uid) - sGroupID, _ = strconv.Atoi(sUser.Gid) - } else { - sUserID = os.Getuid() - sGroupID = os.Getgid() - } - sTVOS = options.IsTVOS + + sUserID = os.Getuid() + sGroupID = os.Getgid() // TODO: remove after fixed // https://github.com/golang/go/issues/68760 sFixAndroidStack = options.FixAndroidStack + sCommandServerListenPort = uint16(options.CommandServerListenPort) + sCommandServerSecret = options.CommandServerSecret + sLogMaxLines = options.LogMaxLines + sDebug = options.Debug + os.MkdirAll(sWorkingPath, 0o777) os.MkdirAll(sTempPath, 0o777) - if options.Username != "" { - os.Chown(sWorkingPath, sUserID, sGroupID) - os.Chown(sTempPath, sUserID, sGroupID) - } return nil } diff --git a/experimental/libbox/tun.go b/experimental/libbox/tun.go index 18b7910ef..84c6372ac 100644 --- a/experimental/libbox/tun.go +++ b/experimental/libbox/tun.go @@ -5,7 +5,7 @@ import ( "net/netip" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" ) diff --git a/log/observable.go b/log/observable.go index eea1df84d..768942bd4 100644 --- a/log/observable.go +++ b/log/observable.go @@ -50,9 +50,9 @@ func NewDefaultFactory( level: LevelTrace, subscriber: observable.NewSubscriber[Entry](128), } - if platformWriter != nil { + /*if platformWriter != nil { factory.platformFormatter.DisableColors = platformWriter.DisableColors() - } + }*/ if needObservable { factory.observer = observable.NewObserver[Entry](factory.subscriber, 64) } @@ -111,28 +111,30 @@ type observableLogger struct { func (l *observableLogger) Log(ctx context.Context, level Level, args []any) { level = OverrideLevelFromContext(level, ctx) - if level > l.level { + if level > l.level && l.platformWriter == nil { return } nowTime := time.Now() - if l.needObservable { - message, messageSimple := l.formatter.FormatWithSimple(ctx, level, l.tag, F.ToString(args...), nowTime) - if level == LevelPanic { - panic(message) - } - l.writer.Write([]byte(message)) - if level == LevelFatal { - os.Exit(1) - } - l.subscriber.Emit(Entry{level, messageSimple}) - } else { - message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime) - if level == LevelPanic { - panic(message) - } - l.writer.Write([]byte(message)) - if level == LevelFatal { - os.Exit(1) + if level <= l.level { + if l.needObservable { + message, messageSimple := l.formatter.FormatWithSimple(ctx, level, l.tag, F.ToString(args...), nowTime) + if level == LevelPanic { + panic(message) + } + l.writer.Write([]byte(message)) + if level == LevelFatal { + os.Exit(1) + } + l.subscriber.Emit(Entry{level, messageSimple}) + } else { + message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime) + if level == LevelPanic { + panic(message) + } + l.writer.Write([]byte(message)) + if level == LevelFatal { + os.Exit(1) + } } } if l.platformWriter != nil { diff --git a/log/platform.go b/log/platform.go index c6a9e525a..a8881d4cc 100644 --- a/log/platform.go +++ b/log/platform.go @@ -1,6 +1,5 @@ package log type PlatformWriter interface { - DisableColors() bool WriteMessage(level Level, message string) } diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index ab9790885..e7e4f9256 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -28,7 +28,6 @@ import ( "github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/route/rule" @@ -80,7 +79,7 @@ type Endpoint struct { logger logger.ContextLogger dnsRouter adapter.DNSRouter network adapter.NetworkManager - platformInterface platform.Interface + platformInterface adapter.PlatformInterface server *tsnet.Server stack *stack.Stack icmpForwarder *tun.ICMPForwarder @@ -190,7 +189,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL logger: logger, dnsRouter: dnsRouter, network: service.FromContext[adapter.NetworkManager](ctx), - platformInterface: service.FromContext[platform.Interface](ctx), + platformInterface: service.FromContext[adapter.PlatformInterface](ctx), server: server, acceptRoutes: options.AcceptRoutes, exitNode: options.ExitNode, @@ -290,7 +289,7 @@ func (t *Endpoint) watchState() { if authURL != "" { t.logger.Info("Waiting for authentication: ", authURL) if t.platformInterface != nil { - err := t.platformInterface.SendNotification(&platform.Notification{ + err := t.platformInterface.SendNotification(&adapter.Notification{ Identifier: "tailscale-authentication", TypeName: "Tailscale Authentication Notifications", TypeID: 10, diff --git a/protocol/tailscale/protect_android.go b/protocol/tailscale/protect_android.go index 90ab615a8..37dd33bdc 100644 --- a/protocol/tailscale/protect_android.go +++ b/protocol/tailscale/protect_android.go @@ -1,11 +1,11 @@ package tailscale import ( - "github.com/sagernet/sing-box/experimental/libbox/platform" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/tailscale/net/netns" ) -func setAndroidProtectFunc(platformInterface platform.Interface) { +func setAndroidProtectFunc(platformInterface adapter.PlatformInterface) { if platformInterface != nil { netns.SetAndroidProtectFunc(func(fd int) error { return platformInterface.AutoDetectInterfaceControl(fd) diff --git a/protocol/tailscale/protect_nonandroid.go b/protocol/tailscale/protect_nonandroid.go index eeb56bf6d..f315c2eaf 100644 --- a/protocol/tailscale/protect_nonandroid.go +++ b/protocol/tailscale/protect_nonandroid.go @@ -2,7 +2,7 @@ package tailscale -import "github.com/sagernet/sing-box/experimental/libbox/platform" +import "github.com/sagernet/sing-box/adapter" -func setAndroidProtectFunc(platformInterface platform.Interface) { +func setAndroidProtectFunc(platformInterface adapter.PlatformInterface) { } diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 20b1cd2f5..1cb2f3091 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -15,7 +15,6 @@ import ( "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/deprecated" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/route/rule" @@ -49,7 +48,7 @@ type Inbound struct { stack string tunIf tun.Tun tunStack tun.Stack - platformInterface platform.Interface + platformInterface adapter.PlatformInterface platformOptions option.TunPlatformOptions autoRedirect tun.AutoRedirect routeRuleSet []adapter.RuleSet @@ -131,7 +130,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo deprecated.Report(ctx, deprecated.OptionTUNGSO) } - platformInterface := service.FromContext[platform.Interface](ctx) + platformInterface := service.FromContext[adapter.PlatformInterface](ctx) tunMTU := options.MTU enableGSO := C.IsLinux && options.Stack == "gvisor" && platformInterface == nil && tunMTU > 0 && tunMTU < 49152 if tunMTU == 0 { @@ -373,8 +372,8 @@ func (t *Inbound) Start(stage adapter.StartStage) error { } } monitor.Start("open interface") - if t.platformInterface != nil { - tunInterface, err = t.platformInterface.OpenTun(&tunOptions, t.platformOptions) + if t.platformInterface != nil && t.platformInterface.UsePlatformInterface() { + tunInterface, err = t.platformInterface.OpenInterface(&tunOptions, t.platformOptions) } else { if HookBeforeCreatePlatformInterface != nil { HookBeforeCreatePlatformInterface() @@ -394,7 +393,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error { ) if t.platformInterface != nil { forwarderBindInterface = true - includeAllNetworks = t.platformInterface.IncludeAllNetworks() + includeAllNetworks = t.platformInterface.NetworkExtensionIncludeAllNetworks() } tunStack, err := tun.NewStack(t.stack, tun.StackOptions{ Context: t.ctx, diff --git a/route/network.go b/route/network.go index 52d74c8cf..3c0417668 100644 --- a/route/network.go +++ b/route/network.go @@ -17,7 +17,6 @@ import ( "github.com/sagernet/sing-box/common/settings" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" @@ -48,7 +47,7 @@ type NetworkManager struct { packageManager tun.PackageManager powerListener winpowrprof.EventListener pauseManager pause.Manager - platformInterface platform.Interface + platformInterface adapter.PlatformInterface endpoint adapter.EndpointManager inbound adapter.InboundManager outbound adapter.OutboundManager @@ -90,7 +89,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp FallbackDelay: time.Duration(routeOptions.DefaultFallbackDelay), }, pauseManager: service.FromContext[pause.Manager](ctx), - platformInterface: service.FromContext[platform.Interface](ctx), + platformInterface: service.FromContext[adapter.PlatformInterface](ctx), endpoint: service.FromContext[adapter.EndpointManager](ctx), inbound: service.FromContext[adapter.InboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx), @@ -189,7 +188,7 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { } } case adapter.StartStatePostStart: - if r.needWIFIState && !(r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor()) { + if r.needWIFIState && r.platformInterface == nil { wifiMonitor, err := settings.NewWIFIMonitor(r.onWIFIStateChanged) if err != nil { if err != os.ErrInvalid { @@ -264,10 +263,10 @@ func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder { } func (r *NetworkManager) UpdateInterfaces() error { - if r.platformInterface == nil { + if r.platformInterface == nil || !r.platformInterface.UsePlatformNetworkInterfaces() { return r.interfaceFinder.Update() } else { - interfaces, err := r.platformInterface.Interfaces() + interfaces, err := r.platformInterface.NetworkInterfaces() if err != nil { return err } @@ -440,7 +439,7 @@ func (r *NetworkManager) UpdateWIFIState() { var state adapter.WIFIState if r.wifiMonitor != nil { state = r.wifiMonitor.ReadWIFIState() - } else if r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor() { + } else if r.platformInterface != nil { state = r.platformInterface.ReadWIFIState() } else { return diff --git a/route/platform_searcher.go b/route/platform_searcher.go new file mode 100644 index 000000000..f6a4e764e --- /dev/null +++ b/route/platform_searcher.go @@ -0,0 +1,45 @@ +package route + +import ( + "context" + "net/netip" + "syscall" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/process" + N "github.com/sagernet/sing/common/network" +) + +type platformSearcher struct { + platform adapter.PlatformInterface +} + +func newPlatformSearcher(platform adapter.PlatformInterface) process.Searcher { + return &platformSearcher{platform: platform} +} + +func (s *platformSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { + if !s.platform.UsePlatformConnectionOwnerFinder() { + return nil, process.ErrNotFound + } + + var ipProtocol int32 + switch N.NetworkName(network) { + case N.NetworkTCP: + ipProtocol = syscall.IPPROTO_TCP + case N.NetworkUDP: + ipProtocol = syscall.IPPROTO_UDP + default: + return nil, process.ErrNotFound + } + + request := &adapter.FindConnectionOwnerRequest{ + IpProtocol: ipProtocol, + SourceAddress: source.Addr().String(), + SourcePort: int32(source.Port()), + DestinationAddress: destination.Addr().String(), + DestinationPort: int32(destination.Port()), + } + + return s.platform.FindConnectionOwner(request) +} diff --git a/route/route.go b/route/route.go index 1d6663d8d..2f1d01f7b 100644 --- a/route/route.go +++ b/route/route.go @@ -382,18 +382,18 @@ func (r *Router) matchRule( r.logger.InfoContext(ctx, "failed to search process: ", fErr) } else { if processInfo.ProcessPath != "" { - if processInfo.User != "" { - r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user: ", processInfo.User) + if processInfo.UserName != "" { + r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user: ", processInfo.UserName) } else if processInfo.UserId != -1 { r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user id: ", processInfo.UserId) } else { r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath) } - } else if processInfo.PackageName != "" { - r.logger.InfoContext(ctx, "found package name: ", processInfo.PackageName) + } else if processInfo.AndroidPackageName != "" { + r.logger.InfoContext(ctx, "found package name: ", processInfo.AndroidPackageName) } else if processInfo.UserId != -1 { - if processInfo.User != "" { - r.logger.InfoContext(ctx, "found user: ", processInfo.User) + if processInfo.UserName != "" { + r.logger.InfoContext(ctx, "found user: ", processInfo.UserName) } else { r.logger.InfoContext(ctx, "found user id: ", processInfo.UserId) } diff --git a/route/router.go b/route/router.go index aca65432b..e3802dd81 100644 --- a/route/router.go +++ b/route/router.go @@ -9,7 +9,6 @@ import ( "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" R "github.com/sagernet/sing-box/route/rule" @@ -37,7 +36,7 @@ type Router struct { processSearcher process.Searcher pauseManager pause.Manager trackers []adapter.ConnectionTracker - platformInterface platform.Interface + platformInterface adapter.PlatformInterface started bool } @@ -55,7 +54,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route ruleSetMap: make(map[string]adapter.RuleSet), needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess, pauseManager: service.FromContext[pause.Manager](ctx), - platformInterface: service.FromContext[platform.Interface](ctx), + platformInterface: service.FromContext[adapter.PlatformInterface](ctx), } } @@ -120,8 +119,8 @@ func (r *Router) Start(stage adapter.StartStage) error { } } if needFindProcess { - if r.platformInterface != nil { - r.processSearcher = r.platformInterface + if r.platformInterface != nil && r.platformInterface.UsePlatformConnectionOwnerFinder() { + r.processSearcher = newPlatformSearcher(r.platformInterface) } else { monitor.Start("initialize process searcher") searcher, err := process.NewSearcher(process.Config{ diff --git a/route/rule/rule_item_package_name.go b/route/rule/rule_item_package_name.go index 0066735c3..fa2275874 100644 --- a/route/rule/rule_item_package_name.go +++ b/route/rule/rule_item_package_name.go @@ -25,10 +25,10 @@ func NewPackageNameItem(packageNameList []string) *PackageNameItem { } func (r *PackageNameItem) Match(metadata *adapter.InboundContext) bool { - if metadata.ProcessInfo == nil || metadata.ProcessInfo.PackageName == "" { + if metadata.ProcessInfo == nil || metadata.ProcessInfo.AndroidPackageName == "" { return false } - return r.packageMap[metadata.ProcessInfo.PackageName] + return r.packageMap[metadata.ProcessInfo.AndroidPackageName] } func (r *PackageNameItem) String() string { diff --git a/route/rule/rule_item_user.go b/route/rule/rule_item_user.go index d635fa165..87a8bff10 100644 --- a/route/rule/rule_item_user.go +++ b/route/rule/rule_item_user.go @@ -26,10 +26,10 @@ func NewUserItem(users []string) *UserItem { } func (r *UserItem) Match(metadata *adapter.InboundContext) bool { - if metadata.ProcessInfo == nil || metadata.ProcessInfo.User == "" { + if metadata.ProcessInfo == nil || metadata.ProcessInfo.UserName == "" { return false } - return r.userMap[metadata.ProcessInfo.User] + return r.userMap[metadata.ProcessInfo.UserName] } func (r *UserItem) String() string { diff --git a/service/resolved/resolve1.go b/service/resolved/resolve1.go index 8e6dd3fa8..ed1ee41a0 100644 --- a/service/resolved/resolve1.go +++ b/service/resolved/resolve1.go @@ -15,7 +15,6 @@ import ( "syscall" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/process" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/log" @@ -111,7 +110,7 @@ func (t *resolve1Manager) createMetadata(sender dbus.Sender) adapter.InboundCont if err != nil { return metadata } - var processInfo process.Info + var processInfo adapter.ConnectionOwner metadata.ProcessInfo = &processInfo processInfo.ProcessID = uint32(senderPid) @@ -140,7 +139,7 @@ func (t *resolve1Manager) createMetadata(sender dbus.Sender) adapter.InboundCont processInfo.UserId = int32(uid) uidFound = true if osUser, _ := user.LookupId(F.ToString(uid)); osUser != nil { - processInfo.User = osUser.Username + processInfo.UserName = osUser.Username } break } @@ -159,8 +158,8 @@ func (t *resolve1Manager) log(sender dbus.Sender, message ...any) { var prefix string if metadata.ProcessInfo.ProcessPath != "" { prefix = filepath.Base(metadata.ProcessInfo.ProcessPath) - } else if metadata.ProcessInfo.User != "" { - prefix = F.ToString("user:", metadata.ProcessInfo.User) + } else if metadata.ProcessInfo.UserName != "" { + prefix = F.ToString("user:", metadata.ProcessInfo.UserName) } else if metadata.ProcessInfo.UserId != 0 { prefix = F.ToString("uid:", metadata.ProcessInfo.UserId) } @@ -177,8 +176,8 @@ func (t *resolve1Manager) logRequest(sender dbus.Sender, message ...any) context var prefix string if metadata.ProcessInfo.ProcessPath != "" { prefix = filepath.Base(metadata.ProcessInfo.ProcessPath) - } else if metadata.ProcessInfo.User != "" { - prefix = F.ToString("user:", metadata.ProcessInfo.User) + } else if metadata.ProcessInfo.UserName != "" { + prefix = F.ToString("user:", metadata.ProcessInfo.UserName) } else if metadata.ProcessInfo.UserId != 0 { prefix = F.ToString("uid:", metadata.ProcessInfo.UserId) }