usbip: drop standard-mode devlist polling

This commit is contained in:
世界
2026-05-16 18:40:26 +08:00
parent dcea94559d
commit 1615c56504
2 changed files with 24 additions and 26 deletions
+19 -25
View File
@@ -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)
}
+5 -1
View File
@@ -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)