From 7df951e82c01c3b0dec1bb860398f95f4bd4e11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 10 Jun 2026 09:24:08 +0800 Subject: [PATCH] usbip: cancel driver reattach attempts on session teardown 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. --- common/usbipvhci/controller_windows.go | 29 ++++++++++++++++++++++++++ common/usbipvhci/usbipvhci.go | 18 ++++++++++++++-- service/usbip/client_windows.go | 19 +++++++++++++---- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/common/usbipvhci/controller_windows.go b/common/usbipvhci/controller_windows.go index e43da7fd6..fae7ab46b 100644 --- a/common/usbipvhci/controller_windows.go +++ b/common/usbipvhci/controller_windows.go @@ -101,6 +101,35 @@ func (c *Controller) Plugin(host, service, busid string) (int, error) { return port, nil } +// StopAttachAttempts cancels the driver's scheduled background +// reconnect attempts for the given location (the exact host/service/ +// busid triple passed to Plugin), or for every location when all three +// are empty. Returns the number of canceled requests. Must be called +// on session teardown: the dropped connection's reattach attempts +// target a one-shot loopback port that no longer exists and would +// otherwise steal a later session's Accept. +func (c *Controller) StopAttachAttempts(host, service, busid string) (int, error) { + var buf [stopAttachAttemptsSize]byte + binary.LittleEndian.PutUint32(buf[offsetPluginSize:], stopAttachAttemptsSize) + err := putCString(buf[offsetPluginBusID:offsetPluginBusID+BusIDSize], busid) + if err != nil { + return 0, E.Cause(err, "usbipvhci: busid") + } + err = putCString(buf[offsetPluginService:offsetPluginService+serviceSize], service) + if err != nil { + return 0, E.Cause(err, "usbipvhci: service") + } + err = putCString(buf[offsetPluginHost:offsetPluginHost+hostSize], host) + if err != nil { + return 0, E.Cause(err, "usbipvhci: host") + } + _, err = c.ioctl(ioctlStopAttachAttempts, buf[:], buf[:]) + if err != nil { + return 0, E.Cause(err, "usbipvhci: STOP_ATTACH_ATTEMPTS") + } + return int(int32(binary.LittleEndian.Uint32(buf[offsetStopAttachAttemptsCount:]))), nil +} + // Plugout detaches the device on the given hub port. PortAll detaches // every device. A port the driver already tore down (after its socket // dropped) reports STATUS_DEVICE_NOT_CONNECTED, surfaced as an error. diff --git a/common/usbipvhci/usbipvhci.go b/common/usbipvhci/usbipvhci.go index c5395849c..70a77a43b 100644 --- a/common/usbipvhci/usbipvhci.go +++ b/common/usbipvhci/usbipvhci.go @@ -21,11 +21,16 @@ const ( // IOCTL codes: CTL_CODE(FILE_DEVICE_UNKNOWN, function, METHOD_BUFFERED, // FILE_READ_DATA|FILE_WRITE_DATA). Function values < 0x800 are reserved -// for Microsoft. PLUGIN_HARDWARE_ONCE attaches without the driver's own -// auto-reconnect — sing-box owns reconnection at the service layer. +// for Microsoft. PLUGIN_HARDWARE_ONCE only suppresses retries of the +// initial attach (vhci_ioctl.cpp checks one_attempt in the connect +// completion alone); when an established connection later drops, +// wsk_receive.cpp unconditionally schedules reattach attempts toward +// the by-then-dead one-shot loopback port, so every teardown must +// cancel them via STOP_ATTACH_ATTEMPTS. const ( ioctlPluginHardwareOnce uint32 = 0x0022_E018 // function 0x806 ioctlPlugoutHardware uint32 = 0x0022_E004 // function 0x801 + ioctlStopAttachAttempts uint32 = 0x0022_E014 // function 0x805 ) // Field offsets within usbip::vhci::ioctl::plugin_hardware, which is @@ -48,6 +53,15 @@ const pluginHardwareSize = 1100 // plugoutHardwareSize is sizeof(plugout_hardware): ULONG size + int port. const plugoutHardwareSize = 8 +// stopAttachAttemptsSize is sizeof(ioctl::stop_attach_attempts): +// base + imported_device_location (1097) padded to 1100 for the +// trailing `int count` (OUT), total 1104. Present in both bundled +// driver versions; absent before 0.9.7.5. +const ( + stopAttachAttemptsSize = 1104 + offsetStopAttachAttemptsCount = 1100 +) + // PortAll detaches every imported device (PORT_ALL = -1). const PortAll = -1 diff --git a/service/usbip/client_windows.go b/service/usbip/client_windows.go index d83b4a401..bfabaa699 100644 --- a/service/usbip/client_windows.go +++ b/service/usbip/client_windows.go @@ -90,8 +90,9 @@ type windowsClientSession struct { ctx context.Context cancel context.CancelFunc - listener net.Listener - hubPort int + listener net.Listener + relayService string // loopback port passed to Plugin, for StopAttachAttempts + hubPort int connAccess sync.Mutex driverConn net.Conn @@ -132,7 +133,8 @@ func (s *windowsClientSession) start(ctx context.Context) error { // Plugin blocks until the driver has connected to the loopback // listener and the import handshake (run by acceptAndRelay) completed. port := listener.Addr().(*net.TCPAddr).Port - hubPort, err := s.controller.Plugin(loopbackHost, strconv.Itoa(port), s.info.BusIDString()) + s.relayService = strconv.Itoa(port) + hubPort, err := s.controller.Plugin(loopbackHost, s.relayService, s.info.BusIDString()) if err != nil { s.cancel() s.markDone() @@ -344,8 +346,17 @@ func (s *windowsClientSession) Start() error { func (s *windowsClientSession) Close() error { s.closeOnce.Do(func() { + // If the connection already dropped, the driver has scheduled + // background reattach attempts toward this session's dead + // loopback port; cancel them before detaching. + count, err := s.controller.StopAttachAttempts(loopbackHost, s.relayService, s.info.BusIDString()) + if err != nil { + s.logger.Debug("usbip windows: stop attach attempts: ", err) + } else if count > 0 { + s.logger.Debug("usbip windows: canceled ", count, " driver reattach attempts") + } if s.hubPort > 0 { - err := s.controller.Plugout(s.hubPort) + err = s.controller.Plugout(s.hubPort) if err != nil { s.logger.Debug("usbip windows: plugout port ", s.hubPort, ": ", err) }