From 1615c56504846f405cc0dab35ae4ad078a572654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 16 May 2026 18:40:26 +0800 Subject: [PATCH] usbip: drop standard-mode devlist polling --- service/usbip/client_shared.go | 44 +++++++++++++---------------- service/usbip/linux_interop_test.go | 6 +++- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/service/usbip/client_shared.go b/service/usbip/client_shared.go index cfcc8eabe..39c7a9bee 100644 --- a/service/usbip/client_shared.go +++ b/service/usbip/client_shared.go @@ -24,7 +24,6 @@ const ( controlSessionIdleHint = "control session lost" controlHandshakeBackoffStart = time.Second controlHandshakeBackoffMax = 30 * time.Second - controlHandshakeMaxTransient = 3 ) var ( @@ -90,11 +89,14 @@ func (c *ClientService) run() { } if errors.Is(err, errControlUnsupported) { - c.logger.Info("control channel unsupported by ", c.serverAddr, "; using standard usbip mode") - c.runStandardPollLoop() + c.logger.Info("control channel unsupported by ", c.serverAddr, "; using standard usbip static discovery") + err = c.runStandardStaticMode() if c.ctx.Err() != nil { return } + if err != nil { + c.logger.Error("control ", c.serverAddr, ": ", err) + } transientStreak = 0 backoff = controlHandshakeBackoffStart continue @@ -103,15 +105,6 @@ func (c *ClientService) run() { if errors.Is(err, errControlTransient) { transientStreak++ c.logger.Warn("control handshake ", c.serverAddr, ": ", err) - if transientStreak >= controlHandshakeMaxTransient { - c.logger.Info("control handshake failed ", transientStreak, " times against ", c.serverAddr, "; using standard usbip mode") - c.runStandardPollLoop() - if c.ctx.Err() != nil { - return - } - transientStreak = 0 - backoff = controlHandshakeBackoffStart - } continue } @@ -124,17 +117,17 @@ func (c *ClientService) run() { } } -func (c *ClientService) runStandardPollLoop() { - for { - err := c.syncRemoteStateContext(c.ctx) - if err != nil { - c.logger.Error("control ", c.serverAddr, ": ", E.Cause(err, "devlist sync")) - return - } - if !sleepCtx(c.ctx, clientReconnectDelay) { - return - } +// Dynamic export discovery and hotplug updates are provided by the sing-box +// USB/IP control extensions. Standard USB/IP implementations expose only a +// static DEVLIST snapshot, so we seed assignments once and do not keep polling +// for newly exported devices. +func (c *ClientService) runStandardStaticMode() error { + err := c.syncRemoteStateContext(c.ctx) + if err != nil { + return E.Cause(err, "initial static devlist sync") } + <-c.ctx.Done() + return nil } func (c *ClientService) runControlSession() error { @@ -164,9 +157,10 @@ func (c *ClientService) runControlSession() error { ackMessage, err := cr.read(conn) if err != nil { // A plain usbipd reads our preface as an op-header, finds a bogus - // version, and closes cleanly: the client sees io.EOF. Other I/O - // errors (timeout, RST, partial read) point at a transient - // network problem, not "server lacks CONTROL". + // version, and closes cleanly: the client sees io.EOF. That means the + // peer lacks the sing-box USB/IP control extensions, including dynamic + // export discovery and hotplug updates. Other I/O errors (timeout, RST, + // partial read) point at a transient network problem instead. if errors.Is(err, io.EOF) { return E.Cause(errControlUnsupported, "read control ack: ", err) } diff --git a/service/usbip/linux_interop_test.go b/service/usbip/linux_interop_test.go index 182a2165d..27694c734 100644 --- a/service/usbip/linux_interop_test.go +++ b/service/usbip/linux_interop_test.go @@ -1109,7 +1109,11 @@ func TestUSBIPOfficialServerHasStaticDiscoveryOnly(t *testing.T) { }) beforeHID := importedNodeSnapshot("/dev/hidraw*") - ensureNoNewImportedNode(t, "/dev/hidraw*", beforeHID, 3*time.Second) + // The official server speaks only the base USB/IP protocol. Hotplug follow-up + // is a sing-box control-extension feature, so a device exported after the + // initial DEVLIST snapshot must stay undiscovered even after the old poll + // interval would have elapsed. + ensureNoNewImportedNode(t, "/dev/hidraw*", beforeHID, clientReconnectDelay+2*time.Second) require.NoError(t, client.Close()) waitForAllVHCIPortsIdle(t)