mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-07-19 21:08:40 +08:00
Add USB/IP service
This commit is contained in:
parent
12f160ab5f
commit
7cffd47dd4
15
adapter/usbip.go
Normal file
15
adapter/usbip.go
Normal file
@ -0,0 +1,15 @@
|
||||
//go:build with_usbip && (linux || (darwin && cgo) || windows)
|
||||
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-usbip"
|
||||
)
|
||||
|
||||
type USBIPDynamicServer interface {
|
||||
AddDevice(info usbip.DynamicDeviceInfo, transport usbip.DeviceTransport) (string, error)
|
||||
RemoveDevice(busID string)
|
||||
SubscribeDevices(ctx context.Context, listener func([]usbip.ControlDeviceInfo))
|
||||
}
|
||||
7
adapter/usbip_stub.go
Normal file
7
adapter/usbip_stub.go
Normal file
@ -0,0 +1,7 @@
|
||||
//go:build !with_usbip || !(linux || (darwin && cgo) || windows)
|
||||
|
||||
package adapter
|
||||
|
||||
type USBIPDynamicServer interface {
|
||||
usbipNotIncluded()
|
||||
}
|
||||
@ -33,6 +33,8 @@ const (
|
||||
TypeCCM = "ccm"
|
||||
TypeOCM = "ocm"
|
||||
TypeOOMKiller = "oom-killer"
|
||||
TypeUSBIPServer = "usbip-server"
|
||||
TypeUSBIPClient = "usbip-client"
|
||||
TypeHysteriaRealm = "hysteria-realm"
|
||||
TypeACME = "acme"
|
||||
TypeCloudflareOriginCA = "cloudflare-origin-ca"
|
||||
|
||||
@ -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 {
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
459
daemon/started_service_usbip.go
Normal file
459
daemon/started_service_usbip.go
Normal file
@ -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
|
||||
}
|
||||
18
daemon/started_service_usbip_stub.go
Normal file
18
daemon/started_service_usbip_stub.go
Normal file
@ -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")
|
||||
}
|
||||
@ -673,7 +673,7 @@ func (c *CommandClient) TriggerNativeCrash() error {
|
||||
}
|
||||
|
||||
func (c *CommandClient) TriggerOOMReport() error {
|
||||
_, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) {
|
||||
_, err := callManagedWithResult(c, func(ctx context.Context, client daemon.ManagedServiceClient) (*emptypb.Empty, error) {
|
||||
return client.TriggerOOMReport(ctx, &emptypb.Empty{})
|
||||
})
|
||||
if err != nil {
|
||||
@ -924,6 +924,61 @@ func (c *CommandClient) SubscribeTailscaleStatus(handler TailscaleStatusHandler)
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (c *CommandClient) SubscribeUSBIPServerStatus(handler USBIPServerStatusHandler) (*USBIPServerStatusSubscription, error) {
|
||||
client, parentCtx, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "subscribe usbip server status")
|
||||
}
|
||||
|
||||
streamCtx, cancel := context.WithCancel(parentCtx)
|
||||
session := &USBIPServerStatusSubscription{
|
||||
streamSession: streamSession{
|
||||
ctx: streamCtx,
|
||||
cancel: cancel,
|
||||
closeDone: make(chan struct{}),
|
||||
},
|
||||
}
|
||||
|
||||
failStart := func(cause error, message string) (*USBIPServerStatusSubscription, error) {
|
||||
cancel()
|
||||
if c.standalone {
|
||||
c.closeConnection()
|
||||
}
|
||||
return nil, E.Cause(cause, message)
|
||||
}
|
||||
|
||||
stream, err := client.SubscribeUSBIPServerStatus(streamCtx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
return failStart(err, "subscribe usbip server status")
|
||||
}
|
||||
|
||||
standalone := c.standalone
|
||||
go func() {
|
||||
defer func() {
|
||||
close(session.closeDone)
|
||||
if standalone {
|
||||
c.closeConnection()
|
||||
}
|
||||
}()
|
||||
for {
|
||||
event, recvErr := stream.Recv()
|
||||
if recvErr != nil {
|
||||
if session.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
if status.Code(recvErr) == codes.NotFound || status.Code(recvErr) == codes.Unavailable {
|
||||
return
|
||||
}
|
||||
handler.OnError(E.Cause(recvErr, "usbip server status recv").Error())
|
||||
return
|
||||
}
|
||||
handler.OnStatusUpdate(usbipServerStatusUpdateFromGRPC(event))
|
||||
}
|
||||
}()
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (c *CommandClient) SetTailscaleExitNode(endpointTag string, stableID string) error {
|
||||
_, err := callWithResult(c, func(ctx context.Context, client daemon.StartedServiceClient) (*emptypb.Empty, error) {
|
||||
return client.SetTailscaleExitNode(ctx, &daemon.SetTailscaleExitNodeRequest{
|
||||
@ -1125,3 +1180,59 @@ func (c *CommandClient) StartTailscaleSSHSession(opts *TailscaleSSHOptions, hand
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (c *CommandClient) ProvideUSBDevices(handler USBProviderHandler) (*USBProviderSession, error) {
|
||||
client, parentCtx, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "provide usb devices")
|
||||
}
|
||||
|
||||
streamCtx, cancel := context.WithCancel(parentCtx)
|
||||
stream, err := client.ProvideUSBDevices(streamCtx)
|
||||
if err != nil {
|
||||
cancel()
|
||||
if c.standalone {
|
||||
c.closeConnection()
|
||||
}
|
||||
return nil, E.Cause(err, "provide usb devices")
|
||||
}
|
||||
|
||||
session := &USBProviderSession{
|
||||
stream: stream,
|
||||
ctx: streamCtx,
|
||||
cancel: cancel,
|
||||
closeDone: make(chan struct{}),
|
||||
}
|
||||
|
||||
standalone := c.standalone
|
||||
go func() {
|
||||
defer close(session.closeDone)
|
||||
for {
|
||||
message, recvErr := stream.Recv()
|
||||
if recvErr == io.EOF {
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
if recvErr != nil {
|
||||
handler.OnError("", E.Cause(recvErr, "usb provider recv").Error())
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
switch payload := message.GetMessage().(type) {
|
||||
case *daemon.USBServerMessage_Ready:
|
||||
handler.OnReady(payload.Ready.GetDeviceId(), payload.Ready.GetBusId())
|
||||
case *daemon.USBServerMessage_UrbRequest:
|
||||
handler.OnURBRequest(usbURBRequestFromGRPC(payload.UrbRequest))
|
||||
case *daemon.USBServerMessage_Abort:
|
||||
handler.OnAbort(payload.Abort.GetDeviceId(), int32(payload.Abort.GetEndpoint()))
|
||||
case *daemon.USBServerMessage_Error:
|
||||
handler.OnError(payload.Error.GetDeviceId(), payload.Error.GetMessage())
|
||||
}
|
||||
}
|
||||
if standalone {
|
||||
c.closeConnection()
|
||||
}
|
||||
}()
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
@ -75,8 +75,9 @@ func NewCommandServer(handler CommandServerHandler, platformInterface PlatformIn
|
||||
// SystemProxyEnabled: false,
|
||||
})
|
||||
server.managedService = daemon.NewManagedService(daemon.ManagedServiceOptions{
|
||||
Handler: (*platformHandler)(server),
|
||||
Debug: sDebug,
|
||||
Handler: (*platformHandler)(server),
|
||||
Debug: sDebug,
|
||||
OOMReporter: sOOMReporter,
|
||||
})
|
||||
return server, nil
|
||||
}
|
||||
|
||||
203
experimental/libbox/command_types_usbip.go
Normal file
203
experimental/libbox/command_types_usbip.go
Normal file
@ -0,0 +1,203 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/daemon"
|
||||
)
|
||||
|
||||
type USBProviderHandler interface {
|
||||
OnReady(deviceID string, busID string)
|
||||
OnURBRequest(request *USBURBRequest)
|
||||
OnAbort(deviceID string, endpoint int32)
|
||||
OnError(deviceID string, message string)
|
||||
}
|
||||
|
||||
type USBIsoPacket struct {
|
||||
Offset int32
|
||||
Length int32
|
||||
ActualLength int32
|
||||
Status int32
|
||||
}
|
||||
|
||||
type USBDeviceDescriptor struct {
|
||||
ServerTag string
|
||||
DeviceID string
|
||||
BusNum int32
|
||||
DevNum int32
|
||||
Speed int32
|
||||
VendorID int32
|
||||
ProductID int32
|
||||
BCDDevice int32
|
||||
DeviceClass int32
|
||||
DeviceSubClass int32
|
||||
DeviceProtocol int32
|
||||
ConfigurationValue int32
|
||||
NumConfigurations int32
|
||||
Serial string
|
||||
Product string
|
||||
|
||||
interfaces []*daemon.USBInterface
|
||||
}
|
||||
|
||||
func NewUSBDeviceDescriptor(serverTag string, deviceID string) *USBDeviceDescriptor {
|
||||
return &USBDeviceDescriptor{ServerTag: serverTag, DeviceID: deviceID}
|
||||
}
|
||||
|
||||
func (d *USBDeviceDescriptor) AddInterface(interfaceClass int32, interfaceSubClass int32, interfaceProtocol int32) {
|
||||
d.interfaces = append(d.interfaces, &daemon.USBInterface{
|
||||
InterfaceClass: uint32(interfaceClass),
|
||||
InterfaceSubClass: uint32(interfaceSubClass),
|
||||
InterfaceProtocol: uint32(interfaceProtocol),
|
||||
})
|
||||
}
|
||||
|
||||
func (d *USBDeviceDescriptor) toProto() *daemon.USBDeviceAttach {
|
||||
return &daemon.USBDeviceAttach{
|
||||
ServerTag: d.ServerTag,
|
||||
Descriptor_: &daemon.USBDeviceDescriptor{
|
||||
DeviceId: d.DeviceID,
|
||||
BusNum: uint32(d.BusNum),
|
||||
DevNum: uint32(d.DevNum),
|
||||
Speed: uint32(d.Speed),
|
||||
VendorId: uint32(d.VendorID),
|
||||
ProductId: uint32(d.ProductID),
|
||||
BcdDevice: uint32(d.BCDDevice),
|
||||
DeviceClass: uint32(d.DeviceClass),
|
||||
DeviceSubClass: uint32(d.DeviceSubClass),
|
||||
DeviceProtocol: uint32(d.DeviceProtocol),
|
||||
ConfigurationValue: uint32(d.ConfigurationValue),
|
||||
NumConfigurations: uint32(d.NumConfigurations),
|
||||
Interfaces: d.interfaces,
|
||||
Serial: d.Serial,
|
||||
Product: d.Product,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type USBURBRequest struct {
|
||||
DeviceID string
|
||||
Seq int64
|
||||
Endpoint int32
|
||||
DirectionIn bool
|
||||
TransferFlags int32
|
||||
Setup []byte
|
||||
TransferBufferLength int32
|
||||
OutData []byte
|
||||
NumberOfPackets int32
|
||||
StartFrame int32
|
||||
Interval int32
|
||||
|
||||
isoPackets []*daemon.USBIsoPacket
|
||||
}
|
||||
|
||||
func (r *USBURBRequest) IsoPacketCount() int32 {
|
||||
return int32(len(r.isoPackets))
|
||||
}
|
||||
|
||||
func (r *USBURBRequest) GetIsoPacket(index int32) *USBIsoPacket {
|
||||
if index < 0 || int(index) >= len(r.isoPackets) {
|
||||
return nil
|
||||
}
|
||||
packet := r.isoPackets[index]
|
||||
return &USBIsoPacket{
|
||||
Offset: packet.GetOffset(),
|
||||
Length: packet.GetLength(),
|
||||
ActualLength: packet.GetActualLength(),
|
||||
Status: packet.GetStatus(),
|
||||
}
|
||||
}
|
||||
|
||||
func usbURBRequestFromGRPC(request *daemon.USBURBRequest) *USBURBRequest {
|
||||
return &USBURBRequest{
|
||||
DeviceID: request.GetDeviceId(),
|
||||
Seq: int64(request.GetSeq()),
|
||||
Endpoint: int32(request.GetEndpoint()),
|
||||
DirectionIn: request.GetDirectionIn(),
|
||||
TransferFlags: int32(request.GetTransferFlags()),
|
||||
Setup: request.GetSetup(),
|
||||
TransferBufferLength: int32(request.GetTransferBufferLength()),
|
||||
OutData: request.GetOutData(),
|
||||
NumberOfPackets: request.GetNumberOfPackets(),
|
||||
StartFrame: request.GetStartFrame(),
|
||||
Interval: request.GetInterval(),
|
||||
isoPackets: request.GetIsoPackets(),
|
||||
}
|
||||
}
|
||||
|
||||
type USBURBResponse struct {
|
||||
DeviceID string
|
||||
Seq int64
|
||||
Status int32
|
||||
ActualLength int32
|
||||
InData []byte
|
||||
|
||||
isoPackets []*daemon.USBIsoPacket
|
||||
}
|
||||
|
||||
func NewUSBURBResponse(deviceID string, seq int64) *USBURBResponse {
|
||||
return &USBURBResponse{DeviceID: deviceID, Seq: seq}
|
||||
}
|
||||
|
||||
func (r *USBURBResponse) AddIsoPacket(offset int32, length int32, actualLength int32, status int32) {
|
||||
r.isoPackets = append(r.isoPackets, &daemon.USBIsoPacket{
|
||||
Offset: offset,
|
||||
Length: length,
|
||||
ActualLength: actualLength,
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *USBURBResponse) toProto() *daemon.USBURBResponse {
|
||||
return &daemon.USBURBResponse{
|
||||
DeviceId: r.DeviceID,
|
||||
Seq: uint64(r.Seq),
|
||||
Status: r.Status,
|
||||
ActualLength: r.ActualLength,
|
||||
InData: r.InData,
|
||||
IsoPackets: r.isoPackets,
|
||||
}
|
||||
}
|
||||
|
||||
type USBProviderSession struct {
|
||||
stream daemon.StartedService_ProvideUSBDevicesClient
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
sendAccess sync.Mutex
|
||||
closeOnce sync.Once
|
||||
closeDone chan struct{}
|
||||
}
|
||||
|
||||
func (s *USBProviderSession) send(message *daemon.USBProviderMessage) error {
|
||||
s.sendAccess.Lock()
|
||||
defer s.sendAccess.Unlock()
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return os.ErrClosed
|
||||
default:
|
||||
}
|
||||
return s.stream.Send(message)
|
||||
}
|
||||
|
||||
func (s *USBProviderSession) AttachDevice(descriptor *USBDeviceDescriptor) error {
|
||||
return s.send(&daemon.USBProviderMessage{Message: &daemon.USBProviderMessage_Attach{Attach: descriptor.toProto()}})
|
||||
}
|
||||
|
||||
func (s *USBProviderSession) DetachDevice(deviceID string) error {
|
||||
return s.send(&daemon.USBProviderMessage{Message: &daemon.USBProviderMessage_Detach{Detach: &daemon.USBDeviceDetach{DeviceId: deviceID}}})
|
||||
}
|
||||
|
||||
func (s *USBProviderSession) SendURBResponse(response *USBURBResponse) error {
|
||||
return s.send(&daemon.USBProviderMessage{Message: &daemon.USBProviderMessage_UrbResponse{UrbResponse: response.toProto()}})
|
||||
}
|
||||
|
||||
func (s *USBProviderSession) Close() error {
|
||||
s.closeOnce.Do(func() {
|
||||
s.cancel()
|
||||
_ = s.stream.CloseSend()
|
||||
})
|
||||
<-s.closeDone
|
||||
return nil
|
||||
}
|
||||
142
experimental/libbox/command_types_usbip_status.go
Normal file
142
experimental/libbox/command_types_usbip_status.go
Normal file
@ -0,0 +1,142 @@
|
||||
package libbox
|
||||
|
||||
import "github.com/sagernet/sing-box/daemon"
|
||||
|
||||
type USBIPServerStatusUpdate struct {
|
||||
servers []*USBIPServerStatus
|
||||
}
|
||||
|
||||
func (u *USBIPServerStatusUpdate) Servers() USBIPServerStatusIterator {
|
||||
return newIterator(u.servers)
|
||||
}
|
||||
|
||||
type USBIPServerStatusIterator interface {
|
||||
Next() *USBIPServerStatus
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
type USBIPServerStatus struct {
|
||||
ServerTag string
|
||||
devices []*USBSharedDevice
|
||||
}
|
||||
|
||||
func (s *USBIPServerStatus) Devices() USBSharedDeviceIterator {
|
||||
return newIterator(s.devices)
|
||||
}
|
||||
|
||||
type USBSharedDeviceIterator interface {
|
||||
Next() *USBSharedDevice
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
const (
|
||||
USBDeviceStateIdle int32 = iota
|
||||
USBDeviceStateAttached
|
||||
USBDeviceStateUnavailable
|
||||
)
|
||||
|
||||
const (
|
||||
USBBackendUnspecified int32 = iota
|
||||
USBBackendLinuxSysfs
|
||||
USBBackendDynamic
|
||||
USBBackendDarwinIOKit
|
||||
USBBackendWindowsVBoxUSB
|
||||
)
|
||||
|
||||
type USBSharedDevice struct {
|
||||
BusID string
|
||||
StableID string
|
||||
Backend int32
|
||||
State int32
|
||||
DeviceID string
|
||||
BusNum int32
|
||||
DevNum int32
|
||||
Speed int32
|
||||
VendorID int32
|
||||
ProductID int32
|
||||
BCDDevice int32
|
||||
DeviceClass int32
|
||||
DeviceSubClass int32
|
||||
DeviceProtocol int32
|
||||
ConfigurationValue int32
|
||||
NumConfigurations int32
|
||||
Serial string
|
||||
Product string
|
||||
interfaces []*USBSharedDeviceInterface
|
||||
}
|
||||
|
||||
func (d *USBSharedDevice) Interfaces() USBSharedDeviceInterfaceIterator {
|
||||
return newIterator(d.interfaces)
|
||||
}
|
||||
|
||||
type USBSharedDeviceInterfaceIterator interface {
|
||||
Next() *USBSharedDeviceInterface
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
type USBSharedDeviceInterface struct {
|
||||
InterfaceClass int32
|
||||
InterfaceSubClass int32
|
||||
InterfaceProtocol int32
|
||||
}
|
||||
|
||||
type USBIPServerStatusHandler interface {
|
||||
OnStatusUpdate(status *USBIPServerStatusUpdate)
|
||||
OnError(message string)
|
||||
}
|
||||
|
||||
type USBIPServerStatusSubscription struct {
|
||||
streamSession
|
||||
}
|
||||
|
||||
func usbipServerStatusUpdateFromGRPC(update *daemon.USBIPServerStatusUpdate) *USBIPServerStatusUpdate {
|
||||
servers := make([]*USBIPServerStatus, len(update.Servers))
|
||||
for i, server := range update.Servers {
|
||||
servers[i] = usbipServerStatusFromGRPC(server)
|
||||
}
|
||||
return &USBIPServerStatusUpdate{servers: servers}
|
||||
}
|
||||
|
||||
func usbipServerStatusFromGRPC(status *daemon.USBIPServerStatus) *USBIPServerStatus {
|
||||
devices := make([]*USBSharedDevice, len(status.Devices))
|
||||
for i, device := range status.Devices {
|
||||
devices[i] = usbSharedDeviceFromGRPC(device)
|
||||
}
|
||||
return &USBIPServerStatus{
|
||||
ServerTag: status.GetServerTag(),
|
||||
devices: devices,
|
||||
}
|
||||
}
|
||||
|
||||
func usbSharedDeviceFromGRPC(device *daemon.USBSharedDevice) *USBSharedDevice {
|
||||
descriptor := device.GetDescriptor_()
|
||||
interfaces := make([]*USBSharedDeviceInterface, len(descriptor.GetInterfaces()))
|
||||
for i, deviceInterface := range descriptor.GetInterfaces() {
|
||||
interfaces[i] = &USBSharedDeviceInterface{
|
||||
InterfaceClass: int32(deviceInterface.GetInterfaceClass()),
|
||||
InterfaceSubClass: int32(deviceInterface.GetInterfaceSubClass()),
|
||||
InterfaceProtocol: int32(deviceInterface.GetInterfaceProtocol()),
|
||||
}
|
||||
}
|
||||
return &USBSharedDevice{
|
||||
BusID: device.GetBusId(),
|
||||
StableID: device.GetStableId(),
|
||||
Backend: int32(device.GetBackend()),
|
||||
State: int32(device.GetState()),
|
||||
DeviceID: descriptor.GetDeviceId(),
|
||||
BusNum: int32(descriptor.GetBusNum()),
|
||||
DevNum: int32(descriptor.GetDevNum()),
|
||||
Speed: int32(descriptor.GetSpeed()),
|
||||
VendorID: int32(descriptor.GetVendorId()),
|
||||
ProductID: int32(descriptor.GetProductId()),
|
||||
BCDDevice: int32(descriptor.GetBcdDevice()),
|
||||
DeviceClass: int32(descriptor.GetDeviceClass()),
|
||||
DeviceSubClass: int32(descriptor.GetDeviceSubClass()),
|
||||
DeviceProtocol: int32(descriptor.GetDeviceProtocol()),
|
||||
ConfigurationValue: int32(descriptor.GetConfigurationValue()),
|
||||
NumConfigurations: int32(descriptor.GetNumConfigurations()),
|
||||
Serial: descriptor.GetSerial(),
|
||||
Product: descriptor.GetProduct(),
|
||||
interfaces: interfaces,
|
||||
}
|
||||
}
|
||||
1
go.mod
1
go.mod
@ -146,6 +146,7 @@ require (
|
||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260620135226-def9ff0fb992 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||
github.com/sagernet/nftables v0.3.0-mod.2 // indirect
|
||||
github.com/sagernet/sing-usbip v0.0.0-20260615120724-630651f2af95
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@ -268,6 +268,8 @@ github.com/sagernet/sing-shadowtls v0.2.1 h1:ZiHZdnEnP+YS73NMsxiZmIFCwNd0M4k7PkG
|
||||
github.com/sagernet/sing-shadowtls v0.2.1/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||
github.com/sagernet/sing-tun v0.8.12-0.20260623031040-54c9dbda5fe7 h1:UZu+fMnbfOJ14v7eXQe0UADNiPkR7gO70BsDFtLXZbg=
|
||||
github.com/sagernet/sing-tun v0.8.12-0.20260623031040-54c9dbda5fe7/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ=
|
||||
github.com/sagernet/sing-usbip v0.0.0-20260615120724-630651f2af95 h1:1z+EwDwim2C1Lt5HRRFeT8MbvEKxb/LSz/F0vbBwm6g=
|
||||
github.com/sagernet/sing-usbip v0.0.0-20260615120724-630651f2af95/go.mod h1:D4CnJX3MNAAANhbQUxfIRgBdnvlTEaV7h6ojedcs+pw=
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
|
||||
|
||||
@ -143,6 +143,7 @@ func ServiceRegistry() *service.Registry {
|
||||
registerCCMService(registry)
|
||||
registerOCMService(registry)
|
||||
registerOOMKillerService(registry)
|
||||
registerUSBIPServices(registry)
|
||||
|
||||
return registry
|
||||
}
|
||||
|
||||
12
include/usbip.go
Normal file
12
include/usbip.go
Normal file
@ -0,0 +1,12 @@
|
||||
//go:build with_usbip && (linux || (darwin && cgo) || windows)
|
||||
|
||||
package include
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/adapter/service"
|
||||
"github.com/sagernet/sing-box/service/usbip"
|
||||
)
|
||||
|
||||
func registerUSBIPServices(registry *service.Registry) {
|
||||
usbip.RegisterService(registry)
|
||||
}
|
||||
23
include/usbip_stub.go
Normal file
23
include/usbip_stub.go
Normal file
@ -0,0 +1,23 @@
|
||||
//go:build !with_usbip || !(linux || (darwin && cgo) || windows)
|
||||
|
||||
package include
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/service"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func registerUSBIPServices(registry *service.Registry) {
|
||||
service.Register[option.USBIPServerServiceOptions](registry, C.TypeUSBIPServer, func(ctx context.Context, logger log.ContextLogger, tag string, options option.USBIPServerServiceOptions) (adapter.Service, error) {
|
||||
return nil, E.New(`USB/IP is not included in this build, rebuild with -tags with_usbip (supported on Linux, Windows, and macOS with CGO)`)
|
||||
})
|
||||
service.Register[option.USBIPClientServiceOptions](registry, C.TypeUSBIPClient, func(ctx context.Context, logger log.ContextLogger, tag string, options option.USBIPClientServiceOptions) (adapter.Service, error) {
|
||||
return nil, E.New(`USB/IP is not included in this build, rebuild with -tags with_usbip (supported on Linux, Windows, and macOS with CGO)`)
|
||||
})
|
||||
}
|
||||
69
option/usbip.go
Normal file
69
option/usbip.go
Normal file
@ -0,0 +1,69 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
)
|
||||
|
||||
const (
|
||||
USBIPProviderDefault = "default"
|
||||
USBIPProviderDynamic = "dynamic"
|
||||
)
|
||||
|
||||
type _USBIPServerServiceOptions struct {
|
||||
ListenOptions
|
||||
Provider string `json:"provider,omitempty"`
|
||||
Options any `json:"-"`
|
||||
}
|
||||
|
||||
type USBIPServerServiceOptions _USBIPServerServiceOptions
|
||||
|
||||
func (o USBIPServerServiceOptions) MarshalJSON() ([]byte, error) {
|
||||
if o.Options == nil {
|
||||
return json.Marshal((_USBIPServerServiceOptions)(o))
|
||||
}
|
||||
return badjson.MarshallObjects((_USBIPServerServiceOptions)(o), o.Options)
|
||||
}
|
||||
|
||||
func (o *USBIPServerServiceOptions) UnmarshalJSON(content []byte) error {
|
||||
err := json.Unmarshal(content, (*_USBIPServerServiceOptions)(o))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var options any
|
||||
switch o.Provider {
|
||||
case "", USBIPProviderDefault:
|
||||
o.Provider = USBIPProviderDefault
|
||||
options = new(USBIPDefaultProviderOptions)
|
||||
case USBIPProviderDynamic:
|
||||
options = new(USBIPDynamicProviderOptions)
|
||||
default:
|
||||
return E.New("unknown usbip provider type: ", o.Provider)
|
||||
}
|
||||
err = badjson.UnmarshallExcluded(content, (*_USBIPServerServiceOptions)(o), options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Options = options
|
||||
return nil
|
||||
}
|
||||
|
||||
type USBIPClientServiceOptions struct {
|
||||
DialerOptions
|
||||
Server string `json:"server,omitempty"`
|
||||
Devices []USBIPDeviceMatch `json:"devices,omitempty"`
|
||||
}
|
||||
|
||||
type USBIPDeviceMatch struct {
|
||||
BusID string `json:"bus_id,omitempty"`
|
||||
VendorID uint16 `json:"vendor_id,omitempty"`
|
||||
ProductID uint16 `json:"product_id,omitempty"`
|
||||
Serial string `json:"serial,omitempty"`
|
||||
}
|
||||
|
||||
type USBIPDefaultProviderOptions struct {
|
||||
Devices []USBIPDeviceMatch `json:"devices,omitempty"`
|
||||
}
|
||||
|
||||
type USBIPDynamicProviderOptions struct{}
|
||||
@ -1 +1 @@
|
||||
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_naive_outbound,badlinkname,tfogo_checklinkname0
|
||||
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_naive_outbound,with_usbip,badlinkname,tfogo_checklinkname0
|
||||
@ -1 +1 @@
|
||||
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,badlinkname,tfogo_checklinkname0
|
||||
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_usbip,badlinkname,tfogo_checklinkname0
|
||||
@ -1 +1 @@
|
||||
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0
|
||||
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_naive_outbound,with_purego,with_usbip,badlinkname,tfogo_checklinkname0
|
||||
63
service/usbip/client.go
Normal file
63
service/usbip/client.go
Normal file
@ -0,0 +1,63 @@
|
||||
//go:build with_usbip && (linux || (darwin && cgo) || windows)
|
||||
|
||||
package usbip
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-usbip"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
type ClientService struct {
|
||||
boxService.Adapter
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
inner *usbip.ClientService
|
||||
}
|
||||
|
||||
func NewClientService(ctx context.Context, logger log.ContextLogger, tag string, options option.USBIPClientServiceOptions) (adapter.Service, error) {
|
||||
serviceDialer, err := dialer.NewWithOptions(dialer.Options{
|
||||
Context: ctx,
|
||||
Options: option.DialerOptions{
|
||||
Detour: options.Detour,
|
||||
},
|
||||
RemoteIsDomain: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create dialer")
|
||||
}
|
||||
inner, err := usbip.NewClientService(ctx, usbip.ClientOptions{
|
||||
Logger: logger,
|
||||
Dialer: serviceDialer,
|
||||
ServerAddress: M.ParseSocksaddr(options.Server),
|
||||
Devices: toDeviceMatches(options.Devices),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ClientService{
|
||||
Adapter: boxService.NewAdapter(C.TypeUSBIPClient, tag),
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
inner: inner,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *ClientService) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateStart {
|
||||
return nil
|
||||
}
|
||||
return s.inner.Start()
|
||||
}
|
||||
|
||||
func (s *ClientService) Close() error {
|
||||
return s.inner.Close()
|
||||
}
|
||||
107
service/usbip/server.go
Normal file
107
service/usbip/server.go
Normal file
@ -0,0 +1,107 @@
|
||||
//go:build with_usbip && (linux || (darwin && cgo) || windows)
|
||||
|
||||
package usbip
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||
"github.com/sagernet/sing-box/common/listener"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-usbip"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type ServerService struct {
|
||||
boxService.Adapter
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
inner *usbip.ServerService
|
||||
}
|
||||
|
||||
type dynamicServerService struct {
|
||||
ServerService
|
||||
host *usbip.DynamicHost
|
||||
}
|
||||
|
||||
var _ adapter.USBIPDynamicServer = (*dynamicServerService)(nil)
|
||||
|
||||
func NewServerService(ctx context.Context, logger log.ContextLogger, tag string, options option.USBIPServerServiceOptions) (adapter.Service, error) {
|
||||
listenOptions := options.ListenOptions
|
||||
if listenOptions.ListenPort == 0 {
|
||||
listenOptions.ListenPort = usbip.DefaultPort
|
||||
}
|
||||
boxListener := listener.New(listener.Options{
|
||||
Context: ctx,
|
||||
Logger: logger,
|
||||
Network: []string{N.NetworkTCP},
|
||||
Listen: listenOptions,
|
||||
})
|
||||
serverOptions := usbip.ServerOptions{
|
||||
Logger: logger,
|
||||
Listen: func(context.Context) (net.Listener, error) {
|
||||
return boxListener.ListenTCP()
|
||||
},
|
||||
}
|
||||
base := ServerService{
|
||||
Adapter: boxService.NewAdapter(C.TypeUSBIPServer, tag),
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
providerType := options.Provider
|
||||
if providerType == "" {
|
||||
providerType = option.USBIPProviderDefault
|
||||
}
|
||||
switch providerType {
|
||||
case option.USBIPProviderDefault:
|
||||
defaultOptions, isDefault := options.Options.(*option.USBIPDefaultProviderOptions)
|
||||
if isDefault {
|
||||
serverOptions.Devices = toDeviceMatches(defaultOptions.Devices)
|
||||
}
|
||||
inner, err := usbip.NewServerService(ctx, serverOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
base.inner = inner
|
||||
return &base, nil
|
||||
case option.USBIPProviderDynamic:
|
||||
host := usbip.NewDynamicHost(logger)
|
||||
inner, err := usbip.NewDynamicServerService(ctx, serverOptions, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
base.inner = inner
|
||||
return &dynamicServerService{ServerService: base, host: host}, nil
|
||||
default:
|
||||
return nil, E.New("unknown usbip provider type: ", providerType)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerService) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateStart {
|
||||
return nil
|
||||
}
|
||||
return s.inner.Start()
|
||||
}
|
||||
|
||||
func (s *ServerService) Close() error {
|
||||
return s.inner.Close()
|
||||
}
|
||||
|
||||
func (s *dynamicServerService) AddDevice(info usbip.DynamicDeviceInfo, transport usbip.DeviceTransport) (string, error) {
|
||||
return s.host.AddDevice(info, transport)
|
||||
}
|
||||
|
||||
func (s *dynamicServerService) RemoveDevice(busID string) {
|
||||
s.host.RemoveDevice(busID)
|
||||
}
|
||||
|
||||
func (s *dynamicServerService) SubscribeDevices(ctx context.Context, listener func([]usbip.ControlDeviceInfo)) {
|
||||
s.inner.SubscribeDevices(ctx, listener)
|
||||
}
|
||||
31
service/usbip/service.go
Normal file
31
service/usbip/service.go
Normal file
@ -0,0 +1,31 @@
|
||||
//go:build with_usbip && (linux || (darwin && cgo) || windows)
|
||||
|
||||
package usbip
|
||||
|
||||
import (
|
||||
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-usbip"
|
||||
)
|
||||
|
||||
func RegisterService(registry *boxService.Registry) {
|
||||
boxService.Register[option.USBIPServerServiceOptions](registry, C.TypeUSBIPServer, NewServerService)
|
||||
boxService.Register[option.USBIPClientServiceOptions](registry, C.TypeUSBIPClient, NewClientService)
|
||||
}
|
||||
|
||||
func toDeviceMatches(matches []option.USBIPDeviceMatch) []usbip.DeviceMatch {
|
||||
if len(matches) == 0 {
|
||||
return nil
|
||||
}
|
||||
deviceMatches := make([]usbip.DeviceMatch, 0, len(matches))
|
||||
for _, match := range matches {
|
||||
deviceMatches = append(deviceMatches, usbip.DeviceMatch{
|
||||
BusID: match.BusID,
|
||||
VendorID: match.VendorID,
|
||||
ProductID: match.ProductID,
|
||||
Serial: match.Serial,
|
||||
})
|
||||
}
|
||||
return deviceMatches
|
||||
}
|
||||
3
service/usbip/stub.go
Normal file
3
service/usbip/stub.go
Normal file
@ -0,0 +1,3 @@
|
||||
//go:build !with_usbip || !(linux || (darwin && cgo) || windows)
|
||||
|
||||
package usbip
|
||||
Loading…
Reference in New Issue
Block a user