From 001ef124a72d78a745e59bc451ac1a316808eec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 10 Jun 2026 09:25:01 +0800 Subject: [PATCH] usbip: poll the devlist periodically in standard static mode Against a plain usbipd (no control extension) the client synced the device list exactly once and then slept until shutdown: devices that changed busid on replug were never rediscovered, and in all-devices mode a worker that stopped had no path back. Refresh the devlist on an interval, re-probe the control extension every few minutes in case the server was upgraded, and warn that serial rules cannot match against servers whose listings carry no serial numbers. --- service/usbip/client_assignment.go | 11 +++++++++++ service/usbip/client_shared.go | 29 +++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/service/usbip/client_assignment.go b/service/usbip/client_assignment.go index 2ccd211b7..dfd6f9f30 100644 --- a/service/usbip/client_assignment.go +++ b/service/usbip/client_assignment.go @@ -51,6 +51,17 @@ func (a *clientAssignment) Matched() bool { return len(a.targets) > 0 } +// HasSerialTargets reports whether any match rule requires a serial +// number. targets is immutable after construction. +func (a *clientAssignment) HasSerialTargets() bool { + for _, target := range a.targets { + if target.match.Serial != "" { + return true + } + } + return false +} + func (a *clientAssignment) SetActive(busid string, active bool) { a.access.Lock() defer a.access.Unlock() diff --git a/service/usbip/client_shared.go b/service/usbip/client_shared.go index 712abf266..85f354f06 100644 --- a/service/usbip/client_shared.go +++ b/service/usbip/client_shared.go @@ -23,6 +23,13 @@ const ( controlSessionIdleHint = "control session lost" controlHandshakeBackoffStart = time.Second controlHandshakeBackoffMax = 30 * time.Second + + // Standard usbipd has no change notifications; poll the devlist so + // replugged or re-enumerated devices are discovered, and re-probe + // the control extension occasionally in case the server was + // upgraded to sing-box. + staticModeRefreshInterval = 15 * time.Second + staticModeControlRetryInterval = 10 * time.Minute ) var ( @@ -115,12 +122,30 @@ func (c *ClientService) run() { } func (c *ClientService) runStandardStaticMode() error { + if c.assignment.HasSerialTargets() { + c.logger.Warn("serial device matches cannot be evaluated against a standard usbipd server (its device list carries no serial numbers)") + } err := c.syncRemoteStateContext(c.ctx) if err != nil { return E.Cause(err, "initial static devlist sync") } - <-c.ctx.Done() - return nil + refresh := time.NewTicker(staticModeRefreshInterval) + defer refresh.Stop() + retryControl := time.NewTimer(staticModeControlRetryInterval) + defer retryControl.Stop() + for { + select { + case <-c.ctx.Done(): + return nil + case <-retryControl.C: + return nil + case <-refresh.C: + err = c.syncRemoteStateContext(c.ctx) + if err != nil && c.ctx.Err() == nil { + c.logger.Debug("static devlist refresh: ", err) + } + } + } } func (c *ClientService) runControlSession() error {