Add USB/IP service
This commit is contained in:
@@ -5,6 +5,9 @@ import (
|
||||
"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"
|
||||
@@ -13,19 +16,22 @@ import (
|
||||
var _ ManagedServiceServer = (*ManagedService)(nil)
|
||||
|
||||
type ManagedService struct {
|
||||
handler ManagedHandler
|
||||
debug bool
|
||||
handler ManagedHandler
|
||||
debug bool
|
||||
oomReporter oomkiller.OOMReporter
|
||||
}
|
||||
|
||||
type ManagedServiceOptions struct {
|
||||
Handler ManagedHandler
|
||||
Debug bool
|
||||
Handler ManagedHandler
|
||||
Debug bool
|
||||
OOMReporter oomkiller.OOMReporter
|
||||
}
|
||||
|
||||
func NewManagedService(options ManagedServiceOptions) *ManagedService {
|
||||
return &ManagedService{
|
||||
handler: options.Handler,
|
||||
debug: options.Debug,
|
||||
handler: options.Handler,
|
||||
debug: options.Debug,
|
||||
oomReporter: options.OOMReporter,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,5 +86,12 @@ func (s *ManagedService) TriggerDebugCrash(ctx context.Context, request *DebugCr
|
||||
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() {
|
||||
}
|
||||
|
||||
@@ -218,13 +218,14 @@ const file_daemon_managed_service_proto_rawDesc = "" +
|
||||
"\x04Type\x12\x06\n" +
|
||||
"\x02GO\x10\x00\x12\n" +
|
||||
"\n" +
|
||||
"\x06NATIVE\x10\x012\x80\x03\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\"\x00B%Z#github.com/sagernet/sing-box/daemonb\x06proto3"
|
||||
"\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
|
||||
@@ -257,13 +258,15 @@ var file_daemon_managed_service_proto_depIdxs = []int32{
|
||||
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.StopService:output_type -> google.protobuf.Empty
|
||||
4, // 7: daemon.ManagedService.ReloadService:output_type -> google.protobuf.Empty
|
||||
1, // 8: daemon.ManagedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus
|
||||
4, // 9: daemon.ManagedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty
|
||||
4, // 10: daemon.ManagedService.TriggerDebugCrash:output_type -> google.protobuf.Empty
|
||||
6, // [6:11] is the sub-list for method output_type
|
||||
1, // [1:6] is the sub-list for method input_type
|
||||
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
|
||||
|
||||
@@ -12,6 +12,7 @@ service ManagedService {
|
||||
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 {
|
||||
|
||||
@@ -20,6 +20,7 @@ const (
|
||||
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.
|
||||
@@ -31,6 +32,7 @@ type ManagedServiceClient interface {
|
||||
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 {
|
||||
@@ -91,6 +93,16 @@ func (c *managedServiceClient) TriggerDebugCrash(ctx context.Context, in *DebugC
|
||||
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.
|
||||
@@ -100,6 +112,7 @@ type ManagedServiceServer interface {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -129,6 +142,10 @@ func (UnimplementedManagedServiceServer) SetSystemProxyEnabled(context.Context,
|
||||
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() {}
|
||||
|
||||
@@ -240,6 +257,24 @@ func _ManagedService_TriggerDebugCrash_Handler(srv interface{}, ctx context.Cont
|
||||
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)
|
||||
@@ -267,6 +302,10 @@ var ManagedService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "TriggerDebugCrash",
|
||||
Handler: _ManagedService_TriggerDebugCrash_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "TriggerOOMReport",
|
||||
Handler: _ManagedService_TriggerOOMReport_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "daemon/managed_service.proto",
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
"github.com/sagernet/sing-box/service/oomkiller"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
@@ -32,7 +31,7 @@ import (
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
const APIVersion = 1
|
||||
const APIVersion = 2
|
||||
|
||||
var _ StartedServiceServer = (*StartedService)(nil)
|
||||
|
||||
@@ -669,18 +668,6 @@ func (s *StartedService) SetGroupExpand(ctx context.Context, request *SetGroupEx
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) TriggerOOMReport(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
instance := s.Instance()
|
||||
if instance == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "service not started")
|
||||
}
|
||||
reporter := service.FromContext[oomkiller.OOMReporter](instance.ctx)
|
||||
if reporter == nil {
|
||||
return nil, status.Error(codes.Unavailable, "OOM reporter not available")
|
||||
}
|
||||
return &emptypb.Empty{}, reporter.WriteReport(memory.Total())
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[ConnectionEvents]) error {
|
||||
err := s.waitForStarted(server.Context())
|
||||
if err != nil {
|
||||
|
||||
+1539
-144
File diff suppressed because it is too large
Load Diff
@@ -22,8 +22,6 @@ service StartedService {
|
||||
rpc SelectOutbound(SelectOutboundRequest) returns (google.protobuf.Empty) {}
|
||||
rpc SetGroupExpand(SetGroupExpandRequest) returns (google.protobuf.Empty) {}
|
||||
|
||||
rpc TriggerOOMReport(google.protobuf.Empty) 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) {}
|
||||
@@ -38,6 +36,8 @@ service StartedService {
|
||||
rpc SetTailscaleExitNode(SetTailscaleExitNodeRequest) returns (google.protobuf.Empty) {}
|
||||
rpc TailscaleLogout(TailscaleLogoutRequest) returns (google.protobuf.Empty) {}
|
||||
rpc StartTailscaleSSHSession(stream TailscaleSSHClientMessage) returns (stream TailscaleSSHServerMessage) {}
|
||||
rpc ProvideUSBDevices(stream USBProviderMessage) returns (stream USBServerMessage) {}
|
||||
rpc SubscribeUSBIPServerStatus(google.protobuf.Empty) returns (stream USBIPServerStatusUpdate) {}
|
||||
}
|
||||
|
||||
message Version {
|
||||
@@ -389,3 +389,130 @@ message TailscaleSSHExit {
|
||||
message TailscaleSSHError {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -15,33 +15,34 @@ import (
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
StartedService_GetVersion_FullMethodName = "/daemon.StartedService/GetVersion"
|
||||
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
|
||||
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
|
||||
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
|
||||
StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs"
|
||||
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
|
||||
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
|
||||
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
|
||||
StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode"
|
||||
StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode"
|
||||
StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest"
|
||||
StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound"
|
||||
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
|
||||
StartedService_TriggerOOMReport_FullMethodName = "/daemon.StartedService/TriggerOOMReport"
|
||||
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
|
||||
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
|
||||
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
|
||||
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
|
||||
StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt"
|
||||
StartedService_SubscribeOutbounds_FullMethodName = "/daemon.StartedService/SubscribeOutbounds"
|
||||
StartedService_StartNetworkQualityTest_FullMethodName = "/daemon.StartedService/StartNetworkQualityTest"
|
||||
StartedService_StartSTUNTest_FullMethodName = "/daemon.StartedService/StartSTUNTest"
|
||||
StartedService_SubscribeTailscaleStatus_FullMethodName = "/daemon.StartedService/SubscribeTailscaleStatus"
|
||||
StartedService_StartTailscalePing_FullMethodName = "/daemon.StartedService/StartTailscalePing"
|
||||
StartedService_SetTailscaleExitNode_FullMethodName = "/daemon.StartedService/SetTailscaleExitNode"
|
||||
StartedService_TailscaleLogout_FullMethodName = "/daemon.StartedService/TailscaleLogout"
|
||||
StartedService_StartTailscaleSSHSession_FullMethodName = "/daemon.StartedService/StartTailscaleSSHSession"
|
||||
StartedService_GetVersion_FullMethodName = "/daemon.StartedService/GetVersion"
|
||||
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
|
||||
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
|
||||
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
|
||||
StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs"
|
||||
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
|
||||
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
|
||||
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
|
||||
StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode"
|
||||
StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode"
|
||||
StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest"
|
||||
StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound"
|
||||
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
|
||||
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
|
||||
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
|
||||
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
|
||||
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
|
||||
StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt"
|
||||
StartedService_SubscribeOutbounds_FullMethodName = "/daemon.StartedService/SubscribeOutbounds"
|
||||
StartedService_StartNetworkQualityTest_FullMethodName = "/daemon.StartedService/StartNetworkQualityTest"
|
||||
StartedService_StartSTUNTest_FullMethodName = "/daemon.StartedService/StartSTUNTest"
|
||||
StartedService_SubscribeTailscaleStatus_FullMethodName = "/daemon.StartedService/SubscribeTailscaleStatus"
|
||||
StartedService_StartTailscalePing_FullMethodName = "/daemon.StartedService/StartTailscalePing"
|
||||
StartedService_SetTailscaleExitNode_FullMethodName = "/daemon.StartedService/SetTailscaleExitNode"
|
||||
StartedService_TailscaleLogout_FullMethodName = "/daemon.StartedService/TailscaleLogout"
|
||||
StartedService_StartTailscaleSSHSession_FullMethodName = "/daemon.StartedService/StartTailscaleSSHSession"
|
||||
StartedService_ProvideUSBDevices_FullMethodName = "/daemon.StartedService/ProvideUSBDevices"
|
||||
StartedService_SubscribeUSBIPServerStatus_FullMethodName = "/daemon.StartedService/SubscribeUSBIPServerStatus"
|
||||
)
|
||||
|
||||
// StartedServiceClient is the client API for StartedService service.
|
||||
@@ -61,7 +62,6 @@ type StartedServiceClient interface {
|
||||
URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
TriggerOOMReport(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error)
|
||||
CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
@@ -75,6 +75,8 @@ type StartedServiceClient interface {
|
||||
SetTailscaleExitNode(ctx context.Context, in *SetTailscaleExitNodeRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
TailscaleLogout(ctx context.Context, in *TailscaleLogoutRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
StartTailscaleSSHSession(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TailscaleSSHClientMessage, TailscaleSSHServerMessage], error)
|
||||
ProvideUSBDevices(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[USBProviderMessage, USBServerMessage], error)
|
||||
SubscribeUSBIPServerStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[USBIPServerStatusUpdate], error)
|
||||
}
|
||||
|
||||
type startedServiceClient struct {
|
||||
@@ -260,16 +262,6 @@ func (c *startedServiceClient) SetGroupExpand(ctx context.Context, in *SetGroupE
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) 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, StartedService_TriggerOOMReport_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)
|
||||
@@ -457,6 +449,38 @@ func (c *startedServiceClient) StartTailscaleSSHSession(ctx context.Context, opt
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_StartTailscaleSSHSessionClient = grpc.BidiStreamingClient[TailscaleSSHClientMessage, TailscaleSSHServerMessage]
|
||||
|
||||
func (c *startedServiceClient) ProvideUSBDevices(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[USBProviderMessage, USBServerMessage], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[12], StartedService_ProvideUSBDevices_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[USBProviderMessage, USBServerMessage]{ClientStream: stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_ProvideUSBDevicesClient = grpc.BidiStreamingClient[USBProviderMessage, USBServerMessage]
|
||||
|
||||
func (c *startedServiceClient) SubscribeUSBIPServerStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[USBIPServerStatusUpdate], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[13], StartedService_SubscribeUSBIPServerStatus_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[emptypb.Empty, USBIPServerStatusUpdate]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeUSBIPServerStatusClient = grpc.ServerStreamingClient[USBIPServerStatusUpdate]
|
||||
|
||||
// StartedServiceServer is the server API for StartedService service.
|
||||
// All implementations must embed UnimplementedStartedServiceServer
|
||||
// for forward compatibility.
|
||||
@@ -474,7 +498,6 @@ type StartedServiceServer interface {
|
||||
URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error)
|
||||
SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error)
|
||||
SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)
|
||||
TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error
|
||||
CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)
|
||||
CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
@@ -488,6 +511,8 @@ type StartedServiceServer interface {
|
||||
SetTailscaleExitNode(context.Context, *SetTailscaleExitNodeRequest) (*emptypb.Empty, error)
|
||||
TailscaleLogout(context.Context, *TailscaleLogoutRequest) (*emptypb.Empty, error)
|
||||
StartTailscaleSSHSession(grpc.BidiStreamingServer[TailscaleSSHClientMessage, TailscaleSSHServerMessage]) error
|
||||
ProvideUSBDevices(grpc.BidiStreamingServer[USBProviderMessage, USBServerMessage]) error
|
||||
SubscribeUSBIPServerStatus(*emptypb.Empty, grpc.ServerStreamingServer[USBIPServerStatusUpdate]) error
|
||||
mustEmbedUnimplementedStartedServiceServer()
|
||||
}
|
||||
|
||||
@@ -550,10 +575,6 @@ func (UnimplementedStartedServiceServer) SetGroupExpand(context.Context, *SetGro
|
||||
return nil, status.Error(codes.Unimplemented, "method SetGroupExpand not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method TriggerOOMReport not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error {
|
||||
return status.Error(codes.Unimplemented, "method SubscribeConnections not implemented")
|
||||
}
|
||||
@@ -605,6 +626,14 @@ func (UnimplementedStartedServiceServer) TailscaleLogout(context.Context, *Tails
|
||||
func (UnimplementedStartedServiceServer) StartTailscaleSSHSession(grpc.BidiStreamingServer[TailscaleSSHClientMessage, TailscaleSSHServerMessage]) error {
|
||||
return status.Error(codes.Unimplemented, "method StartTailscaleSSHSession not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) ProvideUSBDevices(grpc.BidiStreamingServer[USBProviderMessage, USBServerMessage]) error {
|
||||
return status.Error(codes.Unimplemented, "method ProvideUSBDevices not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubscribeUSBIPServerStatus(*emptypb.Empty, grpc.ServerStreamingServer[USBIPServerStatusUpdate]) error {
|
||||
return status.Error(codes.Unimplemented, "method SubscribeUSBIPServerStatus not implemented")
|
||||
}
|
||||
func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}
|
||||
func (UnimplementedStartedServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
@@ -825,24 +854,6 @@ func _StartedService_SetGroupExpand_Handler(srv interface{}, ctx context.Context
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_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.(StartedServiceServer).TriggerOOMReport(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_TriggerOOMReport_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).TriggerOOMReport(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(SubscribeConnectionsRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
@@ -1024,6 +1035,24 @@ func _StartedService_StartTailscaleSSHSession_Handler(srv interface{}, stream gr
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_StartTailscaleSSHSessionServer = grpc.BidiStreamingServer[TailscaleSSHClientMessage, TailscaleSSHServerMessage]
|
||||
|
||||
func _StartedService_ProvideUSBDevices_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(StartedServiceServer).ProvideUSBDevices(&grpc.GenericServerStream[USBProviderMessage, USBServerMessage]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_ProvideUSBDevicesServer = grpc.BidiStreamingServer[USBProviderMessage, USBServerMessage]
|
||||
|
||||
func _StartedService_SubscribeUSBIPServerStatus_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(emptypb.Empty)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(StartedServiceServer).SubscribeUSBIPServerStatus(m, &grpc.GenericServerStream[emptypb.Empty, USBIPServerStatusUpdate]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeUSBIPServerStatusServer = grpc.ServerStreamingServer[USBIPServerStatusUpdate]
|
||||
|
||||
// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -1063,10 +1092,6 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "SetGroupExpand",
|
||||
Handler: _StartedService_SetGroupExpand_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "TriggerOOMReport",
|
||||
Handler: _StartedService_TriggerOOMReport_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CloseConnection",
|
||||
Handler: _StartedService_CloseConnection_Handler,
|
||||
@@ -1154,6 +1179,17 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "ProvideUSBDevices",
|
||||
Handler: _StartedService_ProvideUSBDevices_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "SubscribeUSBIPServerStatus",
|
||||
Handler: _StartedService_SubscribeUSBIPServerStatus_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "daemon/started_service.proto",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,459 @@
|
||||
//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"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"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"
|
||||
)
|
||||
|
||||
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 E.New("service not started")
|
||||
}
|
||||
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
|
||||
}
|
||||
s.serviceAccess.RLock()
|
||||
instance := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
if instance == nil {
|
||||
return E.New("service not started")
|
||||
}
|
||||
serviceManager := service.FromContext[adapter.ServiceManager](instance.ctx)
|
||||
if serviceManager == nil {
|
||||
return status.Error(codes.FailedPrecondition, "service manager not available")
|
||||
}
|
||||
|
||||
type usbipServer struct {
|
||||
tag string
|
||||
provider adapter.USBIPDynamicServer
|
||||
}
|
||||
var servers []usbipServer
|
||||
for _, serverService := range serviceManager.Services() {
|
||||
provider, isDynamic := serverService.(adapter.USBIPDynamicServer)
|
||||
if !isDynamic {
|
||||
continue
|
||||
}
|
||||
servers = append(servers, usbipServer{tag: serverService.Tag(), provider: provider})
|
||||
}
|
||||
if len(servers) == 0 {
|
||||
return status.Error(codes.NotFound, "no usbip-server found")
|
||||
}
|
||||
|
||||
type taggedStatus struct {
|
||||
tag string
|
||||
devices []usbip.ControlDeviceInfo
|
||||
}
|
||||
updates := make(chan taggedStatus, len(servers))
|
||||
ctx, cancel := context.WithCancel(server.Context())
|
||||
defer cancel()
|
||||
|
||||
var waitGroup sync.WaitGroup
|
||||
for _, srv := range servers {
|
||||
// sing-usbip invokes the SubscribeDevices listener while holding the
|
||||
// ledger's broadcast lock, so it must never block.
|
||||
latest := make(chan []usbip.ControlDeviceInfo, 1)
|
||||
waitGroup.Add(1)
|
||||
go func(provider adapter.USBIPDynamicServer) {
|
||||
defer waitGroup.Done()
|
||||
provider.SubscribeDevices(ctx, func(devices []usbip.ControlDeviceInfo) {
|
||||
sendLatestUSBSnapshot(latest, devices)
|
||||
})
|
||||
}(srv.provider)
|
||||
waitGroup.Add(1)
|
||||
go func(tag string) {
|
||||
defer waitGroup.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case devices := <-latest:
|
||||
select {
|
||||
case updates <- taggedStatus{tag: tag, devices: devices}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(srv.tag)
|
||||
}
|
||||
|
||||
go func() {
|
||||
waitGroup.Wait()
|
||||
close(updates)
|
||||
}()
|
||||
|
||||
var tags []string
|
||||
deviceStates := make(map[string][]usbip.ControlDeviceInfo, len(servers))
|
||||
for update := range updates {
|
||||
if _, exists := deviceStates[update.tag]; !exists {
|
||||
tags = append(tags, update.tag)
|
||||
}
|
||||
deviceStates[update.tag] = update.devices
|
||||
protoServers := make([]*USBIPServerStatus, 0, len(deviceStates))
|
||||
for _, tag := range tags {
|
||||
protoServers = append(protoServers, &USBIPServerStatus{
|
||||
ServerTag: tag,
|
||||
Devices: usbSharedDevicesToProto(deviceStates[tag]),
|
||||
})
|
||||
}
|
||||
sendErr := server.Send(&USBIPServerStatusUpdate{Servers: protoServers})
|
||||
if sendErr != nil {
|
||||
return sendErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendLatestUSBSnapshot(slot chan []usbip.ControlDeviceInfo, devices []usbip.ControlDeviceInfo) {
|
||||
select {
|
||||
case slot <- devices:
|
||||
return
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case <-slot:
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case slot <- devices:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
busID, err := provider.AddDevice(usbip.DynamicDeviceInfo{
|
||||
BusID: descriptor.GetDeviceId(),
|
||||
BusNum: descriptor.GetBusNum(),
|
||||
DevNum: descriptor.GetDevNum(),
|
||||
Speed: descriptor.GetSpeed(),
|
||||
VendorID: uint16(descriptor.GetVendorId()),
|
||||
ProductID: uint16(descriptor.GetProductId()),
|
||||
BCDDevice: uint16(descriptor.GetBcdDevice()),
|
||||
DeviceClass: uint8(descriptor.GetDeviceClass()),
|
||||
DeviceSubClass: uint8(descriptor.GetDeviceSubClass()),
|
||||
DeviceProtocol: uint8(descriptor.GetDeviceProtocol()),
|
||||
ConfigurationValue: uint8(descriptor.GetConfigurationValue()),
|
||||
NumConfigurations: uint8(descriptor.GetNumConfigurations()),
|
||||
Interfaces: usbInterfacesFromProto(descriptor.GetInterfaces()),
|
||||
Serial: descriptor.GetSerial(),
|
||||
Product: descriptor.GetProduct(),
|
||||
}, device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
device.busID = busID
|
||||
return device, nil
|
||||
}
|
||||
|
||||
// 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