platform: Refactoring libbox to use gRPC-based protocol

This commit is contained in:
世界
2025-10-07 15:40:11 +08:00
parent 743b460e51
commit 5bc0dfa9dd
67 changed files with 6131 additions and 2002 deletions

View File

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

View File

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

68
adapter/platform.go Normal file
View File

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

7
box.go
View File

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

View File

@@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

29
daemon/deprecated.go Normal file
View File

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

702
daemon/helper.pb.go Normal file
View File

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

61
daemon/helper.proto Normal file
View File

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

133
daemon/instance.go Normal file
View File

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

9
daemon/platform.go Normal file
View File

@@ -0,0 +1,9 @@
package daemon
type PlatformHandler interface {
ServiceStop() error
ServiceReload() error
SystemProxyStatus() (*SystemProxyStatus, error)
SetSystemProxyEnabled(enabled bool) error
WriteDebugMessage(message string)
}

830
daemon/started_service.go Normal file
View File

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

1906
daemon/started_service.pb.go Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -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 服务器的标签。

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
package log
type PlatformWriter interface {
DisableColors() bool
WriteMessage(level Level, message string)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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