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 {