The install fast path treated an openable VHCI interface as 'driver
present', but the interface GUID is identical across all usbip-win2
releases while PLUGIN_HARDWARE_ONCE and STOP_ATTACH_ATTEMPTS only exist
since 0.9.7.5 — a machine with an older community release passed the
check and then failed every Plugin forever. The fast path now probes
STOP_ATTACH_ATTEMPTS and falls through to the (in-place, FORCE) install
when the driver lacks it. The probe's empty location doubles as
cleanup, discarding ghost reattach attempts left by a previous process.
PLUGIN_HARDWARE_ONCE only suppresses retries of the initial attach;
when an established connection later drops, wsk_receive.cpp
unconditionally schedules background reattach attempts (~20 tries over
~2 hours) toward this session's dead one-shot loopback port. With
ephemeral port reuse those ghost connects steal a later session's
Accept and fail its Plugin. Wire up STOP_ATTACH_ATTEMPTS (0x805,
present in both bundled driver builds) and cancel the session's exact
host/service/busid location before plugout.
The catalog signature covers the INF bytes; git's CRLF-to-LF
normalization made SetupCopyOEMInfW reject the package with
ERROR_FILE_HASH_NOT_IN_CATALOG, so the drivers could never install.
Restore the pristine upstream bytes and mark both driver asset trees
-text so checkout cannot corrupt them again.
VBoxUSBMon only rewrites a device's IDs while PnP enumerates it, so
adding a filter for an already-plugged device captured nothing until a
physical replug, and releasing one left it dead under VBoxUSB.sys.
Capture and release now drive the cfgmgr32 restart sequence
(query-and-remove, hub port cycle, re-setup) around the filter change,
mirroring usbipd-win's RestartingDevice.
Capture also changes the devnode's identity to the VBox stub ID, which
broke everything keyed on it: enumeration now reads the true
vendor/product/class/speed from the parent hub's descriptor cache (the
registry hardware ID reads as 80EE:CAFE once captured), data sessions
locate the VBoxUSB interface by bus/address instead of the original
instance ID, and Reconcile keeps exports alive through the
re-enumeration window with a short absence grace.
Device and Monitor shared one manual-reset event across all in-flight
IOCTLs, while the session layer runs one goroutine per endpoint: the
first completion released every GetOverlappedResult waiter with the
first operation's byte count, returning before the driver finished
writing the other buffers. Each overlappedIoctl call now owns its
event, making concurrent URBs (and aborts) on one handle safe.
USBFILTERTYPE has CAPTURE = 4; the encoded value 5 is END, the enum's
out-of-range sentinel, which USBFilterValidate rejects with a negative
rc — so ADD_FILTER never installed a filter and no device could be
captured.
Every SUPUSB/SUPUSBFLT CTL_CODE was computed with Access = 0 instead of
FILE_WRITE_ACCESS (2 << 14), so the driver dispatch rejected each call
with STATUS_INVALID_DEVICE_REQUEST and the export host could not even
pass monitor GET_VERSION. Values now match usbipd-win's interop
definitions (VBoxUsb.cs / VBoxUsbMon.cs).
Implement the Windows importer over vadimgrn/usbip-win2's UDE (USB Device
Emulation) driver. The driver does the TCP connect and import handshake
in-kernel via WSK, so it cannot take sing-box's already-dialed (and
possibly proxied) socket. Instead each Attach runs a one-shot loopback
listener, points the driver at it with PLUGIN_HARDWARE_ONCE, answers the
driver's in-kernel OP_REQ_IMPORT from the cached device info, and splices
the loopback stream to the proxied server connection -- so all dialer and
proxy behavior stays in userspace.
The Microsoft-signed driver is bundled and auto-installed like
common/vboxusb: amd64 from usbip-win2 0.9.7.7, arm64 from 0.9.7.5 (the
newest release with an arm64 build; identical ABI). EnsureDriver extracts
the package, registers the upper-filter, and creates the root-enumerated
UDE devnode via SetupAPI.
Also fix the control-channel fallback to treat a standard server's
connection reset (ECONNRESET on Windows) the same as a clean EOF, so the
client falls back to standard usbip discovery instead of retrying forever.
Verified on Win11 x64: self-installs the driver on a clean machine and
imports a device; behaves byte-identically to the official usbip-win2
client.
Windows export now reports the real USB link speed, probed from the
parent hub (IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX and _V2 for
SuperSpeedPlus), so SuperSpeed devices route to the correct root-hub
speed domain instead of advertising speed=0.
- protocol: pin DeviceInfoTruncated/DeviceInterface wire sizes with
two-sided compile-time assertions so a struct change fails the build
instead of silently mis-bounding the reader
- server: bound inbound connections with a handshake read deadline and a
per-iteration idle deadline on the control loop, plus write deadlines
on control writes; clear the deadline before the conn becomes a data
session
- server: serialize import reservation under reconcileAccess so a reserve
cannot interleave a reconcile pass that would release a busy device
- data: validate CMD_SUBMIT iso descriptor offset/length against the
transfer buffer before forwarding to a platform engine
- darwin: make darwinUSBHostDevice.Close idempotent via sync.Once to
avoid a double close/free under concurrent shutdown
- windows: guard windowsExport.device with a mutex and hand the claimed
handle to a single closer
The VBoxUSB.sys / VBoxUSBMon.sys / .inf / .cat binaries copied from
dorssel/usbipd-win Drivers/{x64,arm64}/ ship paired SPDX
*.license files that record Oracle's GPL-3.0-only copyright. Carrying
them next to the assets keeps the provenance discoverable.
Both the existing darwin backend and the upcoming windows backend
drive USB devices from user space (IOUSBHost CGO calls vs. VBoxUSB
IOCTLs). Refactor the per-attachment URB loop out of host_darwin.go
into a platform-agnostic userspaceURBSession that talks to a URBEngine
interface; the darwin-specific dispatch becomes a 30-line
darwinIOUSBHostEngine. Linux's kernelHandoffSession is untouched.
Move hex8 into shared.go and add usbipStatusEIO so the shared session
does not depend on golang.org/x/sys/unix (Windows has no equivalent).
Serialize probe rounds in startProber to eliminate unbounded fan-out of
fire-and-forget probe goroutines (up to 100/sec per direction), and close
HTTP/3 transports via transport.Close() in addition to CloseIdleConnections.