The HID failure came down to the endpoint type map being indexed by endpoint number without the direction bit. A composite device can have endpoint 1 as both interrupt IN (0x81) and bulk OUT (0x01); the last one read won, so interrupt URBs were submitted as bulk and the kernel rejected them. The device attached and stayed silent. Endpoint data now comes from the raw descriptors read from /dev/bus/usb rather than sysfs, which only ever exposes the active alternate setting — a webcam's isochronous endpoints are invisible there because they only exist after SET_INTERFACE. Two sysfs parsing bugs fell out of that too: the numeric endpoint attributes are hex without a prefix (wMaxPacketSize "0040" was read as 40, not 64), and bInterval was never read at all. Reliability: three places could freeze the whole process. The share path fed io.Pipe from the WebSocket read loop, so one slow USB transfer stalled every tunnel and the keepalives with them. The relay wrote to client sockets while holding the hub lock, so one peer that stopped reading blocked routing and registration for everyone. Control transfers ran inline in the protocol loop behind a 5s timeout. Also fixed: a use-after- free where a discarded URB's memory could be collected while the kernel still owned it, a reap loop that spun at 100% CPU on ioctl errors, a missing attach timeout, a double close(done) panic, and Hash[:8] in the relay's log line, which let a client with a short hash take the server down. Adds mode "both", so one client can offer and consume devices at once. The tunnel and client-left callbacks became multicast for it: as plain fields the second manager to register silently unhooked the first. Tunnel traffic is now AES-256-GCM end to end, on the relay path as well as directly. The key is derived from the three tokens, not from the group hash — the relay is told the hash, so a key derived from it would protect nothing from the one party in the middle. Group IDs are unchanged, so existing setups keep working; only clients configured without the tokens drop to unencrypted, relay-only operation. Peers now try to connect directly, with the relay supplying the public address neither side can determine for itself. Candidates are raced because an unreachable address hangs until timeout rather than refusing. Falling back to the relay is not an error. Platform reach: cross-compiled targets for ARM, MIPS and RISC-V (the Linux client needed no code changes — usbdevfs is not architecture specific), multi-arch Docker images, an Android bridge that accepts devices over SCM_RIGHTS because apps cannot open /dev/bus/usb, and macOS builds via system_profiler enumeration. Adds a Windows KMDF filter driver under driver/windows with its Go side. UNTESTED: it has never been compiled or run, needs the WDK to build and an EV certificate to distribute. Treat it as a starting point. Adds "usb-client diag": says per machine whether sharing and using are possible, what stands in the way, and what fixes it. Reports can be uploaded to a relay to get them off machines that are awkward to copy from. 96 tests, all green under -race. Builds for linux, windows and darwin on amd64 and arm64. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
163 lines
4.8 KiB
C
163 lines
4.8 KiB
C
/*
|
|
* usbshare - public interface
|
|
*
|
|
* Shared between the kernel driver and the user mode client. Keep this file
|
|
* in sync with internal/usb/driver_windows.go: both sides marshal the same
|
|
* structures, and a mismatch corrupts memory rather than failing cleanly.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <initguid.h>
|
|
|
|
/*
|
|
* Device interface GUID. User mode enumerates this to find devices that have
|
|
* the filter attached.
|
|
*
|
|
* Generate a fresh GUID if you fork this driver: two drivers exposing the
|
|
* same interface would be indistinguishable to clients.
|
|
*/
|
|
// {8F3D2A14-6C7B-4E59-9A1D-3F5B7C8E2D40}
|
|
DEFINE_GUID(GUID_DEVINTERFACE_USBSHARE,
|
|
0x8f3d2a14, 0x6c7b, 0x4e59, 0x9a, 0x1d, 0x3f, 0x5b, 0x7c, 0x8e, 0x2d, 0x40);
|
|
|
|
#define USBSHARE_DEVICE_TYPE 0x8000
|
|
|
|
#define USBSHARE_IOCTL(index) \
|
|
CTL_CODE(USBSHARE_DEVICE_TYPE, 0x800 + (index), METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
|
|
/*
|
|
* Take exclusive control of the device.
|
|
*
|
|
* While claimed the filter stops passing the class driver's requests down, so
|
|
* the device stops responding to the local system and answers only to URBs
|
|
* submitted here. The claim is bound to the file handle: closing it — or the
|
|
* process dying — releases the device, which is what stops a crashed client
|
|
* from leaving hardware permanently stuck.
|
|
*
|
|
* Input: none
|
|
* Output: USBSHARE_DEVICE_INFO
|
|
*/
|
|
#define IOCTL_USBSHARE_CLAIM USBSHARE_IOCTL(0)
|
|
|
|
/* Release the device back to its class driver. Input/output: none. */
|
|
#define IOCTL_USBSHARE_RELEASE USBSHARE_IOCTL(1)
|
|
|
|
/*
|
|
* Read the raw descriptor blob: device descriptor followed by every
|
|
* configuration descriptor, the same layout a Linux usbdevfs read returns.
|
|
*
|
|
* Input: none
|
|
* Output: raw bytes; STATUS_BUFFER_TOO_SMALL reports the needed size
|
|
*/
|
|
#define IOCTL_USBSHARE_GET_DESCRIPTORS USBSHARE_IOCTL(2)
|
|
|
|
/*
|
|
* Submit a transfer. Completion is asynchronous: the request stays pending
|
|
* until the device answers.
|
|
*
|
|
* Input: USBSHARE_TRANSFER followed by the payload for OUT transfers
|
|
* Output: USBSHARE_TRANSFER_RESULT followed by the payload for IN transfers
|
|
*/
|
|
#define IOCTL_USBSHARE_SUBMIT USBSHARE_IOCTL(3)
|
|
|
|
/*
|
|
* Cancel a previously submitted transfer.
|
|
*
|
|
* Input: USBSHARE_CANCEL
|
|
* Output: none
|
|
*/
|
|
#define IOCTL_USBSHARE_CANCEL USBSHARE_IOCTL(4)
|
|
|
|
/* Select an alternate setting. Input: USBSHARE_SET_INTERFACE. */
|
|
#define IOCTL_USBSHARE_SET_INTERFACE USBSHARE_IOCTL(5)
|
|
|
|
/* Clear a stall on an endpoint. Input: USBSHARE_CLEAR_HALT. */
|
|
#define IOCTL_USBSHARE_CLEAR_HALT USBSHARE_IOCTL(6)
|
|
|
|
/* Reset the port. Input/output: none. */
|
|
#define IOCTL_USBSHARE_RESET USBSHARE_IOCTL(7)
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
/* Transfer types, matching the USB endpoint attribute values. */
|
|
#define USBSHARE_TRANSFER_CONTROL 0
|
|
#define USBSHARE_TRANSFER_ISOCHRONOUS 1
|
|
#define USBSHARE_TRANSFER_BULK 2
|
|
#define USBSHARE_TRANSFER_INTERRUPT 3
|
|
|
|
/* Direction, taken from the endpoint address bit 7. */
|
|
#define USBSHARE_DIR_OUT 0
|
|
#define USBSHARE_DIR_IN 1
|
|
|
|
typedef struct _USBSHARE_DEVICE_INFO {
|
|
USHORT VendorId;
|
|
USHORT ProductId;
|
|
USHORT BcdDevice;
|
|
UCHAR DeviceClass;
|
|
UCHAR DeviceSubClass;
|
|
UCHAR DeviceProtocol;
|
|
UCHAR ConfigurationValue;
|
|
UCHAR NumConfigurations;
|
|
/* USB_DEVICE_SPEED_* from usbdi.h, translated by the client. */
|
|
ULONG Speed;
|
|
/* Hub port number, used to build a stable bus ID. */
|
|
ULONG PortNumber;
|
|
} USBSHARE_DEVICE_INFO, *PUSBSHARE_DEVICE_INFO;
|
|
|
|
typedef struct _USBSHARE_TRANSFER {
|
|
/* Caller-assigned, unique among outstanding transfers. Used to cancel. */
|
|
ULONG64 Id;
|
|
|
|
/* Full bEndpointAddress including the direction bit. */
|
|
UCHAR EndpointAddress;
|
|
|
|
/* USBSHARE_TRANSFER_* */
|
|
UCHAR Type;
|
|
|
|
/* USBSHARE_DIR_*, redundant with the address bit but explicit. */
|
|
UCHAR Direction;
|
|
|
|
UCHAR Reserved;
|
|
|
|
/* Bytes of payload following this header (OUT), or expected (IN). */
|
|
ULONG BufferLength;
|
|
|
|
/* Milliseconds; 0 means no timeout. */
|
|
ULONG Timeout;
|
|
|
|
/*
|
|
* Setup packet for control transfers, in USB wire order (little endian).
|
|
* Ignored for other types.
|
|
*/
|
|
UCHAR Setup[8];
|
|
} USBSHARE_TRANSFER, *PUSBSHARE_TRANSFER;
|
|
|
|
typedef struct _USBSHARE_TRANSFER_RESULT {
|
|
ULONG64 Id;
|
|
|
|
/* NTSTATUS from the USB stack; 0 means success. */
|
|
LONG Status;
|
|
|
|
/* USBD_STATUS, kept separate because it distinguishes stall from timeout. */
|
|
ULONG UsbdStatus;
|
|
|
|
/* Bytes actually transferred. Meaningful for OUT transfers too. */
|
|
ULONG ActualLength;
|
|
} USBSHARE_TRANSFER_RESULT, *PUSBSHARE_TRANSFER_RESULT;
|
|
|
|
typedef struct _USBSHARE_CANCEL {
|
|
ULONG64 Id;
|
|
} USBSHARE_CANCEL, *PUSBSHARE_CANCEL;
|
|
|
|
typedef struct _USBSHARE_SET_INTERFACE {
|
|
UCHAR InterfaceNumber;
|
|
UCHAR AlternateSetting;
|
|
} USBSHARE_SET_INTERFACE, *PUSBSHARE_SET_INTERFACE;
|
|
|
|
typedef struct _USBSHARE_CLEAR_HALT {
|
|
UCHAR EndpointAddress;
|
|
} USBSHARE_CLEAR_HALT, *PUSBSHARE_CLEAR_HALT;
|
|
|
|
#pragma pack(pop)
|