mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-10-27 13:36:57 +00:00
Merge tag 'v1.14.0'
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
)
|
||||
|
||||
const defaultAttachedLogMaxLines = 3000
|
||||
|
||||
// StartOrReloadService and CloseService must not be called on an attached service.
|
||||
func NewAttachedService(ctx context.Context) *StartedService {
|
||||
instance := attachInstance(ctx)
|
||||
s := NewStartedService(ServiceOptions{
|
||||
Context: ctx,
|
||||
LogMaxLines: defaultAttachedLogMaxLines,
|
||||
})
|
||||
s.instance = instance
|
||||
s.serviceStatus = &ServiceStatus{Status: ServiceStatus_STARTED}
|
||||
s.startedAt = time.Now()
|
||||
instance.urlTestHistoryStorage.AddUpdateHook(s.urlTestSubscriber)
|
||||
if instance.clashServer != nil {
|
||||
instance.clashServer.AddModeUpdateHook(s.clashModeSubscriber)
|
||||
}
|
||||
instance.logFactory.(log.ObservableFactory).AttachPlatformWriter(s)
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
type RemoteClientOptions struct {
|
||||
ServerURL string
|
||||
Secret string
|
||||
}
|
||||
|
||||
func (o RemoteClientOptions) ServerTarget() (string, credentials.TransportCredentials, error) {
|
||||
if o.ServerURL == "" {
|
||||
return "", nil, E.New("missing server URL")
|
||||
}
|
||||
serverURL, err := url.Parse(o.ServerURL)
|
||||
if err != nil {
|
||||
return "", nil, E.Cause(err, "invalid server URL: ", o.ServerURL)
|
||||
}
|
||||
var enableTLS bool
|
||||
switch serverURL.Scheme {
|
||||
case "http":
|
||||
case "https":
|
||||
enableTLS = true
|
||||
default:
|
||||
return "", nil, E.New("invalid server URL scheme: ", serverURL.Scheme, ", expected http or https")
|
||||
}
|
||||
host := serverURL.Hostname()
|
||||
if host == "" {
|
||||
return "", nil, E.New("missing host in server URL: ", o.ServerURL)
|
||||
}
|
||||
port := serverURL.Port()
|
||||
if port == "" {
|
||||
if enableTLS {
|
||||
port = "443"
|
||||
} else {
|
||||
port = "80"
|
||||
}
|
||||
}
|
||||
transportCredentials := insecure.NewCredentials()
|
||||
if enableTLS {
|
||||
transportCredentials = credentials.NewTLS(&tls.Config{ServerName: host})
|
||||
}
|
||||
return net.JoinHostPort(host, port), transportCredentials, nil
|
||||
}
|
||||
|
||||
func NewRemoteClient(options RemoteClientOptions) (*grpc.ClientConn, error) {
|
||||
target, transportCredentials, err := options.ServerTarget()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return grpc.NewClient(target,
|
||||
grpc.WithTransportCredentials(transportCredentials),
|
||||
grpc.WithChainUnaryInterceptor(UnaryClientLocaleInterceptor, NewClientAuthUnaryInterceptor(options.Secret)),
|
||||
grpc.WithChainStreamInterceptor(StreamClientLocaleInterceptor, NewClientAuthStreamInterceptor(options.Secret)),
|
||||
)
|
||||
}
|
||||
|
||||
func NewClientAuthUnaryInterceptor(secret string) grpc.UnaryClientInterceptor {
|
||||
return func(ctx context.Context, method string, request, reply any, clientConn *grpc.ClientConn, invoker grpc.UnaryInvoker, options ...grpc.CallOption) error {
|
||||
if secret != "" {
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+secret)
|
||||
}
|
||||
return invoker(ctx, method, request, reply, clientConn, options...)
|
||||
}
|
||||
}
|
||||
|
||||
func NewClientAuthStreamInterceptor(secret string) grpc.StreamClientInterceptor {
|
||||
return func(ctx context.Context, desc *grpc.StreamDesc, clientConn *grpc.ClientConn, method string, streamer grpc.Streamer, options ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
if secret != "" {
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+secret)
|
||||
}
|
||||
return streamer(ctx, desc, clientConn, method, options...)
|
||||
}
|
||||
}
|
||||
+50
-11
@@ -6,10 +6,11 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/trafficcontrol"
|
||||
"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/include"
|
||||
"github.com/sagernet/sing-box/experimental/locale"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
@@ -25,17 +26,23 @@ type Instance struct {
|
||||
instance *box.Box
|
||||
connectionManager adapter.ConnectionManager
|
||||
clashServer adapter.ClashServer
|
||||
trafficManager *trafficcontrol.Manager
|
||||
cacheFile adapter.CacheFile
|
||||
pauseManager pause.Manager
|
||||
urlTestHistoryStorage *urltest.HistoryStorage
|
||||
outboundManager adapter.OutboundManager
|
||||
endpointManager adapter.EndpointManager
|
||||
logFactory log.Factory
|
||||
}
|
||||
|
||||
func (s *StartedService) CheckConfig(configContent string) error {
|
||||
options, err := parseConfig(s.ctx, configContent)
|
||||
func (s *StartedService) CheckConfig(ctx context.Context, configContent string) error {
|
||||
selectedLocale := locale.FromContext(ctx)
|
||||
ctx, _ = locale.ContextWithLocale(s.ctx, selectedLocale.Locale)
|
||||
options, err := parseConfig(ctx, configContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(s.ctx)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
instance, err := box.New(box.Options{
|
||||
Context: ctx,
|
||||
@@ -47,8 +54,10 @@ func (s *StartedService) CheckConfig(configContent string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StartedService) FormatConfig(configContent string) (string, error) {
|
||||
options, err := parseConfig(s.ctx, configContent)
|
||||
func (s *StartedService) FormatConfig(ctx context.Context, configContent string) (string, error) {
|
||||
selectedLocale := locale.FromContext(ctx)
|
||||
ctx, _ = locale.ContextWithLocale(s.ctx, selectedLocale.Locale)
|
||||
options, err := parseConfig(ctx, configContent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -68,10 +77,12 @@ type OverrideOptions struct {
|
||||
ExcludePackage []string
|
||||
}
|
||||
|
||||
func (s *StartedService) newInstance(profileContent string, overrideOptions *OverrideOptions) (*Instance, error) {
|
||||
ctx := service.ExtendContext(s.ctx)
|
||||
func (s *StartedService) newInstance(ctx context.Context, profileContent string, overrideOptions *OverrideOptions) (*Instance, error) {
|
||||
selectedLocale := locale.FromContext(ctx)
|
||||
ctx, _ = locale.ContextWithLocale(s.ctx, selectedLocale.Locale)
|
||||
ctx = service.ExtendContext(ctx)
|
||||
service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))
|
||||
ctx, cancel := context.WithCancel(include.Context(ctx))
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
options, err := parseConfig(ctx, profileContent)
|
||||
if err != nil {
|
||||
cancel()
|
||||
@@ -87,12 +98,17 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
|
||||
}
|
||||
}
|
||||
}
|
||||
if s.oomKiller && C.IsIos {
|
||||
if s.oomKillerEnabled {
|
||||
if !common.Any(options.Services, func(it option.Service) bool {
|
||||
return it.Type == C.TypeOOMKiller
|
||||
}) {
|
||||
oomOptions := &option.OOMKillerServiceOptions{
|
||||
KillerDisabled: s.oomKillerDisabled,
|
||||
MemoryLimitOverride: s.oomMemoryLimit,
|
||||
}
|
||||
options.Services = append(options.Services, option.Service{
|
||||
Type: C.TypeOOMKiller,
|
||||
Type: C.TypeOOMKiller,
|
||||
Options: oomOptions,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -119,12 +135,31 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
|
||||
i.instance = boxInstance
|
||||
i.connectionManager = service.FromContext[adapter.ConnectionManager](ctx)
|
||||
i.clashServer = service.FromContext[adapter.ClashServer](ctx)
|
||||
i.trafficManager = service.PtrFromContext[trafficcontrol.Manager](ctx)
|
||||
i.pauseManager = service.FromContext[pause.Manager](ctx)
|
||||
i.cacheFile = service.FromContext[adapter.CacheFile](ctx)
|
||||
i.outboundManager = service.FromContext[adapter.OutboundManager](ctx)
|
||||
i.endpointManager = service.FromContext[adapter.EndpointManager](ctx)
|
||||
i.logFactory = boxInstance.LogFactory()
|
||||
log.SetStdLogger(boxInstance.LogFactory().Logger())
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func attachInstance(ctx context.Context) *Instance {
|
||||
return &Instance{
|
||||
ctx: ctx,
|
||||
connectionManager: service.FromContext[adapter.ConnectionManager](ctx),
|
||||
clashServer: service.FromContext[adapter.ClashServer](ctx),
|
||||
trafficManager: service.PtrFromContext[trafficcontrol.Manager](ctx),
|
||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||
cacheFile: service.FromContext[adapter.CacheFile](ctx),
|
||||
urlTestHistoryStorage: service.PtrFromContext[urltest.HistoryStorage](ctx),
|
||||
outboundManager: service.FromContext[adapter.OutboundManager](ctx),
|
||||
endpointManager: service.FromContext[adapter.EndpointManager](ctx),
|
||||
logFactory: service.FromContext[log.Factory](ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Instance) Start() error {
|
||||
return i.instance.Start()
|
||||
}
|
||||
@@ -143,6 +178,10 @@ func (i *Instance) PauseManager() pause.Manager {
|
||||
return i.pauseManager
|
||||
}
|
||||
|
||||
func (i *Instance) TrafficManager() *trafficcontrol.Manager {
|
||||
return i.trafficManager
|
||||
}
|
||||
|
||||
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
||||
options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent))
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/locale"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
func contextWithLocale(ctx context.Context) context.Context {
|
||||
requestMetadata, loaded := metadata.FromIncomingContext(ctx)
|
||||
if !loaded {
|
||||
return ctx
|
||||
}
|
||||
for _, localeID := range requestMetadata.Get("accept-language") {
|
||||
localizedContext, matched := locale.ContextWithLocale(ctx, localeID)
|
||||
if matched {
|
||||
return localizedContext
|
||||
}
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func UnaryLocaleInterceptor(ctx context.Context, request any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
return handler(contextWithLocale(ctx), request)
|
||||
}
|
||||
|
||||
func StreamLocaleInterceptor(server any, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
return handler(server, &localeServerStream{
|
||||
ServerStream: stream,
|
||||
ctx: contextWithLocale(stream.Context()),
|
||||
})
|
||||
}
|
||||
|
||||
func UnaryClientLocaleInterceptor(ctx context.Context, method string, request, reply any, connection *grpc.ClientConn, invoker grpc.UnaryInvoker, options ...grpc.CallOption) error {
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "accept-language", locale.FromContext(ctx).Locale)
|
||||
return invoker(ctx, method, request, reply, connection, options...)
|
||||
}
|
||||
|
||||
func StreamClientLocaleInterceptor(ctx context.Context, description *grpc.StreamDesc, connection *grpc.ClientConn, method string, streamer grpc.Streamer, options ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "accept-language", locale.FromContext(ctx).Locale)
|
||||
return streamer(ctx, description, connection, method, options...)
|
||||
}
|
||||
|
||||
type localeServerStream struct {
|
||||
grpc.ServerStream
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (s *localeServerStream) Context() context.Context {
|
||||
return s.ctx
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-box/service/oomkiller"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
var _ ManagedServiceServer = (*ManagedService)(nil)
|
||||
|
||||
type ManagedService struct {
|
||||
handler ManagedHandler
|
||||
debug bool
|
||||
oomReporter oomkiller.OOMReporter
|
||||
}
|
||||
|
||||
type ManagedServiceOptions struct {
|
||||
Handler ManagedHandler
|
||||
Debug bool
|
||||
OOMReporter oomkiller.OOMReporter
|
||||
}
|
||||
|
||||
func NewManagedService(options ManagedServiceOptions) *ManagedService {
|
||||
return &ManagedService{
|
||||
handler: options.Handler,
|
||||
debug: options.Debug,
|
||||
oomReporter: options.OOMReporter,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ManagedService) 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 *ManagedService) ReloadService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
err := s.handler.ServiceReload(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *ManagedService) GetSystemProxyStatus(ctx context.Context, empty *emptypb.Empty) (*SystemProxyStatus, error) {
|
||||
return s.handler.SystemProxyStatus()
|
||||
}
|
||||
|
||||
func (s *ManagedService) SetSystemProxyEnabled(ctx context.Context, request *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) {
|
||||
err := s.handler.SetSystemProxyEnabled(request.Enabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *ManagedService) TriggerDebugCrash(ctx context.Context, request *DebugCrashRequest) (*emptypb.Empty, error) {
|
||||
if !s.debug {
|
||||
return nil, status.Error(codes.PermissionDenied, "debug crash trigger unavailable")
|
||||
}
|
||||
if request == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "missing debug crash request")
|
||||
}
|
||||
switch request.Type {
|
||||
case DebugCrashRequest_GO:
|
||||
time.AfterFunc(200*time.Millisecond, func() {
|
||||
*(*int)(unsafe.Pointer(uintptr(0))) = 0
|
||||
})
|
||||
case DebugCrashRequest_NATIVE:
|
||||
err := s.handler.TriggerNativeCrash()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, status.Error(codes.InvalidArgument, "unknown debug crash type")
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *ManagedService) TriggerOOMReport(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
if s.oomReporter == nil {
|
||||
return nil, status.Error(codes.Unavailable, "OOM reporter not available")
|
||||
}
|
||||
return &emptypb.Empty{}, s.oomReporter.WriteReport(memory.Total())
|
||||
}
|
||||
|
||||
func (s *ManagedService) mustEmbedUnimplementedManagedServiceServer() {
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
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 DebugCrashRequest_Type int32
|
||||
|
||||
const (
|
||||
DebugCrashRequest_GO DebugCrashRequest_Type = 0
|
||||
DebugCrashRequest_NATIVE DebugCrashRequest_Type = 1
|
||||
)
|
||||
|
||||
// Enum value maps for DebugCrashRequest_Type.
|
||||
var (
|
||||
DebugCrashRequest_Type_name = map[int32]string{
|
||||
0: "GO",
|
||||
1: "NATIVE",
|
||||
}
|
||||
DebugCrashRequest_Type_value = map[string]int32{
|
||||
"GO": 0,
|
||||
"NATIVE": 1,
|
||||
}
|
||||
)
|
||||
|
||||
func (x DebugCrashRequest_Type) Enum() *DebugCrashRequest_Type {
|
||||
p := new(DebugCrashRequest_Type)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x DebugCrashRequest_Type) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (DebugCrashRequest_Type) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_daemon_managed_service_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (DebugCrashRequest_Type) Type() protoreflect.EnumType {
|
||||
return &file_daemon_managed_service_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x DebugCrashRequest_Type) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DebugCrashRequest_Type.Descriptor instead.
|
||||
func (DebugCrashRequest_Type) EnumDescriptor() ([]byte, []int) {
|
||||
return file_daemon_managed_service_proto_rawDescGZIP(), []int{2, 0}
|
||||
}
|
||||
|
||||
type SystemProxyStatus struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Available bool `protobuf:"varint,1,opt,name=available,proto3" json:"available,omitempty"`
|
||||
Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SystemProxyStatus) Reset() {
|
||||
*x = SystemProxyStatus{}
|
||||
mi := &file_daemon_managed_service_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SystemProxyStatus) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SystemProxyStatus) ProtoMessage() {}
|
||||
|
||||
func (x *SystemProxyStatus) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_managed_service_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SystemProxyStatus.ProtoReflect.Descriptor instead.
|
||||
func (*SystemProxyStatus) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_managed_service_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *SystemProxyStatus) GetAvailable() bool {
|
||||
if x != nil {
|
||||
return x.Available
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *SystemProxyStatus) GetEnabled() bool {
|
||||
if x != nil {
|
||||
return x.Enabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type SetSystemProxyEnabledRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SetSystemProxyEnabledRequest) Reset() {
|
||||
*x = SetSystemProxyEnabledRequest{}
|
||||
mi := &file_daemon_managed_service_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SetSystemProxyEnabledRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SetSystemProxyEnabledRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SetSystemProxyEnabledRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_managed_service_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SetSystemProxyEnabledRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SetSystemProxyEnabledRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_managed_service_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *SetSystemProxyEnabledRequest) GetEnabled() bool {
|
||||
if x != nil {
|
||||
return x.Enabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type DebugCrashRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type DebugCrashRequest_Type `protobuf:"varint,1,opt,name=type,proto3,enum=daemon.DebugCrashRequest_Type" json:"type,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *DebugCrashRequest) Reset() {
|
||||
*x = DebugCrashRequest{}
|
||||
mi := &file_daemon_managed_service_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *DebugCrashRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DebugCrashRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DebugCrashRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_managed_service_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DebugCrashRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DebugCrashRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_managed_service_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *DebugCrashRequest) GetType() DebugCrashRequest_Type {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return DebugCrashRequest_GO
|
||||
}
|
||||
|
||||
var File_daemon_managed_service_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_daemon_managed_service_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1cdaemon/managed_service.proto\x12\x06daemon\x1a\x1bgoogle/protobuf/empty.proto\"K\n" +
|
||||
"\x11SystemProxyStatus\x12\x1c\n" +
|
||||
"\tavailable\x18\x01 \x01(\bR\tavailable\x12\x18\n" +
|
||||
"\aenabled\x18\x02 \x01(\bR\aenabled\"8\n" +
|
||||
"\x1cSetSystemProxyEnabledRequest\x12\x18\n" +
|
||||
"\aenabled\x18\x01 \x01(\bR\aenabled\"c\n" +
|
||||
"\x11DebugCrashRequest\x122\n" +
|
||||
"\x04type\x18\x01 \x01(\x0e2\x1e.daemon.DebugCrashRequest.TypeR\x04type\"\x1a\n" +
|
||||
"\x04Type\x12\x06\n" +
|
||||
"\x02GO\x10\x00\x12\n" +
|
||||
"\n" +
|
||||
"\x06NATIVE\x10\x012\xc6\x03\n" +
|
||||
"\x0eManagedService\x12=\n" +
|
||||
"\vStopService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12?\n" +
|
||||
"\rReloadService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12K\n" +
|
||||
"\x14GetSystemProxyStatus\x12\x16.google.protobuf.Empty\x1a\x19.daemon.SystemProxyStatus\"\x00\x12W\n" +
|
||||
"\x15SetSystemProxyEnabled\x12$.daemon.SetSystemProxyEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12H\n" +
|
||||
"\x11TriggerDebugCrash\x12\x19.daemon.DebugCrashRequest\x1a\x16.google.protobuf.Empty\"\x00\x12D\n" +
|
||||
"\x10TriggerOOMReport\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00B%Z#github.com/sagernet/sing-box/daemonb\x06proto3"
|
||||
|
||||
var (
|
||||
file_daemon_managed_service_proto_rawDescOnce sync.Once
|
||||
file_daemon_managed_service_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_daemon_managed_service_proto_rawDescGZIP() []byte {
|
||||
file_daemon_managed_service_proto_rawDescOnce.Do(func() {
|
||||
file_daemon_managed_service_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_daemon_managed_service_proto_rawDesc), len(file_daemon_managed_service_proto_rawDesc)))
|
||||
})
|
||||
return file_daemon_managed_service_proto_rawDescData
|
||||
}
|
||||
|
||||
var (
|
||||
file_daemon_managed_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
file_daemon_managed_service_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
file_daemon_managed_service_proto_goTypes = []any{
|
||||
DebugCrashRequest_Type(0), // 0: daemon.DebugCrashRequest.Type
|
||||
(*SystemProxyStatus)(nil), // 1: daemon.SystemProxyStatus
|
||||
(*SetSystemProxyEnabledRequest)(nil), // 2: daemon.SetSystemProxyEnabledRequest
|
||||
(*DebugCrashRequest)(nil), // 3: daemon.DebugCrashRequest
|
||||
(*emptypb.Empty)(nil), // 4: google.protobuf.Empty
|
||||
}
|
||||
)
|
||||
|
||||
var file_daemon_managed_service_proto_depIdxs = []int32{
|
||||
0, // 0: daemon.DebugCrashRequest.type:type_name -> daemon.DebugCrashRequest.Type
|
||||
4, // 1: daemon.ManagedService.StopService:input_type -> google.protobuf.Empty
|
||||
4, // 2: daemon.ManagedService.ReloadService:input_type -> google.protobuf.Empty
|
||||
4, // 3: daemon.ManagedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty
|
||||
2, // 4: daemon.ManagedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest
|
||||
3, // 5: daemon.ManagedService.TriggerDebugCrash:input_type -> daemon.DebugCrashRequest
|
||||
4, // 6: daemon.ManagedService.TriggerOOMReport:input_type -> google.protobuf.Empty
|
||||
4, // 7: daemon.ManagedService.StopService:output_type -> google.protobuf.Empty
|
||||
4, // 8: daemon.ManagedService.ReloadService:output_type -> google.protobuf.Empty
|
||||
1, // 9: daemon.ManagedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus
|
||||
4, // 10: daemon.ManagedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty
|
||||
4, // 11: daemon.ManagedService.TriggerDebugCrash:output_type -> google.protobuf.Empty
|
||||
4, // 12: daemon.ManagedService.TriggerOOMReport:output_type -> google.protobuf.Empty
|
||||
7, // [7:13] is the sub-list for method output_type
|
||||
1, // [1:7] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_daemon_managed_service_proto_init() }
|
||||
func file_daemon_managed_service_proto_init() {
|
||||
if File_daemon_managed_service_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_managed_service_proto_rawDesc), len(file_daemon_managed_service_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_daemon_managed_service_proto_goTypes,
|
||||
DependencyIndexes: file_daemon_managed_service_proto_depIdxs,
|
||||
EnumInfos: file_daemon_managed_service_proto_enumTypes,
|
||||
MessageInfos: file_daemon_managed_service_proto_msgTypes,
|
||||
}.Build()
|
||||
File_daemon_managed_service_proto = out.File
|
||||
file_daemon_managed_service_proto_goTypes = nil
|
||||
file_daemon_managed_service_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package daemon;
|
||||
option go_package = "github.com/sagernet/sing-box/daemon";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
service ManagedService {
|
||||
rpc StopService(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
rpc ReloadService(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
|
||||
rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}
|
||||
rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}
|
||||
rpc TriggerDebugCrash(DebugCrashRequest) returns(google.protobuf.Empty) {}
|
||||
rpc TriggerOOMReport(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
||||
}
|
||||
|
||||
message SystemProxyStatus {
|
||||
bool available = 1;
|
||||
bool enabled = 2;
|
||||
}
|
||||
|
||||
message SetSystemProxyEnabledRequest {
|
||||
bool enabled = 1;
|
||||
}
|
||||
|
||||
message DebugCrashRequest {
|
||||
enum Type {
|
||||
GO = 0;
|
||||
NATIVE = 1;
|
||||
}
|
||||
|
||||
Type type = 1;
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
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 (
|
||||
ManagedService_StopService_FullMethodName = "/daemon.ManagedService/StopService"
|
||||
ManagedService_ReloadService_FullMethodName = "/daemon.ManagedService/ReloadService"
|
||||
ManagedService_GetSystemProxyStatus_FullMethodName = "/daemon.ManagedService/GetSystemProxyStatus"
|
||||
ManagedService_SetSystemProxyEnabled_FullMethodName = "/daemon.ManagedService/SetSystemProxyEnabled"
|
||||
ManagedService_TriggerDebugCrash_FullMethodName = "/daemon.ManagedService/TriggerDebugCrash"
|
||||
ManagedService_TriggerOOMReport_FullMethodName = "/daemon.ManagedService/TriggerOOMReport"
|
||||
)
|
||||
|
||||
// ManagedServiceClient is the client API for ManagedService 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 ManagedServiceClient 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)
|
||||
GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
TriggerOOMReport(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
}
|
||||
|
||||
type managedServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewManagedServiceClient(cc grpc.ClientConnInterface) ManagedServiceClient {
|
||||
return &managedServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *managedServiceClient) 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, ManagedService_StopService_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *managedServiceClient) 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, ManagedService_ReloadService_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *managedServiceClient) 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, ManagedService_GetSystemProxyStatus_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *managedServiceClient) 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, ManagedService_SetSystemProxyEnabled_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *managedServiceClient) TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, ManagedService_TriggerDebugCrash_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *managedServiceClient) TriggerOOMReport(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, ManagedService_TriggerOOMReport_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ManagedServiceServer is the server API for ManagedService service.
|
||||
// All implementations must embed UnimplementedManagedServiceServer
|
||||
// for forward compatibility.
|
||||
type ManagedServiceServer interface {
|
||||
StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)
|
||||
TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error)
|
||||
TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
mustEmbedUnimplementedManagedServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedManagedServiceServer 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 UnimplementedManagedServiceServer struct{}
|
||||
|
||||
func (UnimplementedManagedServiceServer) StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method StopService not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedManagedServiceServer) ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method ReloadService not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedManagedServiceServer) GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetSystemProxyStatus not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedManagedServiceServer) SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedManagedServiceServer) TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method TriggerDebugCrash not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedManagedServiceServer) TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method TriggerOOMReport not implemented")
|
||||
}
|
||||
func (UnimplementedManagedServiceServer) mustEmbedUnimplementedManagedServiceServer() {}
|
||||
func (UnimplementedManagedServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeManagedServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to ManagedServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeManagedServiceServer interface {
|
||||
mustEmbedUnimplementedManagedServiceServer()
|
||||
}
|
||||
|
||||
func RegisterManagedServiceServer(s grpc.ServiceRegistrar, srv ManagedServiceServer) {
|
||||
// If the following call panics, it indicates UnimplementedManagedServiceServer 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(&ManagedService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _ManagedService_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.(ManagedServiceServer).StopService(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: ManagedService_StopService_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ManagedServiceServer).StopService(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ManagedService_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.(ManagedServiceServer).ReloadService(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: ManagedService_ReloadService_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ManagedServiceServer).ReloadService(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ManagedService_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.(ManagedServiceServer).GetSystemProxyStatus(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: ManagedService_GetSystemProxyStatus_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ManagedServiceServer).GetSystemProxyStatus(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ManagedService_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.(ManagedServiceServer).SetSystemProxyEnabled(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: ManagedService_SetSystemProxyEnabled_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ManagedServiceServer).SetSystemProxyEnabled(ctx, req.(*SetSystemProxyEnabledRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ManagedService_TriggerDebugCrash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DebugCrashRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ManagedServiceServer).TriggerDebugCrash(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: ManagedService_TriggerDebugCrash_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ManagedServiceServer).TriggerDebugCrash(ctx, req.(*DebugCrashRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ManagedService_TriggerOOMReport_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.(ManagedServiceServer).TriggerOOMReport(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: ManagedService_TriggerOOMReport_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ManagedServiceServer).TriggerOOMReport(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// ManagedService_ServiceDesc is the grpc.ServiceDesc for ManagedService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var ManagedService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "daemon.ManagedService",
|
||||
HandlerType: (*ManagedServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "StopService",
|
||||
Handler: _ManagedService_StopService_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ReloadService",
|
||||
Handler: _ManagedService_ReloadService_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetSystemProxyStatus",
|
||||
Handler: _ManagedService_GetSystemProxyStatus_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SetSystemProxyEnabled",
|
||||
Handler: _ManagedService_SetSystemProxyEnabled_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "TriggerDebugCrash",
|
||||
Handler: _ManagedService_TriggerDebugCrash_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "TriggerOOMReport",
|
||||
Handler: _ManagedService_TriggerOOMReport_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "daemon/managed_service.proto",
|
||||
}
|
||||
+9
-2
@@ -1,9 +1,16 @@
|
||||
package daemon
|
||||
|
||||
import "context"
|
||||
|
||||
type PlatformHandler interface {
|
||||
WriteDebugMessage(message string)
|
||||
ConnectSSHAgent() (int32, error)
|
||||
}
|
||||
|
||||
type ManagedHandler interface {
|
||||
ServiceStop() error
|
||||
ServiceReload() error
|
||||
ServiceReload(ctx context.Context) error
|
||||
SystemProxyStatus() (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(enabled bool) error
|
||||
WriteDebugMessage(message string)
|
||||
TriggerNativeCrash() error
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/health"
|
||||
"google.golang.org/grpc/health/grpc_health_v1"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func NewServer(startedService *StartedService, secret string) *grpc.Server {
|
||||
server := grpc.NewServer(
|
||||
grpc.ChainUnaryInterceptor(newUnaryAuthInterceptor(secret), UnaryLocaleInterceptor),
|
||||
grpc.ChainStreamInterceptor(newStreamAuthInterceptor(secret), StreamLocaleInterceptor),
|
||||
)
|
||||
healthServer := health.NewServer()
|
||||
RegisterStartedServiceServer(server, startedService)
|
||||
healthServer.SetServingStatus(StartedService_ServiceDesc.ServiceName, grpc_health_v1.HealthCheckResponse_SERVING)
|
||||
grpc_health_v1.RegisterHealthServer(server, healthServer)
|
||||
reflection.Register(server)
|
||||
return server
|
||||
}
|
||||
|
||||
func newUnaryAuthInterceptor(secret string) grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, request any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
err := authenticate(ctx, secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return handler(ctx, request)
|
||||
}
|
||||
}
|
||||
|
||||
func newStreamAuthInterceptor(secret string) grpc.StreamServerInterceptor {
|
||||
return func(server any, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
err := authenticate(stream.Context(), secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return handler(server, stream)
|
||||
}
|
||||
}
|
||||
|
||||
func authenticate(ctx context.Context, secret string) error {
|
||||
if secret == "" {
|
||||
return nil
|
||||
}
|
||||
md, loaded := metadata.FromIncomingContext(ctx)
|
||||
if !loaded {
|
||||
return status.Error(codes.Unauthenticated, "missing metadata")
|
||||
}
|
||||
values := md.Get("authorization")
|
||||
if len(values) == 0 {
|
||||
return status.Error(codes.Unauthenticated, "missing authorization")
|
||||
}
|
||||
token, isBearer := strings.CutPrefix(values[0], "Bearer ")
|
||||
if !isBearer || token != secret {
|
||||
return status.Error(codes.Unauthenticated, "invalid authorization")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+1260
-198
File diff suppressed because it is too large
Load Diff
+6680
-306
File diff suppressed because it is too large
Load Diff
+611
-20
@@ -6,9 +6,7 @@ option go_package = "github.com/sagernet/sing-box/daemon";
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
service StartedService {
|
||||
rpc StopService(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
rpc ReloadService(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
|
||||
rpc GetVersion(google.protobuf.Empty) returns(Version) {}
|
||||
rpc SubscribeServiceStatus(google.protobuf.Empty) returns(stream ServiceStatus) {}
|
||||
rpc SubscribeLog(google.protobuf.Empty) returns(stream Log) {}
|
||||
rpc GetDefaultLogLevel(google.protobuf.Empty) returns(DefaultLogLevel) {}
|
||||
@@ -24,14 +22,41 @@ service StartedService {
|
||||
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 ConnectionEvents) {}
|
||||
rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}
|
||||
rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
||||
rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {}
|
||||
rpc GetStartedAt(google.protobuf.Empty) returns(StartedAt) {}
|
||||
|
||||
rpc SubscribeOutbounds(google.protobuf.Empty) returns (stream OutboundList) {}
|
||||
rpc StartNetworkQualityTest(NetworkQualityTestRequest) returns (stream NetworkQualityTestProgress) {}
|
||||
rpc StartSTUNTest(STUNTestRequest) returns (stream STUNTestProgress) {}
|
||||
rpc SubscribeTailscaleStatus(google.protobuf.Empty) returns (stream TailscaleStatusUpdate) {}
|
||||
rpc StartTailscalePing(TailscalePingRequest) returns (stream TailscalePingResponse) {}
|
||||
rpc SetTailscaleExitNode(SetTailscaleExitNodeRequest) returns (google.protobuf.Empty) {}
|
||||
rpc TailscaleLogout(TailscaleLogoutRequest) returns (google.protobuf.Empty) {}
|
||||
rpc GetTailscaleCertificate(TailscaleCertificateRequest) returns (TailscaleCertificate) {}
|
||||
rpc StartTailscaleSSHSession(stream TailscaleSSHClientMessage) returns (stream TailscaleSSHServerMessage) {}
|
||||
rpc SubscribeTaildropInbox(SubscribeTaildropInboxRequest) returns (stream TaildropInbox) {}
|
||||
rpc MarkTaildropInboxRead(MarkTaildropInboxReadRequest) returns (google.protobuf.Empty) {}
|
||||
rpc SendTaildropFiles(stream TaildropSendClientMessage) returns (stream TaildropSendServerMessage) {}
|
||||
rpc DownloadTaildropFile(DownloadTaildropFileRequest) returns (stream DownloadTaildropFileChunk) {}
|
||||
rpc DeleteTaildropFile(DeleteTaildropFileRequest) returns (google.protobuf.Empty) {}
|
||||
rpc CancelTaildropReceiving(CancelTaildropReceivingRequest) returns (google.protobuf.Empty) {}
|
||||
rpc ProvideUSBDevices(stream USBProviderMessage) returns (stream USBServerMessage) {}
|
||||
rpc SubscribeUSBIPServerStatus(google.protobuf.Empty) returns (stream USBIPServerStatusUpdate) {}
|
||||
rpc SubscribeOpenConnectStatus(google.protobuf.Empty) returns (stream OpenConnectStatusUpdate) {}
|
||||
rpc SubmitOpenConnectAuthResponse(OpenConnectAuthResponseSubmission) returns (google.protobuf.Empty) {}
|
||||
rpc CancelOpenConnectAuthChallenge(OpenConnectAuthChallengeCancel) returns (google.protobuf.Empty) {}
|
||||
rpc SubscribeOpenVPNStatus(google.protobuf.Empty) returns (stream OpenVPNStatusUpdate) {}
|
||||
rpc SubmitOpenVPNChallengeResponse(OpenVPNChallengeSubmission) returns (google.protobuf.Empty) {}
|
||||
rpc CancelOpenVPNChallenge(OpenVPNChallengeCancel) returns (google.protobuf.Empty) {}
|
||||
rpc SubscribeNotifications(google.protobuf.Empty) returns (stream NotificationEvent) {}
|
||||
}
|
||||
|
||||
message Version {
|
||||
string version = 1;
|
||||
int32 apiVersion = 2;
|
||||
}
|
||||
|
||||
message ServiceStatus {
|
||||
@@ -46,10 +71,6 @@ message ServiceStatus {
|
||||
string errorMessage = 2;
|
||||
}
|
||||
|
||||
message ReloadServiceRequest {
|
||||
string newProfileContent = 1;
|
||||
}
|
||||
|
||||
message SubscribeStatusRequest {
|
||||
int64 interval = 1;
|
||||
}
|
||||
@@ -133,15 +154,6 @@ message ClashModeStatus {
|
||||
string currentMode = 2;
|
||||
}
|
||||
|
||||
message SystemProxyStatus {
|
||||
bool available = 1;
|
||||
bool enabled = 2;
|
||||
}
|
||||
|
||||
message SetSystemProxyEnabledRequest {
|
||||
bool enabled = 1;
|
||||
}
|
||||
|
||||
message SubscribeConnectionsRequest {
|
||||
int64 interval = 1;
|
||||
}
|
||||
@@ -211,8 +223,587 @@ message DeprecatedWarning {
|
||||
string message = 1;
|
||||
bool impending = 2;
|
||||
string migrationLink = 3;
|
||||
string description = 4;
|
||||
string deprecatedVersion = 5;
|
||||
string scheduledVersion = 6;
|
||||
}
|
||||
|
||||
message StartedAt {
|
||||
int64 startedAt = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message OutboundList {
|
||||
repeated GroupItem outbounds = 1;
|
||||
}
|
||||
|
||||
message NetworkQualityTestRequest {
|
||||
string configURL = 1;
|
||||
string outboundTag = 2;
|
||||
bool serial = 3;
|
||||
int32 maxRuntimeSeconds = 4;
|
||||
bool http3 = 5;
|
||||
}
|
||||
|
||||
message NetworkQualityTestProgress {
|
||||
int32 phase = 1;
|
||||
int64 downloadCapacity = 2;
|
||||
int64 uploadCapacity = 3;
|
||||
int32 downloadRPM = 4;
|
||||
int32 uploadRPM = 5;
|
||||
int32 idleLatencyMs = 6;
|
||||
int64 elapsedMs = 7;
|
||||
bool isFinal = 8;
|
||||
string error = 9;
|
||||
int32 downloadCapacityAccuracy = 10;
|
||||
int32 uploadCapacityAccuracy = 11;
|
||||
int32 downloadRPMAccuracy = 12;
|
||||
int32 uploadRPMAccuracy = 13;
|
||||
}
|
||||
|
||||
message STUNTestRequest {
|
||||
string server = 1;
|
||||
string outboundTag = 2;
|
||||
}
|
||||
|
||||
message STUNTestProgress {
|
||||
int32 phase = 1;
|
||||
string externalAddr = 2;
|
||||
int32 latencyMs = 3;
|
||||
int32 natMapping = 4;
|
||||
int32 natFiltering = 5;
|
||||
bool isFinal = 6;
|
||||
string error = 7;
|
||||
bool natTypeSupported = 8;
|
||||
}
|
||||
|
||||
message TailscaleStatusUpdate {
|
||||
repeated TailscaleEndpointStatus endpoints = 1;
|
||||
}
|
||||
|
||||
message TailscaleEndpointStatus {
|
||||
string endpointTag = 1;
|
||||
string backendState = 2;
|
||||
string stateText = 3;
|
||||
string authURL = 4;
|
||||
string networkName = 5;
|
||||
string magicDNSSuffix = 6;
|
||||
TailscalePeer self = 7;
|
||||
repeated TailscaleUserGroup userGroups = 8;
|
||||
TailscalePeer exitNode = 9;
|
||||
bool keyAuth = 10;
|
||||
bool canShareFiles = 11;
|
||||
int32 waitingFileCount = 12;
|
||||
int32 receivingFileCount = 13;
|
||||
int32 unreadFileCount = 14;
|
||||
repeated string certDomains = 15;
|
||||
}
|
||||
|
||||
message TailscaleUserGroup {
|
||||
int64 userID = 1;
|
||||
string loginName = 2;
|
||||
string displayName = 3;
|
||||
string profilePicURL = 4;
|
||||
repeated TailscalePeer peers = 5;
|
||||
}
|
||||
|
||||
message TailscalePeer {
|
||||
string hostName = 1;
|
||||
string dnsName = 2;
|
||||
string os = 3;
|
||||
repeated string tailscaleIPs = 4;
|
||||
bool online = 5;
|
||||
bool exitNode = 6;
|
||||
bool exitNodeOption = 7;
|
||||
bool active = 8;
|
||||
int64 rxBytes = 9;
|
||||
int64 txBytes = 10;
|
||||
int64 keyExpiry = 11;
|
||||
string stableID = 12;
|
||||
bool expired = 13;
|
||||
repeated string sshHostKeys = 14;
|
||||
bool shareeNode = 15;
|
||||
int64 lastSeen = 16;
|
||||
bool canReceiveFiles = 17;
|
||||
}
|
||||
|
||||
message TailscalePingRequest {
|
||||
string endpointTag = 1;
|
||||
string peerIP = 2;
|
||||
}
|
||||
|
||||
message TailscalePingResponse {
|
||||
double latencyMs = 1;
|
||||
bool isDirect = 2;
|
||||
string endpoint = 3;
|
||||
int32 derpRegionID = 4;
|
||||
string derpRegionCode = 5;
|
||||
string error = 6;
|
||||
string peerRelay = 7;
|
||||
}
|
||||
|
||||
message SetTailscaleExitNodeRequest {
|
||||
string endpointTag = 1;
|
||||
string stableID = 2;
|
||||
}
|
||||
|
||||
message TailscaleLogoutRequest {
|
||||
string endpointTag = 1;
|
||||
}
|
||||
|
||||
message TailscaleCertificateRequest {
|
||||
string endpointTag = 1;
|
||||
string domain = 2;
|
||||
int64 minValiditySeconds = 3;
|
||||
}
|
||||
|
||||
message TailscaleCertificate {
|
||||
bytes certificatePEM = 1;
|
||||
bytes privateKeyPEM = 2;
|
||||
}
|
||||
|
||||
message TailscaleSSHClientMessage {
|
||||
oneof message {
|
||||
TailscaleSSHStart start = 1;
|
||||
TailscaleSSHInput input = 2;
|
||||
TailscaleSSHResize resize = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message TailscaleSSHStart {
|
||||
string endpointTag = 1;
|
||||
string peerAddress = 2;
|
||||
string username = 3;
|
||||
string terminalType = 4;
|
||||
int32 columns = 5;
|
||||
int32 rows = 6;
|
||||
int32 widthPixels = 7;
|
||||
int32 heightPixels = 8;
|
||||
repeated string hostKeys = 9;
|
||||
bool forward_agent = 10;
|
||||
}
|
||||
|
||||
message TailscaleSSHInput {
|
||||
bytes data = 1;
|
||||
}
|
||||
|
||||
message TailscaleSSHResize {
|
||||
int32 columns = 1;
|
||||
int32 rows = 2;
|
||||
int32 widthPixels = 3;
|
||||
int32 heightPixels = 4;
|
||||
}
|
||||
|
||||
message TailscaleSSHServerMessage {
|
||||
oneof message {
|
||||
TailscaleSSHAuthBanner authBanner = 1;
|
||||
TailscaleSSHReady ready = 2;
|
||||
TailscaleSSHOutput output = 3;
|
||||
TailscaleSSHExit exit = 4;
|
||||
TailscaleSSHError error = 5;
|
||||
}
|
||||
}
|
||||
|
||||
message TailscaleSSHAuthBanner {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
message TailscaleSSHReady {
|
||||
}
|
||||
|
||||
message TailscaleSSHOutput {
|
||||
bytes data = 1;
|
||||
}
|
||||
|
||||
message TailscaleSSHExit {
|
||||
int32 exitCode = 1;
|
||||
string signal = 2;
|
||||
string errorMessage = 3;
|
||||
}
|
||||
|
||||
message TailscaleSSHError {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
message SubscribeTaildropInboxRequest {
|
||||
string endpointTag = 1;
|
||||
}
|
||||
|
||||
message MarkTaildropInboxReadRequest {
|
||||
string endpointTag = 1;
|
||||
}
|
||||
|
||||
message TaildropInbox {
|
||||
string endpointTag = 1;
|
||||
repeated TaildropFile files = 2;
|
||||
repeated TaildropReceivingFile receiving = 3;
|
||||
}
|
||||
|
||||
message TaildropFile {
|
||||
string name = 1;
|
||||
int64 size = 2;
|
||||
string senderName = 3;
|
||||
int64 modifiedAt = 4;
|
||||
}
|
||||
|
||||
message TaildropReceivingFile {
|
||||
string name = 1;
|
||||
int64 size = 2;
|
||||
int64 receivedBytes = 3;
|
||||
string senderID = 4;
|
||||
string senderName = 5;
|
||||
}
|
||||
|
||||
message TaildropSendClientMessage {
|
||||
oneof message {
|
||||
TaildropSendStart start = 1;
|
||||
TaildropFileChunk chunk = 2;
|
||||
TaildropFileDone fileDone = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message TaildropSendStart {
|
||||
string endpointTag = 1;
|
||||
string peerStableID = 2;
|
||||
repeated TaildropOutgoingFile files = 3;
|
||||
}
|
||||
|
||||
message TaildropOutgoingFile {
|
||||
string name = 1;
|
||||
int64 size = 2;
|
||||
}
|
||||
|
||||
message TaildropFileChunk {
|
||||
bytes data = 1;
|
||||
}
|
||||
|
||||
message TaildropFileDone {}
|
||||
|
||||
message TaildropSendServerMessage {
|
||||
oneof message {
|
||||
TaildropSendProgress progress = 1;
|
||||
int64 receivedBytes = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message TaildropSendProgress {
|
||||
int32 fileIndex = 1;
|
||||
int64 sentBytes = 2;
|
||||
bool fileCompleted = 3;
|
||||
}
|
||||
|
||||
message DownloadTaildropFileRequest {
|
||||
string endpointTag = 1;
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
message DownloadTaildropFileChunk {
|
||||
int64 size = 1;
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
message DeleteTaildropFileRequest {
|
||||
string endpointTag = 1;
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
message CancelTaildropReceivingRequest {
|
||||
string endpointTag = 1;
|
||||
string senderID = 2;
|
||||
string name = 3;
|
||||
}
|
||||
|
||||
message USBProviderMessage {
|
||||
oneof message {
|
||||
USBDeviceAttach attach = 1;
|
||||
USBDeviceDetach detach = 2;
|
||||
USBURBResponse urbResponse = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message USBServerMessage {
|
||||
oneof message {
|
||||
USBDeviceReady ready = 1;
|
||||
USBURBRequest urbRequest = 2;
|
||||
USBEndpointAbort abort = 3;
|
||||
USBError error = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message USBDeviceDescriptor {
|
||||
string deviceId = 1;
|
||||
uint32 busNum = 2;
|
||||
uint32 devNum = 3;
|
||||
uint32 speed = 4;
|
||||
uint32 vendorId = 5;
|
||||
uint32 productId = 6;
|
||||
uint32 bcdDevice = 7;
|
||||
uint32 deviceClass = 8;
|
||||
uint32 deviceSubClass = 9;
|
||||
uint32 deviceProtocol = 10;
|
||||
uint32 configurationValue = 11;
|
||||
uint32 numConfigurations = 12;
|
||||
repeated USBInterface interfaces = 13;
|
||||
string serial = 14;
|
||||
string product = 15;
|
||||
}
|
||||
|
||||
message USBDeviceAttach {
|
||||
string serverTag = 1;
|
||||
USBDeviceDescriptor descriptor = 2;
|
||||
}
|
||||
|
||||
message USBInterface {
|
||||
uint32 interfaceClass = 1;
|
||||
uint32 interfaceSubClass = 2;
|
||||
uint32 interfaceProtocol = 3;
|
||||
}
|
||||
|
||||
message USBDeviceDetach {
|
||||
string deviceId = 1;
|
||||
}
|
||||
|
||||
message USBDeviceReady {
|
||||
string deviceId = 1;
|
||||
string busId = 2;
|
||||
}
|
||||
|
||||
message USBURBRequest {
|
||||
string deviceId = 1;
|
||||
uint64 seq = 2;
|
||||
uint32 endpoint = 3;
|
||||
bool directionIn = 4;
|
||||
uint32 transferFlags = 5;
|
||||
bytes setup = 6;
|
||||
uint32 transferBufferLength = 7;
|
||||
bytes outData = 8;
|
||||
int32 numberOfPackets = 9;
|
||||
int32 startFrame = 10;
|
||||
int32 interval = 11;
|
||||
repeated USBIsoPacket isoPackets = 12;
|
||||
}
|
||||
|
||||
message USBURBResponse {
|
||||
string deviceId = 1;
|
||||
uint64 seq = 2;
|
||||
int32 status = 3;
|
||||
int32 actualLength = 4;
|
||||
bytes inData = 5;
|
||||
repeated USBIsoPacket isoPackets = 6;
|
||||
}
|
||||
|
||||
message USBIsoPacket {
|
||||
int32 offset = 1;
|
||||
int32 length = 2;
|
||||
int32 actualLength = 3;
|
||||
int32 status = 4;
|
||||
}
|
||||
|
||||
message USBEndpointAbort {
|
||||
string deviceId = 1;
|
||||
uint32 endpoint = 2;
|
||||
}
|
||||
|
||||
message USBError {
|
||||
string deviceId = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message USBIPServerStatusUpdate {
|
||||
repeated USBIPServerStatus servers = 1;
|
||||
}
|
||||
|
||||
message USBIPServerStatus {
|
||||
string serverTag = 1;
|
||||
repeated USBSharedDevice devices = 2;
|
||||
}
|
||||
|
||||
message USBSharedDevice {
|
||||
USBDeviceDescriptor descriptor = 1;
|
||||
string busId = 2;
|
||||
string stableId = 3;
|
||||
USBBackend backend = 4;
|
||||
USBDeviceState state = 5;
|
||||
}
|
||||
|
||||
enum USBDeviceState {
|
||||
USB_DEVICE_STATE_IDLE = 0;
|
||||
USB_DEVICE_STATE_ATTACHED = 1;
|
||||
USB_DEVICE_STATE_UNAVAILABLE = 2;
|
||||
}
|
||||
|
||||
enum USBBackend {
|
||||
USB_BACKEND_UNSPECIFIED = 0;
|
||||
USB_BACKEND_LINUX_SYSFS = 1;
|
||||
USB_BACKEND_DYNAMIC = 2;
|
||||
USB_BACKEND_DARWIN_IOKIT = 3;
|
||||
USB_BACKEND_WINDOWS_VBOXUSB = 4;
|
||||
}
|
||||
|
||||
message OpenConnectStatusUpdate {
|
||||
repeated OpenConnectEndpointStatus endpoints = 1;
|
||||
}
|
||||
|
||||
message OpenConnectEndpointStatus {
|
||||
string endpointTag = 1;
|
||||
string state = 2;
|
||||
string stateText = 3;
|
||||
OpenConnectAuthChallenge authChallenge = 4;
|
||||
string error = 5;
|
||||
OpenConnectTunnelInfo tunnelInfo = 6;
|
||||
}
|
||||
|
||||
message OpenConnectTunnelInfo {
|
||||
string server = 1;
|
||||
string flavor = 2;
|
||||
string transport = 3;
|
||||
repeated string ipv4 = 4;
|
||||
repeated string ipv6 = 5;
|
||||
repeated string dns = 6;
|
||||
uint32 mtu = 7;
|
||||
int64 connectedSince = 8;
|
||||
}
|
||||
|
||||
message OpenConnectAuthChallenge {
|
||||
string id = 1;
|
||||
string banner = 2;
|
||||
string message = 3;
|
||||
string error = 4;
|
||||
oneof challenge {
|
||||
OpenConnectAuthForm form = 5;
|
||||
OpenConnectBrowserRequest browser = 6;
|
||||
}
|
||||
}
|
||||
|
||||
message OpenConnectAuthForm {
|
||||
repeated OpenConnectAuthFormField fields = 1;
|
||||
}
|
||||
|
||||
message OpenConnectAuthFormField {
|
||||
string submissionKey = 1;
|
||||
string name = 2;
|
||||
string label = 3;
|
||||
string kind = 4;
|
||||
string value = 5;
|
||||
repeated OpenConnectAuthFormChoice options = 6;
|
||||
}
|
||||
|
||||
message OpenConnectAuthFormChoice {
|
||||
string value = 1;
|
||||
string label = 2;
|
||||
}
|
||||
|
||||
message OpenConnectBrowserRequest {
|
||||
string url = 1;
|
||||
string finalURL = 2;
|
||||
repeated string cookieNames = 3;
|
||||
repeated string headerNames = 4;
|
||||
repeated string callbackURLPrefixes = 5;
|
||||
repeated string earlyCookieNames = 6;
|
||||
string cacheID = 7;
|
||||
}
|
||||
|
||||
message OpenConnectBrowserCookie {
|
||||
string name = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message OpenConnectBrowserHeader {
|
||||
string name = 1;
|
||||
repeated string values = 2;
|
||||
}
|
||||
|
||||
message OpenConnectAuthFormResponse {
|
||||
map<string, string> values = 1;
|
||||
}
|
||||
|
||||
message OpenConnectBrowserResult {
|
||||
string finalURL = 1;
|
||||
repeated OpenConnectBrowserCookie cookies = 2;
|
||||
repeated OpenConnectBrowserHeader headers = 3;
|
||||
}
|
||||
|
||||
message OpenConnectAuthResponseSubmission {
|
||||
string endpointTag = 1;
|
||||
string challengeID = 2;
|
||||
oneof response {
|
||||
OpenConnectAuthFormResponse form = 3;
|
||||
OpenConnectBrowserResult browser = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message OpenConnectAuthChallengeCancel {
|
||||
string endpointTag = 1;
|
||||
string challengeID = 2;
|
||||
}
|
||||
|
||||
message OpenVPNStatusUpdate {
|
||||
repeated OpenVPNEndpointStatus endpoints = 1;
|
||||
}
|
||||
|
||||
message OpenVPNEndpointStatus {
|
||||
string endpointTag = 1;
|
||||
string state = 2;
|
||||
string stateText = 3;
|
||||
OpenVPNChallenge challenge = 4;
|
||||
string error = 5;
|
||||
OpenVPNTunnelInfo tunnelInfo = 6;
|
||||
}
|
||||
|
||||
message OpenVPNTunnelInfo {
|
||||
string server = 1;
|
||||
reserved 2;
|
||||
string network = 3;
|
||||
repeated string ipv4 = 4;
|
||||
repeated string ipv6 = 5;
|
||||
repeated string dns = 6;
|
||||
uint32 mtu = 7;
|
||||
int64 connectedSince = 8;
|
||||
string cipher = 9;
|
||||
}
|
||||
|
||||
message OpenVPNChallenge {
|
||||
string id = 1;
|
||||
string kind = 2;
|
||||
string username = 3;
|
||||
string message = 4;
|
||||
string url = 5;
|
||||
string secretMessage = 6;
|
||||
bool echo = 7;
|
||||
string previousError = 8;
|
||||
int64 deadline = 9;
|
||||
}
|
||||
|
||||
message OpenVPNChallengeSubmission {
|
||||
string endpointTag = 1;
|
||||
string challengeID = 2;
|
||||
string username = 3;
|
||||
string password = 4;
|
||||
string secret = 5;
|
||||
}
|
||||
|
||||
message OpenVPNChallengeCancel {
|
||||
string endpointTag = 1;
|
||||
string challengeID = 2;
|
||||
}
|
||||
|
||||
message NotificationEvent {
|
||||
oneof event {
|
||||
Notification send = 1;
|
||||
NotificationCancel cancel = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message Notification {
|
||||
string identifier = 1;
|
||||
string typeName = 2;
|
||||
int32 typeID = 3;
|
||||
string title = 4;
|
||||
string subtitle = 5;
|
||||
string body = 6;
|
||||
string openURL = 7;
|
||||
}
|
||||
|
||||
message NotificationCancel {
|
||||
string identifier = 1;
|
||||
int32 typeID = 2;
|
||||
}
|
||||
|
||||
+982
-148
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,394 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
const (
|
||||
// gRPC splits DATA frames at the unexported transport.http2MaxFrameLen (16384),
|
||||
// counting the 5-byte message prefix. A chunk message that stays inside one frame
|
||||
// arrives as a single mem.Buffer, so BufferSlice.MaterializeToBuffer returns it
|
||||
// without taking a pooled buffer, and codecV2.Marshal fits the 16 KiB pool tier.
|
||||
TaildropChunkSize = 16*1024 - 64
|
||||
|
||||
TaildropProgressMinInterval = 200 * time.Millisecond
|
||||
|
||||
// Kept well below the sender's flow control window, so a sender that stops at
|
||||
// the window edge always has an acknowledgement pending.
|
||||
TaildropReceiveAckInterval = 1 << 20
|
||||
)
|
||||
|
||||
func resolveTailscaleProvider(instance *Instance, tag string) (adapter.TailscaleEndpoint, string, error) {
|
||||
if tag != "" {
|
||||
endpoint, err := resolveTailscaleEndpoint(instance, tag)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
provider, loaded := endpoint.(adapter.TailscaleEndpoint)
|
||||
if !loaded {
|
||||
return nil, "", status.Error(codes.FailedPrecondition, "endpoint does not support tailscale")
|
||||
}
|
||||
return provider, endpoint.Tag(), nil
|
||||
}
|
||||
endpointManager := service.FromContext[adapter.EndpointManager](instance.ctx)
|
||||
if endpointManager == nil {
|
||||
return nil, "", status.Error(codes.FailedPrecondition, "endpoint manager not available")
|
||||
}
|
||||
for _, endpoint := range endpointManager.Endpoints() {
|
||||
if endpoint.Type() != C.TypeTailscale {
|
||||
continue
|
||||
}
|
||||
provider, loaded := endpoint.(adapter.TailscaleEndpoint)
|
||||
if loaded {
|
||||
return provider, endpoint.Tag(), nil
|
||||
}
|
||||
}
|
||||
return nil, "", status.Error(codes.NotFound, "no Tailscale endpoint found")
|
||||
}
|
||||
|
||||
func taildropError(err error) error {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return status.Error(codes.NotFound, err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeTaildropInbox(
|
||||
request *SubscribeTaildropInboxRequest,
|
||||
server grpc.ServerStreamingServer[TaildropInbox],
|
||||
) error {
|
||||
err := s.waitForStarted(server.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.followInstance(server.Context(), func(ctx context.Context, instance *Instance) error {
|
||||
var (
|
||||
provider adapter.TailscaleEndpoint
|
||||
endpointTag string
|
||||
)
|
||||
if instance != nil {
|
||||
var resolveErr error
|
||||
provider, endpointTag, resolveErr = resolveTailscaleProvider(instance, request.EndpointTag)
|
||||
if resolveErr != nil && status.Code(resolveErr) != codes.NotFound {
|
||||
return resolveErr
|
||||
}
|
||||
}
|
||||
if provider == nil {
|
||||
sendErr := server.Send(&TaildropInbox{})
|
||||
if sendErr != nil {
|
||||
return sendErr
|
||||
}
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
subscribeCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
var sendErr error
|
||||
subscribeErr := provider.SubscribeTaildropInbox(subscribeCtx, func(inbox *adapter.TaildropInbox) {
|
||||
if sendErr != nil {
|
||||
return
|
||||
}
|
||||
sendErr = server.Send(taildropInboxToProto(endpointTag, inbox))
|
||||
if sendErr != nil {
|
||||
cancel()
|
||||
}
|
||||
})
|
||||
if sendErr != nil {
|
||||
return sendErr
|
||||
}
|
||||
if subscribeErr != nil && !errors.Is(subscribeErr, context.Canceled) {
|
||||
if !errors.Is(subscribeErr, os.ErrClosed) {
|
||||
return taildropError(subscribeErr)
|
||||
}
|
||||
sendErr = server.Send(&TaildropInbox{EndpointTag: endpointTag})
|
||||
if sendErr != nil {
|
||||
return sendErr
|
||||
}
|
||||
}
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func taildropInboxToProto(endpointTag string, inbox *adapter.TaildropInbox) *TaildropInbox {
|
||||
result := &TaildropInbox{EndpointTag: endpointTag}
|
||||
for _, file := range inbox.Files {
|
||||
result.Files = append(result.Files, &TaildropFile{
|
||||
Name: file.Name,
|
||||
Size: file.Size,
|
||||
SenderName: file.SenderName,
|
||||
ModifiedAt: file.ModifiedAt,
|
||||
})
|
||||
}
|
||||
for _, file := range inbox.Receiving {
|
||||
result.Receiving = append(result.Receiving, &TaildropReceivingFile{
|
||||
Name: file.Name,
|
||||
Size: file.Size,
|
||||
ReceivedBytes: file.ReceivedBytes,
|
||||
SenderID: file.SenderID,
|
||||
SenderName: file.SenderName,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *StartedService) MarkTaildropInboxRead(ctx context.Context, request *MarkTaildropInboxReadRequest) (*emptypb.Empty, error) {
|
||||
err := s.waitForStarted(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.serviceAccess.RLock()
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
|
||||
provider, _, err := resolveTailscaleProvider(boxService, request.EndpointTag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = provider.MarkTaildropInboxRead()
|
||||
if err != nil {
|
||||
return nil, taildropError(err)
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) SendTaildropFiles(
|
||||
server grpc.BidiStreamingServer[TaildropSendClientMessage, TaildropSendServerMessage],
|
||||
) error {
|
||||
streamCtx := server.Context()
|
||||
err := s.waitForStarted(streamCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.serviceAccess.RLock()
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
|
||||
firstMessage, err := server.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start := firstMessage.GetStart()
|
||||
if start == nil {
|
||||
return status.Error(codes.InvalidArgument, "expected start message")
|
||||
}
|
||||
if len(start.Files) == 0 {
|
||||
return status.Error(codes.InvalidArgument, "no files to send")
|
||||
}
|
||||
provider, _, err := resolveTailscaleProvider(boxService, start.EndpointTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var sendAccess sync.Mutex
|
||||
sendMessage := func(message *TaildropSendServerMessage) {
|
||||
sendAccess.Lock()
|
||||
defer sendAccess.Unlock()
|
||||
_ = server.Send(message)
|
||||
}
|
||||
|
||||
chunkSource := &taildropChunkReader{server: server}
|
||||
chunkSource.reportReceived = func(receivedBytes int64) {
|
||||
sendMessage(&TaildropSendServerMessage{
|
||||
Message: &TaildropSendServerMessage_ReceivedBytes{ReceivedBytes: receivedBytes},
|
||||
})
|
||||
}
|
||||
for index, file := range start.Files {
|
||||
fileIndex := int32(index)
|
||||
chunkSource.beginFile()
|
||||
var lastProgress time.Time
|
||||
err = provider.SendTaildropFile(streamCtx, start.PeerStableID, file.Name, file.Size, chunkSource, func(sentBytes int64) {
|
||||
now := time.Now()
|
||||
if now.Sub(lastProgress) < TaildropProgressMinInterval {
|
||||
return
|
||||
}
|
||||
lastProgress = now
|
||||
sendMessage(&TaildropSendServerMessage{
|
||||
Message: &TaildropSendServerMessage_Progress{
|
||||
Progress: &TaildropSendProgress{FileIndex: fileIndex, SentBytes: sentBytes},
|
||||
},
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return taildropError(err)
|
||||
}
|
||||
err = chunkSource.finishFile(file.Size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sendMessage(&TaildropSendServerMessage{
|
||||
Message: &TaildropSendServerMessage_Progress{
|
||||
Progress: &TaildropSendProgress{FileIndex: fileIndex, SentBytes: chunkSource.fileReceived, FileCompleted: true},
|
||||
},
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type taildropChunkReader struct {
|
||||
server grpc.BidiStreamingServer[TaildropSendClientMessage, TaildropSendServerMessage]
|
||||
buffer []byte
|
||||
fileDone bool
|
||||
fileReceived int64
|
||||
reportReceived func(receivedBytes int64)
|
||||
received int64
|
||||
acknowledged int64
|
||||
}
|
||||
|
||||
func (r *taildropChunkReader) beginFile() {
|
||||
r.fileDone = false
|
||||
r.fileReceived = 0
|
||||
}
|
||||
|
||||
func (r *taildropChunkReader) Read(destination []byte) (int, error) {
|
||||
for len(r.buffer) == 0 {
|
||||
if r.fileDone {
|
||||
return 0, io.EOF
|
||||
}
|
||||
message, err := r.server.Recv()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch content := message.Message.(type) {
|
||||
case *TaildropSendClientMessage_Chunk:
|
||||
r.buffer = content.Chunk.Data
|
||||
case *TaildropSendClientMessage_FileDone:
|
||||
r.fileDone = true
|
||||
default:
|
||||
return 0, status.Error(codes.InvalidArgument, "expected chunk message")
|
||||
}
|
||||
}
|
||||
n := copy(destination, r.buffer)
|
||||
r.buffer = r.buffer[n:]
|
||||
r.fileReceived += int64(n)
|
||||
r.received += int64(n)
|
||||
if r.reportReceived != nil && r.received-r.acknowledged >= TaildropReceiveAckInterval {
|
||||
r.acknowledged = r.received
|
||||
r.reportReceived(r.received)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r *taildropChunkReader) finishFile(declaredSize int64) error {
|
||||
for !r.fileDone {
|
||||
if len(r.buffer) > 0 {
|
||||
return status.Error(codes.InvalidArgument, "file changed while sending")
|
||||
}
|
||||
message, err := r.server.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch content := message.Message.(type) {
|
||||
case *TaildropSendClientMessage_Chunk:
|
||||
r.buffer = content.Chunk.Data
|
||||
case *TaildropSendClientMessage_FileDone:
|
||||
r.fileDone = true
|
||||
default:
|
||||
return status.Error(codes.InvalidArgument, "expected chunk message")
|
||||
}
|
||||
}
|
||||
if len(r.buffer) > 0 {
|
||||
return status.Error(codes.InvalidArgument, "file changed while sending")
|
||||
}
|
||||
if declaredSize >= 0 && r.fileReceived != declaredSize {
|
||||
return status.Error(codes.InvalidArgument, "file changed while sending")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StartedService) DownloadTaildropFile(
|
||||
request *DownloadTaildropFileRequest,
|
||||
server grpc.ServerStreamingServer[DownloadTaildropFileChunk],
|
||||
) error {
|
||||
err := s.waitForStarted(server.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.serviceAccess.RLock()
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
|
||||
provider, _, err := resolveTailscaleProvider(boxService, request.EndpointTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, size, err := provider.OpenTaildropFile(request.Name)
|
||||
if err != nil {
|
||||
return taildropError(err)
|
||||
}
|
||||
defer file.Close()
|
||||
err = server.Send(&DownloadTaildropFileChunk{Size: size})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := make([]byte, TaildropChunkSize)
|
||||
for {
|
||||
n, readErr := file.Read(buffer)
|
||||
if n > 0 {
|
||||
err = server.Send(&DownloadTaildropFileChunk{Data: buffer[:n]})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if readErr == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StartedService) DeleteTaildropFile(ctx context.Context, request *DeleteTaildropFileRequest) (*emptypb.Empty, error) {
|
||||
err := s.waitForStarted(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.serviceAccess.RLock()
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
|
||||
provider, _, err := resolveTailscaleProvider(boxService, request.EndpointTag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = provider.DeleteTaildropFile(request.Name)
|
||||
if err != nil {
|
||||
return nil, taildropError(err)
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) CancelTaildropReceiving(ctx context.Context, request *CancelTaildropReceivingRequest) (*emptypb.Empty, error) {
|
||||
err := s.waitForStarted(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.serviceAccess.RLock()
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
|
||||
provider, _, err := resolveTailscaleProvider(boxService, request.EndpointTag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = provider.CancelTaildropReceiving(request.SenderID, request.Name)
|
||||
if err != nil {
|
||||
return nil, taildropError(err)
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type windowChangeRequest struct {
|
||||
Columns uint32
|
||||
Rows uint32
|
||||
WidthPixels uint32
|
||||
HeightPixels uint32
|
||||
}
|
||||
|
||||
func (s *StartedService) StartTailscaleSSHSession(
|
||||
server grpc.BidiStreamingServer[TailscaleSSHClientMessage, TailscaleSSHServerMessage],
|
||||
) error {
|
||||
ctx := server.Context()
|
||||
err := s.waitForStarted(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.serviceAccess.RLock()
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
|
||||
firstMessage, err := server.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start := firstMessage.GetStart()
|
||||
|
||||
hostKeys := make([]ssh.PublicKey, 0, len(start.HostKeys))
|
||||
for _, line := range start.HostKeys {
|
||||
key, _, _, _, parseErr := ssh.ParseAuthorizedKey([]byte(line))
|
||||
if parseErr != nil {
|
||||
return E.Cause(parseErr, "parse host key")
|
||||
}
|
||||
hostKeys = append(hostKeys, key)
|
||||
}
|
||||
|
||||
endpoint, err := resolveTailscaleEndpoint(boxService, start.EndpointTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerAddr := M.ParseSocksaddrHostPort(start.PeerAddress, 22)
|
||||
|
||||
sessionCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
var sendAccess sync.Mutex
|
||||
sendMessage := func(msg *TailscaleSSHServerMessage) {
|
||||
sendAccess.Lock()
|
||||
defer sendAccess.Unlock()
|
||||
sendErr := server.Send(msg)
|
||||
if sendErr != nil {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
finishWithError := func(message string) error {
|
||||
sendMessage(&TailscaleSSHServerMessage{
|
||||
Message: &TailscaleSSHServerMessage_Error{Error: &TailscaleSSHError{Message: message}},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
peerConn, err := endpoint.DialContext(ctx, N.NetworkTCP, peerAddr)
|
||||
if err != nil {
|
||||
return finishWithError(E.Cause(err, "dial peer").Error())
|
||||
}
|
||||
|
||||
var lastBanner string
|
||||
config := &ssh.ClientConfig{
|
||||
User: start.Username,
|
||||
Auth: nil,
|
||||
BannerCallback: func(message string) error {
|
||||
lastBanner = message
|
||||
sendMessage(&TailscaleSSHServerMessage{
|
||||
Message: &TailscaleSSHServerMessage_AuthBanner{
|
||||
AuthBanner: &TailscaleSSHAuthBanner{Message: message},
|
||||
},
|
||||
})
|
||||
return nil
|
||||
},
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
serverKey := key.Marshal()
|
||||
for _, hostKey := range hostKeys {
|
||||
if bytes.Equal(serverKey, hostKey.Marshal()) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return E.New("untrusted host key: ", key.Type())
|
||||
},
|
||||
}
|
||||
sshConn, chans, reqs, err := ssh.NewClientConn(peerConn, peerAddr.String(), config)
|
||||
if err != nil {
|
||||
common.Close(peerConn)
|
||||
banner := strings.TrimSpace(lastBanner)
|
||||
if banner != "" {
|
||||
return finishWithError(banner)
|
||||
}
|
||||
return finishWithError(E.Cause(err, "ssh handshake").Error())
|
||||
}
|
||||
sshClient := ssh.NewClient(sshConn, chans, reqs)
|
||||
|
||||
if start.ForwardAgent && s.handler != nil {
|
||||
agentChannels := sshClient.HandleChannelOpen("auth-agent@openssh.com")
|
||||
if agentChannels != nil {
|
||||
go func() {
|
||||
for newChannel := range agentChannels {
|
||||
channel, reqs, acceptErr := newChannel.Accept()
|
||||
if acceptErr != nil {
|
||||
continue
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
go s.forwardSSHAgentChannel(channel)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
sshSession, err := sshClient.NewSession()
|
||||
if err != nil {
|
||||
common.Close(sshClient)
|
||||
return finishWithError(E.Cause(err, "open ssh session").Error())
|
||||
}
|
||||
|
||||
cols := int(start.Columns)
|
||||
rows := int(start.Rows)
|
||||
err = sshSession.RequestPty(start.TerminalType, rows, cols, ssh.TerminalModes{
|
||||
ssh.ECHO: 1,
|
||||
ssh.ECHOE: 1,
|
||||
ssh.ECHOK: 1,
|
||||
ssh.ECHOKE: 1,
|
||||
ssh.ECHOCTL: 1,
|
||||
ssh.ICANON: 1,
|
||||
ssh.ISIG: 1,
|
||||
ssh.IEXTEN: 1,
|
||||
ssh.ICRNL: 1,
|
||||
ssh.IXON: 1,
|
||||
ssh.IXANY: 1,
|
||||
ssh.IMAXBEL: 1,
|
||||
ssh.OPOST: 1,
|
||||
ssh.ONLCR: 1,
|
||||
ssh.TTY_OP_ISPEED: 14400,
|
||||
ssh.TTY_OP_OSPEED: 14400,
|
||||
})
|
||||
if err != nil {
|
||||
common.Close(sshSession, sshClient)
|
||||
return finishWithError(E.Cause(err, "request pty").Error())
|
||||
}
|
||||
if start.WidthPixels > 0 || start.HeightPixels > 0 {
|
||||
_, _ = sshSession.SendRequest("window-change", false, ssh.Marshal(&windowChangeRequest{
|
||||
Columns: uint32(start.Columns),
|
||||
Rows: uint32(start.Rows),
|
||||
WidthPixels: uint32(start.WidthPixels),
|
||||
HeightPixels: uint32(start.HeightPixels),
|
||||
}))
|
||||
}
|
||||
|
||||
if start.ForwardAgent && s.handler != nil {
|
||||
err = agent.RequestAgentForwarding(sshSession)
|
||||
if err != nil {
|
||||
common.Close(sshSession, sshClient)
|
||||
return finishWithError(E.Cause(err, "request agent forwarding").Error())
|
||||
}
|
||||
}
|
||||
|
||||
stdin, err := sshSession.StdinPipe()
|
||||
if err != nil {
|
||||
common.Close(sshSession, sshClient)
|
||||
return finishWithError(E.Cause(err, "stdin pipe").Error())
|
||||
}
|
||||
stdout, err := sshSession.StdoutPipe()
|
||||
if err != nil {
|
||||
common.Close(sshSession, sshClient)
|
||||
return finishWithError(E.Cause(err, "stdout pipe").Error())
|
||||
}
|
||||
stderr, err := sshSession.StderrPipe()
|
||||
if err != nil {
|
||||
common.Close(sshSession, sshClient)
|
||||
return finishWithError(E.Cause(err, "stderr pipe").Error())
|
||||
}
|
||||
err = sshSession.Shell()
|
||||
if err != nil {
|
||||
common.Close(sshSession, sshClient)
|
||||
return finishWithError(E.Cause(err, "start shell").Error())
|
||||
}
|
||||
|
||||
var workersWg sync.WaitGroup
|
||||
|
||||
sendMessage(&TailscaleSSHServerMessage{
|
||||
Message: &TailscaleSSHServerMessage_Ready{Ready: &TailscaleSSHReady{}},
|
||||
})
|
||||
|
||||
workersWg.Go(func() {
|
||||
for {
|
||||
msg, recvErr := server.Recv()
|
||||
if recvErr == io.EOF {
|
||||
stdin.Close()
|
||||
return
|
||||
}
|
||||
if recvErr != nil {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
switch m := msg.GetMessage().(type) {
|
||||
case *TailscaleSSHClientMessage_Input:
|
||||
if len(m.Input.Data) == 0 {
|
||||
continue
|
||||
}
|
||||
_, writeErr := stdin.Write(m.Input.Data)
|
||||
if writeErr != nil {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
case *TailscaleSSHClientMessage_Resize:
|
||||
_, _ = sshSession.SendRequest("window-change", false, ssh.Marshal(&windowChangeRequest{
|
||||
Columns: uint32(m.Resize.Columns),
|
||||
Rows: uint32(m.Resize.Rows),
|
||||
WidthPixels: uint32(m.Resize.WidthPixels),
|
||||
HeightPixels: uint32(m.Resize.HeightPixels),
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
pumpReader := func(reader io.Reader) {
|
||||
buffer := buf.Get(buf.BufferSize)
|
||||
defer buf.Put(buffer)
|
||||
for {
|
||||
n, readErr := reader.Read(buffer)
|
||||
if n > 0 {
|
||||
sendMessage(&TailscaleSSHServerMessage{
|
||||
Message: &TailscaleSSHServerMessage_Output{Output: &TailscaleSSHOutput{Data: bytes.Clone(buffer[:n])}},
|
||||
})
|
||||
}
|
||||
if readErr != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
workersWg.Go(func() { pumpReader(stdout) })
|
||||
workersWg.Go(func() { pumpReader(stderr) })
|
||||
|
||||
workersWg.Go(func() {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-sessionCtx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
_, _, keepAliveErr := sshConn.SendRequest("keepalive@openssh.com", true, nil)
|
||||
if keepAliveErr != nil {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
workersWg.Go(func() {
|
||||
waitErr := sshSession.Wait()
|
||||
exitMessage := &TailscaleSSHExit{}
|
||||
switch waitErrTyped := waitErr.(type) {
|
||||
case nil:
|
||||
case *ssh.ExitError:
|
||||
exitMessage.ExitCode = int32(waitErrTyped.ExitStatus())
|
||||
exitMessage.Signal = waitErrTyped.Signal()
|
||||
default:
|
||||
exitMessage.ErrorMessage = waitErrTyped.Error()
|
||||
}
|
||||
sendMessage(&TailscaleSSHServerMessage{
|
||||
Message: &TailscaleSSHServerMessage_Exit{Exit: exitMessage},
|
||||
})
|
||||
cancel()
|
||||
})
|
||||
|
||||
go func() {
|
||||
<-sessionCtx.Done()
|
||||
common.Close(peerConn, sshSession, sshClient)
|
||||
}()
|
||||
|
||||
workersWg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StartedService) forwardSSHAgentChannel(channel ssh.Channel) {
|
||||
defer channel.Close()
|
||||
fd, err := s.handler.ConnectSSHAgent()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
file := os.NewFile(uintptr(fd), "ssh-agent")
|
||||
conn, err := net.FileConn(file)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
io.Copy(conn, channel)
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
io.Copy(channel, conn)
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
//go:build with_usbip && (linux || (darwin && cgo) || windows)
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-usbip"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
func (s *StartedService) ProvideUSBDevices(server grpc.BidiStreamingServer[USBProviderMessage, USBServerMessage]) error {
|
||||
ctx := server.Context()
|
||||
err := s.waitForStarted(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.serviceAccess.RLock()
|
||||
instance := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
if instance == nil {
|
||||
return nil
|
||||
}
|
||||
serviceManager := service.FromContext[adapter.ServiceManager](instance.ctx)
|
||||
if serviceManager == nil {
|
||||
return E.New("missing service manager")
|
||||
}
|
||||
|
||||
sessionCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
var sendAccess sync.Mutex
|
||||
send := func(message *USBServerMessage) error {
|
||||
sendAccess.Lock()
|
||||
defer sendAccess.Unlock()
|
||||
return server.Send(message)
|
||||
}
|
||||
|
||||
var devicesAccess sync.Mutex
|
||||
devices := make(map[string]*usbProvidedDevice)
|
||||
defer func() {
|
||||
devicesAccess.Lock()
|
||||
for _, device := range devices {
|
||||
device.close()
|
||||
}
|
||||
devicesAccess.Unlock()
|
||||
}()
|
||||
|
||||
for {
|
||||
message, recvErr := server.Recv()
|
||||
if recvErr != nil {
|
||||
if recvErr == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return recvErr
|
||||
}
|
||||
switch body := message.GetMessage().(type) {
|
||||
case *USBProviderMessage_Attach:
|
||||
attach := body.Attach
|
||||
deviceID := attach.GetDescriptor_().GetDeviceId()
|
||||
device, addErr := addUSBDevice(sessionCtx, serviceManager, send, attach)
|
||||
if addErr != nil {
|
||||
_ = send(&USBServerMessage{Message: &USBServerMessage_Error{Error: &USBError{
|
||||
DeviceId: deviceID,
|
||||
Message: addErr.Error(),
|
||||
}}})
|
||||
continue
|
||||
}
|
||||
devicesAccess.Lock()
|
||||
previous, replaced := devices[deviceID]
|
||||
devices[deviceID] = device
|
||||
devicesAccess.Unlock()
|
||||
if replaced {
|
||||
previous.close()
|
||||
}
|
||||
_ = send(&USBServerMessage{Message: &USBServerMessage_Ready{Ready: &USBDeviceReady{
|
||||
DeviceId: deviceID,
|
||||
BusId: device.busID,
|
||||
}}})
|
||||
case *USBProviderMessage_Detach:
|
||||
deviceID := body.Detach.GetDeviceId()
|
||||
devicesAccess.Lock()
|
||||
device, found := devices[deviceID]
|
||||
if found {
|
||||
delete(devices, deviceID)
|
||||
}
|
||||
devicesAccess.Unlock()
|
||||
if found {
|
||||
device.close()
|
||||
}
|
||||
case *USBProviderMessage_UrbResponse:
|
||||
response := body.UrbResponse
|
||||
devicesAccess.Lock()
|
||||
device, found := devices[response.GetDeviceId()]
|
||||
devicesAccess.Unlock()
|
||||
if found {
|
||||
device.deliver(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeUSBIPServerStatus(
|
||||
_ *emptypb.Empty,
|
||||
server grpc.ServerStreamingServer[USBIPServerStatusUpdate],
|
||||
) error {
|
||||
err := s.waitForStarted(server.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.followInstance(server.Context(), func(ctx context.Context, instance *Instance) error {
|
||||
type usbipServer struct {
|
||||
tag string
|
||||
provider adapter.USBIPDynamicServer
|
||||
}
|
||||
var servers []usbipServer
|
||||
if instance != nil {
|
||||
serviceManager := service.FromContext[adapter.ServiceManager](instance.ctx)
|
||||
for _, serverService := range serviceManager.Services() {
|
||||
provider, isDynamic := serverService.(adapter.USBIPDynamicServer)
|
||||
if !isDynamic {
|
||||
continue
|
||||
}
|
||||
servers = append(servers, usbipServer{tag: serverService.Tag(), provider: provider})
|
||||
}
|
||||
}
|
||||
// sing-usbip invokes the SubscribeDevices listener with the current snapshot
|
||||
// before registration returns, and later invocations happen while the ledger's
|
||||
// broadcast lock is held, so the listener must never block.
|
||||
sources := common.Map(servers, func(serverService usbipServer) taggedStatusSource[[]usbip.ControlDeviceInfo] {
|
||||
return taggedStatusSource[[]usbip.ControlDeviceInfo]{
|
||||
tag: serverService.tag,
|
||||
subscribe: func(subscribeCtx context.Context, listener func([]usbip.ControlDeviceInfo)) {
|
||||
serverService.provider.SubscribeDevices(subscribeCtx, listener)
|
||||
},
|
||||
}
|
||||
})
|
||||
return streamTaggedStatus(ctx, sources, func(deviceStates map[string][]usbip.ControlDeviceInfo) error {
|
||||
protoServers := make([]*USBIPServerStatus, 0, len(servers))
|
||||
for _, serverService := range servers {
|
||||
devices, found := deviceStates[serverService.tag]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
protoServers = append(protoServers, &USBIPServerStatus{
|
||||
ServerTag: serverService.tag,
|
||||
Devices: usbSharedDevicesToProto(devices),
|
||||
})
|
||||
}
|
||||
return server.Send(&USBIPServerStatusUpdate{Servers: protoServers})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func usbSharedDevicesToProto(devices []usbip.ControlDeviceInfo) []*USBSharedDevice {
|
||||
if len(devices) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]*USBSharedDevice, 0, len(devices))
|
||||
for _, device := range devices {
|
||||
interfaces := make([]*USBInterface, 0, len(device.Interfaces))
|
||||
for _, deviceInterface := range device.Interfaces {
|
||||
interfaces = append(interfaces, &USBInterface{
|
||||
InterfaceClass: uint32(deviceInterface.Class),
|
||||
InterfaceSubClass: uint32(deviceInterface.SubClass),
|
||||
InterfaceProtocol: uint32(deviceInterface.Protocol),
|
||||
})
|
||||
}
|
||||
out = append(out, &USBSharedDevice{
|
||||
Descriptor_: &USBDeviceDescriptor{
|
||||
DeviceId: device.BusID,
|
||||
BusNum: device.BusNum,
|
||||
DevNum: device.DevNum,
|
||||
Speed: device.Speed,
|
||||
VendorId: uint32(device.VendorID),
|
||||
ProductId: uint32(device.ProductID),
|
||||
BcdDevice: uint32(device.BCDDevice),
|
||||
DeviceClass: uint32(device.DeviceClass),
|
||||
DeviceSubClass: uint32(device.DeviceSubClass),
|
||||
DeviceProtocol: uint32(device.DeviceProtocol),
|
||||
ConfigurationValue: uint32(device.ConfigurationValue),
|
||||
NumConfigurations: uint32(device.NumConfigurations),
|
||||
Interfaces: interfaces,
|
||||
Serial: device.Serial,
|
||||
Product: device.Product,
|
||||
},
|
||||
BusId: device.BusID,
|
||||
StableId: device.StableID,
|
||||
Backend: USBBackend(device.Backend),
|
||||
State: USBDeviceState(device.State),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func addUSBDevice(ctx context.Context, serviceManager adapter.ServiceManager, send func(*USBServerMessage) error, attach *USBDeviceAttach) (*usbProvidedDevice, error) {
|
||||
serverService, found := serviceManager.Get(attach.GetServerTag())
|
||||
if !found {
|
||||
return nil, E.New("usbip-server not found: ", attach.GetServerTag())
|
||||
}
|
||||
provider, isDynamic := serverService.(adapter.USBIPDynamicServer)
|
||||
if !isDynamic {
|
||||
return nil, E.New("service ", attach.GetServerTag(), " is not a dynamic usbip-server")
|
||||
}
|
||||
descriptor := attach.GetDescriptor_()
|
||||
if descriptor == nil {
|
||||
return nil, E.New("missing device descriptor")
|
||||
}
|
||||
device := &usbProvidedDevice{
|
||||
deviceID: descriptor.GetDeviceId(),
|
||||
provider: provider,
|
||||
send: send,
|
||||
ctx: ctx,
|
||||
pending: make(map[uint64]chan *USBURBResponse),
|
||||
}
|
||||
entry := usbDeviceEntryFromDescriptor(descriptor)
|
||||
busID, err := provider.AddDevice(usbip.ProvidedDeviceInfo{Entry: entry}, device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
device.busID = busID
|
||||
return device, nil
|
||||
}
|
||||
|
||||
func usbDeviceEntryFromDescriptor(descriptor *USBDeviceDescriptor) usbip.DeviceEntry {
|
||||
deviceID := descriptor.GetDeviceId()
|
||||
interfaces := usbInterfacesFromProto(descriptor.GetInterfaces())
|
||||
info := usbip.DeviceInfoTruncated{
|
||||
BusNum: descriptor.GetBusNum(),
|
||||
DevNum: descriptor.GetDevNum(),
|
||||
Speed: descriptor.GetSpeed(),
|
||||
IDVendor: uint16(descriptor.GetVendorId()),
|
||||
IDProduct: uint16(descriptor.GetProductId()),
|
||||
BCDDevice: uint16(descriptor.GetBcdDevice()),
|
||||
BDeviceClass: uint8(descriptor.GetDeviceClass()),
|
||||
BDeviceSubClass: uint8(descriptor.GetDeviceSubClass()),
|
||||
BDeviceProtocol: uint8(descriptor.GetDeviceProtocol()),
|
||||
BConfigurationValue: uint8(descriptor.GetConfigurationValue()),
|
||||
BNumConfigurations: uint8(descriptor.GetNumConfigurations()),
|
||||
BNumInterfaces: uint8(len(interfaces)),
|
||||
}
|
||||
copy(info.BusID[:], deviceID)
|
||||
return usbip.DeviceEntry{
|
||||
Info: info,
|
||||
Interfaces: interfaces,
|
||||
Serial: descriptor.GetSerial(),
|
||||
Product: descriptor.GetProduct(),
|
||||
}
|
||||
}
|
||||
|
||||
// sing-usbip calls Submit concurrently across endpoints for a single device.
|
||||
type usbProvidedDevice struct {
|
||||
deviceID string
|
||||
busID string
|
||||
provider adapter.USBIPDynamicServer
|
||||
send func(*USBServerMessage) error
|
||||
ctx context.Context
|
||||
|
||||
seq atomic.Uint64
|
||||
access sync.Mutex
|
||||
pending map[uint64]chan *USBURBResponse
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (d *usbProvidedDevice) Submit(request usbip.URBRequest) usbip.URBResponse {
|
||||
seq := d.seq.Add(1)
|
||||
responseChan := make(chan *USBURBResponse, 1)
|
||||
d.access.Lock()
|
||||
if d.closed {
|
||||
d.access.Unlock()
|
||||
return usbip.URBResponse{Error: E.New("device detached")}
|
||||
}
|
||||
d.pending[seq] = responseChan
|
||||
d.access.Unlock()
|
||||
defer func() {
|
||||
d.access.Lock()
|
||||
delete(d.pending, seq)
|
||||
d.access.Unlock()
|
||||
}()
|
||||
|
||||
directionIn := request.Endpoint&0x80 != 0
|
||||
message := &USBURBRequest{
|
||||
DeviceId: d.deviceID,
|
||||
Seq: seq,
|
||||
Endpoint: uint32(request.Endpoint),
|
||||
DirectionIn: directionIn,
|
||||
TransferFlags: uint32(request.Command.TransferFlags),
|
||||
Setup: append([]byte(nil), request.Command.Setup[:]...),
|
||||
TransferBufferLength: uint32(request.Command.TransferBufferLength),
|
||||
NumberOfPackets: request.Command.NumberOfPackets,
|
||||
StartFrame: request.Command.StartFrame,
|
||||
Interval: request.Command.Interval,
|
||||
IsoPackets: isoPacketsToProto(request.IsoPackets),
|
||||
}
|
||||
if !directionIn {
|
||||
message.OutData = request.Buffer
|
||||
}
|
||||
sendErr := d.send(&USBServerMessage{Message: &USBServerMessage_UrbRequest{UrbRequest: message}})
|
||||
if sendErr != nil {
|
||||
return usbip.URBResponse{Error: sendErr}
|
||||
}
|
||||
select {
|
||||
case <-d.ctx.Done():
|
||||
return usbip.URBResponse{Error: d.ctx.Err()}
|
||||
case response := <-responseChan:
|
||||
result := usbip.URBResponse{
|
||||
Status: response.GetStatus(),
|
||||
ActualLength: response.GetActualLength(),
|
||||
IsoPackets: isoPacketsFromProto(response.GetIsoPackets()),
|
||||
}
|
||||
if directionIn {
|
||||
result.Buffer = response.GetInData()
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
func (d *usbProvidedDevice) AbortEndpoint(endpoint uint8) error {
|
||||
return d.send(&USBServerMessage{Message: &USBServerMessage_Abort{Abort: &USBEndpointAbort{
|
||||
DeviceId: d.deviceID,
|
||||
Endpoint: uint32(endpoint),
|
||||
}}})
|
||||
}
|
||||
|
||||
func (d *usbProvidedDevice) deliver(response *USBURBResponse) {
|
||||
d.access.Lock()
|
||||
responseChan, found := d.pending[response.GetSeq()]
|
||||
d.access.Unlock()
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case responseChan <- response:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (d *usbProvidedDevice) close() {
|
||||
d.access.Lock()
|
||||
if d.closed {
|
||||
d.access.Unlock()
|
||||
return
|
||||
}
|
||||
d.closed = true
|
||||
d.access.Unlock()
|
||||
if d.busID != "" {
|
||||
d.provider.RemoveDevice(d.busID)
|
||||
}
|
||||
}
|
||||
|
||||
func usbInterfacesFromProto(interfaces []*USBInterface) []usbip.DeviceInterface {
|
||||
if len(interfaces) == 0 {
|
||||
return nil
|
||||
}
|
||||
deviceInterfaces := make([]usbip.DeviceInterface, 0, len(interfaces))
|
||||
for _, deviceInterface := range interfaces {
|
||||
deviceInterfaces = append(deviceInterfaces, usbip.DeviceInterface{
|
||||
BInterfaceClass: uint8(deviceInterface.GetInterfaceClass()),
|
||||
BInterfaceSubClass: uint8(deviceInterface.GetInterfaceSubClass()),
|
||||
BInterfaceProtocol: uint8(deviceInterface.GetInterfaceProtocol()),
|
||||
})
|
||||
}
|
||||
return deviceInterfaces
|
||||
}
|
||||
|
||||
func isoPacketsToProto(packets []usbip.IsoPacketDescriptor) []*USBIsoPacket {
|
||||
if len(packets) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]*USBIsoPacket, 0, len(packets))
|
||||
for _, packet := range packets {
|
||||
out = append(out, &USBIsoPacket{
|
||||
Offset: packet.Offset,
|
||||
Length: packet.Length,
|
||||
ActualLength: packet.ActualLength,
|
||||
Status: packet.Status,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func isoPacketsFromProto(packets []*USBIsoPacket) []usbip.IsoPacketDescriptor {
|
||||
if len(packets) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]usbip.IsoPacketDescriptor, 0, len(packets))
|
||||
for _, packet := range packets {
|
||||
out = append(out, usbip.IsoPacketDescriptor{
|
||||
Offset: packet.GetOffset(),
|
||||
Length: packet.GetLength(),
|
||||
ActualLength: packet.GetActualLength(),
|
||||
Status: packet.GetStatus(),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//go:build !with_usbip || !(linux || (darwin && cgo) || windows)
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
func (s *StartedService) ProvideUSBDevices(server grpc.BidiStreamingServer[USBProviderMessage, USBServerMessage]) error {
|
||||
return status.Error(codes.Unimplemented, "USB/IP is not included in this build, rebuild with -tags with_usbip")
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeUSBIPServerStatus(_ *emptypb.Empty, server grpc.ServerStreamingServer[USBIPServerStatusUpdate]) error {
|
||||
return status.Error(codes.NotFound, "USB/IP is not included in this build, rebuild with -tags with_usbip")
|
||||
}
|
||||
Reference in New Issue
Block a user