From 14778f5961bb83b680d3f533d1be16bff2b476ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 13 May 2026 13:20:31 +0800 Subject: [PATCH] usbip: inline single-caller wrappers and drop dead code Inline thin pass-through helpers (< 10 lines, < 3 non-test callers) and the writeSysfs path wrappers; delete the test-only vhciUsedPorts plus its singleflight machinery and the never-called clientAssignment.IsActive. --- service/usbip/client.go | 6 +- service/usbip/client_assignment.go | 18 +---- service/usbip/client_control.go | 12 ---- service/usbip/client_darwin.go | 19 ++--- service/usbip/client_shared.go | 38 +++++++--- service/usbip/control_protocol.go | 63 ++++++----------- service/usbip/export_ledger.go | 22 +++--- service/usbip/handoff_linux.go | 11 --- service/usbip/host_linux.go | 84 +++++++++++++--------- service/usbip/linux_interop_test.go | 24 ++++--- service/usbip/linux_test.go | 30 ++++---- service/usbip/shared.go | 11 --- service/usbip/sysfs_linux.go | 106 +--------------------------- 13 files changed, 151 insertions(+), 293 deletions(-) diff --git a/service/usbip/client.go b/service/usbip/client.go index 6f45313b0..13b7f5c38 100644 --- a/service/usbip/client.go +++ b/service/usbip/client.go @@ -116,10 +116,10 @@ func (c *ClientService) runBusIDLoop(ctx context.Context, busid, description str if ctx.Err() != nil { return } - c.setBusIDActive(busid, true) + c.assignment.SetActive(busid, true) session, err := c.attemptAttach(ctx, busid) if err != nil { - c.setBusIDActive(busid, false) + c.assignment.SetActive(busid, false) c.logger.Error("attach ", description, " (", busid, "): ", err) if !sleepCtx(ctx, clientReconnectDelay) { return @@ -135,7 +135,7 @@ func (c *ClientService) runBusIDLoop(ctx context.Context, busid, description str <-session.Done() } _ = session.Close() - c.setBusIDActive(busid, false) + c.assignment.SetActive(busid, false) if ctx.Err() != nil { return } diff --git a/service/usbip/client_assignment.go b/service/usbip/client_assignment.go index a9ad696b3..def740a36 100644 --- a/service/usbip/client_assignment.go +++ b/service/usbip/client_assignment.go @@ -30,7 +30,7 @@ func newClientAssignment(matches []option.USBIPDeviceMatch) *clientAssignment { seenFixed := make(map[string]struct{}) targets = make([]clientTarget, 0, len(matches)) for _, m := range matches { - if isBusIDOnlyMatch(m) { + if m.BusID != "" && m.VendorID == 0 && m.ProductID == 0 && m.Serial == "" { if _, seen := seenFixed[m.BusID]; seen { continue } @@ -63,13 +63,6 @@ func (a *clientAssignment) SetActive(busid string, active bool) { } } -func (a *clientAssignment) IsActive(busid string) bool { - a.access.Lock() - defer a.access.Unlock() - _, exists := a.activeBusIDs[busid] - return exists -} - func (a *clientAssignment) ApplyMatched(entries []DeviceEntry, knownKeys map[string]DeviceKey) (next []string, previous []string) { a.access.Lock() defer a.access.Unlock() @@ -133,15 +126,6 @@ func (a *clientAssignment) IsRetryDesired(busid string) bool { return desired } -func (a *clientAssignment) ClearRegistered() { - a.access.Lock() - defer a.access.Unlock() - if len(a.registered) == 0 { - return - } - a.registered = make(map[string]struct{}) -} - func (a *clientAssignment) matchedKeysForAssignmentLocked(entries []DeviceEntry, knownKeys map[string]DeviceKey) map[string]DeviceKey { if len(a.matchedKnownKeys) == 0 && len(entries) == 0 && len(knownKeys) == 0 { return nil diff --git a/service/usbip/client_control.go b/service/usbip/client_control.go index f2b41274a..b75c530d5 100644 --- a/service/usbip/client_control.go +++ b/service/usbip/client_control.go @@ -144,18 +144,6 @@ func (c *ClientService) requestImportLease(ctx context.Context, busid string) (c }, nil } -func (c *ClientService) runStandardSessionWithInterval(interval time.Duration) error { - for { - err := c.syncRemoteStateContext(c.ctx) - if err != nil { - return E.Cause(err, "devlist sync") - } - if !sleepCtx(c.ctx, interval) { - return nil - } - } -} - func (c *ClientService) applyControlSnapshot(snapshot controlDeviceSnapshot) { devices := deviceInfoV2Map(snapshot.Devices) values := sortedDeviceInfoV2Values(devices) diff --git a/service/usbip/client_darwin.go b/service/usbip/client_darwin.go index d498cdcbe..1026b2a87 100644 --- a/service/usbip/client_darwin.go +++ b/service/usbip/client_darwin.go @@ -151,9 +151,12 @@ func (c *darwinVirtualController) readLoop() { } switch header.Command { case RetSubmit: - payloadDirection, ok := c.pendingSubmitDirection(header.SeqNum) - if !ok { - payloadDirection = header.Direction + c.pendingAccess.Lock() + pending, ok := c.pending[header.SeqNum] + c.pendingAccess.Unlock() + payloadDirection := header.Direction + if ok { + payloadDirection = pending.direction } response, err := ReadSubmitResponseBody(c.conn, header, payloadDirection) if err != nil { @@ -577,16 +580,6 @@ func (c *darwinVirtualController) sendSubmit(command SubmitCommand) (SubmitRespo } } -func (c *darwinVirtualController) pendingSubmitDirection(seq uint32) (uint32, bool) { - c.pendingAccess.Lock() - defer c.pendingAccess.Unlock() - pending, ok := c.pending[seq] - if !ok { - return 0, false - } - return pending.direction, true -} - func (c *darwinVirtualController) deliverSubmit(response SubmitResponse) { c.pendingAccess.Lock() pending, ok := c.pending[response.Header.SeqNum] diff --git a/service/usbip/client_shared.go b/service/usbip/client_shared.go index b737fd184..68291c695 100644 --- a/service/usbip/client_shared.go +++ b/service/usbip/client_shared.go @@ -9,6 +9,7 @@ import ( "slices" "time" + "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" ) @@ -60,7 +61,17 @@ func (c *ClientService) run() { err := c.runControlSession() if errors.Is(err, errControlUnsupported) { c.logger.Info("control channel unsupported by ", c.serverAddr, "; using standard usbip mode") - err = c.runStandardSessionWithInterval(clientReconnectDelay) + for { + err = c.syncRemoteStateContext(c.ctx) + if err != nil { + err = E.Cause(err, "devlist sync") + break + } + if !sleepCtx(c.ctx, clientReconnectDelay) { + err = nil + break + } + } } if c.ctx.Err() != nil { break @@ -266,7 +277,12 @@ func (c *ClientService) applyRemoteDeviceState(devices []DeviceInfoV2) { if device.BusID == "" { continue } - knownKeys[device.BusID] = device.key() + knownKeys[device.BusID] = DeviceKey{ + BusID: device.BusID, + VendorID: device.VendorID, + ProductID: device.ProductID, + Serial: device.Serial, + } } c.applyMatchedExportsWithRetained(availableEntries, knownKeys) } @@ -348,12 +364,16 @@ func (c *ClientService) runAssignedWorker(worker *clientAssignedWorker) { runnerCancel = cancel runnerDone = done + match := worker.target.match + if worker.target.fixedBusID != "" { + match = option.USBIPDeviceMatch{BusID: worker.target.fixedBusID} + } c.wg.Add(1) - go func(busid string) { + go func(busid, description string) { defer c.wg.Done() defer close(done) - c.runBusIDLoop(runCtx, busid, worker.target.description()) - }(desired) + c.runBusIDLoop(runCtx, busid, description) + }(desired, describeMatch(match)) } } } @@ -386,7 +406,9 @@ func (c *ClientService) startRemoteBusIDWorker(busid, description string) { } func (c *ClientService) stopAllWorkers() { - c.assignment.ClearRegistered() + c.assignment.access.Lock() + c.assignment.registered = make(map[string]struct{}) + c.assignment.access.Unlock() c.workerAccess.Lock() cancels := make([]context.CancelFunc, 0, len(c.allWorkers)) @@ -427,10 +449,6 @@ func (c *ClientService) fetchDevList(ctx context.Context) ([]DeviceEntry, error) return ReadOpRepDevListBody(conn) } -func (c *ClientService) setBusIDActive(busid string, active bool) { - c.assignment.SetActive(busid, active) -} - func (c *ClientService) shouldRetryBusID(ctx context.Context, busid string) bool { if c.assignment.Matched() { return true diff --git a/service/usbip/control_protocol.go b/service/usbip/control_protocol.go index ff806b791..1d0a0922b 100644 --- a/service/usbip/control_protocol.go +++ b/service/usbip/control_protocol.go @@ -247,44 +247,6 @@ func deviceInfoV2FromEntry(entry DeviceEntry, backend string, stableID string, s } } -func (d DeviceInfoV2) toDeviceEntry() DeviceEntry { - var info DeviceInfoTruncated - encodePathField(&info.Path, d.Path) - copy(info.BusID[:], d.BusID) - info.Speed = d.Speed - info.IDVendor = d.VendorID - info.IDProduct = d.ProductID - info.BCDDevice = d.BCDDevice - info.BDeviceClass = d.DeviceClass - info.BDeviceSubClass = d.DeviceSubClass - info.BDeviceProtocol = d.DeviceProtocol - info.BConfigurationValue = d.ConfigurationValue - info.BNumConfigurations = d.NumConfigurations - info.BNumInterfaces = d.NumInterfaces - interfaces := make([]DeviceInterface, len(d.Interfaces)) - for i := range d.Interfaces { - interfaces[i] = DeviceInterface{ - BInterfaceClass: d.Interfaces[i].Class, - BInterfaceSubClass: d.Interfaces[i].SubClass, - BInterfaceProtocol: d.Interfaces[i].Protocol, - } - } - return DeviceEntry{Info: info, Interfaces: interfaces, Serial: d.Serial} -} - -func (d DeviceInfoV2) key() DeviceKey { - return DeviceKey{ - BusID: d.BusID, - VendorID: d.VendorID, - ProductID: d.ProductID, - Serial: d.Serial, - } -} - -func (d DeviceInfoV2) available() bool { - return d.State == "" || d.State == deviceStateAvailable -} - func deviceInfoV2Map(devices []DeviceInfoV2) map[string]DeviceInfoV2 { out := make(map[string]DeviceInfoV2, len(devices)) for _, device := range devices { @@ -312,10 +274,31 @@ func sortedDeviceInfoV2Values(devices map[string]DeviceInfoV2) []DeviceInfoV2 { func deviceInfoV2ToEntries(devices []DeviceInfoV2, availableOnly bool) []DeviceEntry { entries := make([]DeviceEntry, 0, len(devices)) for _, device := range devices { - if availableOnly && !device.available() { + if availableOnly && device.State != "" && device.State != deviceStateAvailable { continue } - entries = append(entries, device.toDeviceEntry()) + var info DeviceInfoTruncated + encodePathField(&info.Path, device.Path) + copy(info.BusID[:], device.BusID) + info.Speed = device.Speed + info.IDVendor = device.VendorID + info.IDProduct = device.ProductID + info.BCDDevice = device.BCDDevice + info.BDeviceClass = device.DeviceClass + info.BDeviceSubClass = device.DeviceSubClass + info.BDeviceProtocol = device.DeviceProtocol + info.BConfigurationValue = device.ConfigurationValue + info.BNumConfigurations = device.NumConfigurations + info.BNumInterfaces = device.NumInterfaces + interfaces := make([]DeviceInterface, len(device.Interfaces)) + for i := range device.Interfaces { + interfaces[i] = DeviceInterface{ + BInterfaceClass: device.Interfaces[i].Class, + BInterfaceSubClass: device.Interfaces[i].SubClass, + BInterfaceProtocol: device.Interfaces[i].Protocol, + } + } + entries = append(entries, DeviceEntry{Info: info, Interfaces: interfaces, Serial: device.Serial}) } return entries } diff --git a/service/usbip/export_ledger.go b/service/usbip/export_ledger.go index b3af3dc9b..346c47b13 100644 --- a/service/usbip/export_ledger.go +++ b/service/usbip/export_ledger.go @@ -326,7 +326,15 @@ func (l *exportLedger) Subscribe(ctx context.Context, conn net.Conn, capabilitie } sequence := l.seq if supportsControlExtensions(capabilities) { - l.enqueueSnapshotLocked(sub, sequence, snapshot) + l.enqueuePayload(sub, controlFrame{ + Type: controlFrameDeviceSnapshot, + Version: controlProtocolVersion, + Sequence: sequence, + }, controlDeviceSnapshot{Sequence: sequence, Devices: snapshot}, controlFrame{ + Type: controlFrameChanged, + Version: controlProtocolVersion, + Sequence: sequence, + }) } l.subs[sub.id] = sub return sub, sequence @@ -424,18 +432,6 @@ func (l *exportLedger) snapshotDeviceState(ctx context.Context) []DeviceInfoV2 { return out } -func (l *exportLedger) enqueueSnapshotLocked(sub *exportSubscriber, sequence uint64, devices []DeviceInfoV2) { - l.enqueuePayload(sub, controlFrame{ - Type: controlFrameDeviceSnapshot, - Version: controlProtocolVersion, - Sequence: sequence, - }, controlDeviceSnapshot{Sequence: sequence, Devices: devices}, controlFrame{ - Type: controlFrameChanged, - Version: controlProtocolVersion, - Sequence: sequence, - }) -} - func (l *exportLedger) enqueueFrame(sub *exportSubscriber, frame controlFrame) { select { case sub.send <- controlMessage{Frame: frame}: diff --git a/service/usbip/handoff_linux.go b/service/usbip/handoff_linux.go index 9b2be5c29..6f2d543b3 100644 --- a/service/usbip/handoff_linux.go +++ b/service/usbip/handoff_linux.go @@ -72,17 +72,6 @@ func newKernelHandoffSession(conn net.Conn) (*kernelHandoffSession, error) { }, nil } -func (h *kernelHandoffSession) kernelFD() uintptr { - return h.file.Fd() -} - -func (h *kernelHandoffSession) mode() string { - if h.relayConn != nil { - return "relay" - } - return "direct" -} - func (h *kernelHandoffSession) closeKernelFD() error { if h.file == nil { return nil diff --git a/service/usbip/host_linux.go b/service/usbip/host_linux.go index 3247762d5..706dd4dda 100644 --- a/service/usbip/host_linux.go +++ b/service/usbip/host_linux.go @@ -9,6 +9,7 @@ import ( "net" "os" "path/filepath" + "strconv" "strings" "sync" "time" @@ -28,10 +29,6 @@ func newPlatformImportHost(logger log.ContextLogger) (ImportHost, error) { return newLinuxImportHost(logger), nil } -func sysBusDevicePath(busid string) string { - return sysBusUSBDevices + "/" + busid -} - func isMissingUSBDeviceError(err error) bool { return errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENODEV) } @@ -66,7 +63,7 @@ func newLinuxExportHost(logger log.ContextLogger, matches []option.USBIPDeviceMa } func (h *linuxExportHost) Start(ctx context.Context) error { - return ensureHostDriver() + return ensureKernelPath(sysUsbipHostDriver, "usbip-host", "usbip-host driver") } func (h *linuxExportHost) Close() error { @@ -75,7 +72,7 @@ func (h *linuxExportHost) Close() error { h.exports = make(map[string]*linuxExport) h.access.Unlock() for _, exp := range exports { - _, statErr := os.Stat(sysBusDevicePath(exp.busid)) + _, statErr := os.Stat(filepath.Join(sysBusUSBDevices, exp.busid)) restore := statErr == nil releaseErr := h.releaseExport(exp, restore) if releaseErr != nil { @@ -110,7 +107,10 @@ func (h *linuxExportHost) ueventLoop(ctx context.Context, ch chan<- struct{}) { if !sleepCtx(ctx, backoff) { return } - backoff = nextUEventListenerBackoff(backoff) + backoff *= 2 + if backoff > ueventListenerBackoffMax { + backoff = ueventListenerBackoffMax + } continue } backoff = ueventListenerBackoffInitial @@ -135,7 +135,10 @@ func (h *linuxExportHost) ueventLoop(ctx context.Context, ch chan<- struct{}) { if !sleepCtx(ctx, backoff) { return } - backoff = nextUEventListenerBackoff(backoff) + backoff *= 2 + if backoff > ueventListenerBackoffMax { + backoff = ueventListenerBackoffMax + } break } signal() @@ -148,14 +151,6 @@ const ( ueventListenerBackoffMax = 30 * time.Second ) -func nextUEventListenerBackoff(current time.Duration) time.Duration { - next := current * 2 - if next > ueventListenerBackoffMax { - return ueventListenerBackoffMax - } - return next -} - func (h *linuxExportHost) Reconcile(ctx context.Context, isBusy func(busid string) bool) (map[string]Export, []string, error) { devices, err := listUSBDevices() if err != nil { @@ -168,7 +163,13 @@ func (h *linuxExportHost) Reconcile(ctx context.Context, isBusy func(busid strin } for _, m := range h.matches { for i := range devices { - if !matches(m, devices[i].key()) { + deviceKey := DeviceKey{ + BusID: devices[i].BusID, + VendorID: devices[i].VendorID, + ProductID: devices[i].ProductID, + Serial: devices[i].Serial, + } + if !matches(m, deviceKey) { continue } path := devices[i].Path @@ -231,7 +232,7 @@ func (h *linuxExportHost) Reconcile(ctx context.Context, isBusy func(busid strin } func (h *linuxExportHost) FinishImport(ctx context.Context, busid string) (bool, error) { - err := writeUsbipSockfd(busid, -1) + err := writeSysfs(filepath.Join(sysBusUSBDevices, busid, "usbip_sockfd"), "-1") if err != nil && !os.IsNotExist(err) && !isMissingUSBDeviceError(err) { h.logger.Debug("release ", busid, " from usbip-host: ", err) } @@ -286,23 +287,24 @@ func (h *linuxExportHost) bindOneOnce(d *sysfsDevice) (*linuxExport, error) { return h.newExport(*d, false, ""), nil } if driver != "" { - err = unbindFromDriver(d.BusID, driver) + err = writeSysfs(filepath.Join("/sys/bus/usb/drivers", driver, "unbind"), d.BusID) if err != nil { return nil, E.Cause(err, "unbind from ", driver) } } - err = hostMatchBusID(d.BusID, true) + matchBusIDPath := filepath.Join(sysUsbipHostDriver, "match_busid") + err = writeSysfs(matchBusIDPath, "add "+d.BusID) if err != nil { if driver != "" { - _ = bindToDriver(d.BusID, driver) + _ = writeSysfs(filepath.Join("/sys/bus/usb/drivers", driver, "bind"), d.BusID) } return nil, E.Cause(err, "match_busid add") } - err = hostBind(d.BusID) + err = writeSysfs(filepath.Join(sysUsbipHostDriver, "bind"), d.BusID) if err != nil { - _ = hostMatchBusID(d.BusID, false) + _ = writeSysfs(matchBusIDPath, "del "+d.BusID) if driver != "" { - _ = bindToDriver(d.BusID, driver) + _ = writeSysfs(filepath.Join("/sys/bus/usb/drivers", driver, "bind"), d.BusID) } return nil, E.Cause(err, "bind to usbip-host") } @@ -324,16 +326,16 @@ func (h *linuxExportHost) releaseExport(exp *linuxExport, restore bool) error { return statusErr } if statusErr == nil && status == usbipStatusUsed { - err := writeUsbipSockfd(exp.busid, -1) + err := writeSysfs(filepath.Join(sysBusUSBDevices, exp.busid, "usbip_sockfd"), "-1") if err != nil && !os.IsNotExist(err) { return err } } - err := hostUnbind(exp.busid) + err := writeSysfs(filepath.Join(sysUsbipHostDriver, "unbind"), exp.busid) if err != nil && !os.IsNotExist(err) && !(isMissingUSBDeviceError(err) && !restore) { return err } - err = hostMatchBusID(exp.busid, false) + err = writeSysfs(filepath.Join(sysUsbipHostDriver, "match_busid"), "del "+exp.busid) if err != nil { return err } @@ -345,7 +347,7 @@ func (h *linuxExportHost) releaseExport(exp *linuxExport, restore bool) error { h.logger.Info("released ", exp.busid, " from usbip-host") return nil } - err = bindToDriver(exp.busid, exp.originalDriver) + err = writeSysfs(filepath.Join("/sys/bus/usb/drivers", exp.originalDriver, "bind"), exp.busid) if err != nil { return err } @@ -403,7 +405,11 @@ func (e *linuxExport) Snapshot(ctx context.Context, busy bool) ExportSnapshot { reason = linuxUSBIPStatusReason(status) } return ExportSnapshot{ - Entry: e.descriptor.toDeviceEntry(), + Entry: DeviceEntry{ + Info: e.descriptor.toProtocol(), + Interfaces: e.descriptor.Interfaces, + Serial: e.descriptor.Serial, + }, Backend: backendIDLinuxSysfs, StableID: stableID, State: state, @@ -432,8 +438,12 @@ func (e *linuxExport) NewServerDataSession(ctx context.Context, conn net.Conn) ( if err != nil { return nil, E.Cause(err, "prepare handoff") } - e.logger.Debug("usbip server handoff ", e.busid, ": ", handoff.mode()) - err = writeUsbipSockfd(e.busid, int(handoff.kernelFD())) + mode := "direct" + if handoff.relayConn != nil { + mode = "relay" + } + e.logger.Debug("usbip server handoff ", e.busid, ": ", mode) + err = writeSysfs(filepath.Join(sysBusUSBDevices, e.busid, "usbip_sockfd"), strconv.Itoa(int(handoff.file.Fd()))) if err != nil { _ = handoff.Close() return nil, E.Cause(err, "hand off ", e.busid, " to kernel") @@ -461,7 +471,7 @@ func newLinuxImportHost(logger log.ContextLogger) *linuxImportHost { } func (h *linuxImportHost) Start(ctx context.Context) error { - return ensureVHCI() + return ensureKernelPath(sysVHCIControllerV0, "vhci-hcd", "vhci_hcd.0") } func (h *linuxImportHost) Close() error { @@ -473,7 +483,11 @@ func (h *linuxImportHost) Attach(ctx context.Context, info DeviceInfoTruncated, if err != nil { return nil, E.Cause(err, "prepare handoff") } - h.logger.Debug("usbip client handoff ", info.BusIDString(), ": ", handoff.mode()) + mode := "direct" + if handoff.relayConn != nil { + mode = "relay" + } + h.logger.Debug("usbip client handoff ", info.BusIDString(), ": ", mode) port, attachErr := h.attachOnce(ctx, info, handoff) if attachErr != nil { _ = handoff.Close() @@ -498,7 +512,7 @@ func (h *linuxImportHost) attachOnce(ctx context.Context, info DeviceInfoTruncat triedPorts[port] = struct{}{} continue } - err = vhciAttach(port, handoff.kernelFD(), info.DevID(), info.Speed) + err = vhciAttach(port, handoff.file.Fd(), info.DevID(), info.Speed) if err != nil { h.releasePort(port) if errors.Is(err, unix.EBUSY) { @@ -553,7 +567,7 @@ func (s *linuxClientSession) Err() error { func (s *linuxClientSession) Close() error { s.closeOnce.Do(func() { - detachErr := vhciDetach(s.port) + detachErr := writeSysfs(filepath.Join(sysVHCIControllerV0, "detach"), strconv.Itoa(s.port)) closeErr := s.handoff.Close() s.host.releasePort(s.port) s.closeErr = E.Errors(detachErr, closeErr) diff --git a/service/usbip/linux_interop_test.go b/service/usbip/linux_interop_test.go index 16f59505a..976af6a3f 100644 --- a/service/usbip/linux_interop_test.go +++ b/service/usbip/linux_interop_test.go @@ -235,7 +235,7 @@ func detachUsedVHCIPorts() { } for _, record := range records { if record.state == 6 { - _ = vhciDetach(record.port) + _ = writeSysfs(filepath.Join(sysVHCIControllerV0, "detach"), strconv.Itoa(record.port)) } } } @@ -261,8 +261,16 @@ func waitForAllVHCIPortsIdle(t *testing.T) { func waitForVHCIPortIdle(t *testing.T, port int) { t.Helper() require.Eventually(t, func() bool { - used, err := vhciUsedPorts() - return err == nil && !used[port] + records, err := readVHCIStatus() + if err != nil { + return true + } + for _, record := range records { + if record.port == port && record.state == 6 { + return false + } + } + return true }, testUSBIPTeardownTimeout, testUSBIPTeardownPollInterval) } @@ -307,12 +315,12 @@ func waitForGadgetNodesGone(nodes map[string]string) bool { func shutdownUSBIPHostDevice(busid string) { status, err := readUsbipStatus(busid) if err == nil && status == usbipStatusUsed { - _ = writeUsbipSockfd(busid, -1) + _ = writeSysfs(filepath.Join(sysBusUSBDevices, busid, "usbip_sockfd"), "-1") _ = waitForUSBIPHostAvailable(busid) } if driver, err := currentDriver(busid); err == nil && driver == "usbip-host" { - _ = hostUnbind(busid) - _ = hostMatchBusID(busid, false) + _ = writeSysfs(filepath.Join(sysUsbipHostDriver, "unbind"), busid) + _ = writeSysfs(filepath.Join(sysUsbipHostDriver, "match_busid"), "del "+busid) _ = waitForDriverAway(busid, "usbip-host") } } @@ -333,7 +341,7 @@ func resetUSBIPInteropState(t *testing.T) { continue } shutdownUSBIPHostDevice(device.BusID) - _ = bindToDriver(device.BusID, "usb") + _ = writeSysfs("/sys/bus/usb/drivers/usb/bind", device.BusID) } paths, _ := filepath.Glob("/sys/kernel/config/usb_gadget/codex_usbip_*") @@ -840,7 +848,7 @@ func (g *testVirtualGadget) Close() { _ = writeSysfsLine(filepath.Join(g.path, "UDC"), "") if g.busid != "" { - _ = waitForSysfsPathGone(sysBusDevicePath(g.busid)) + _ = waitForSysfsPathGone(filepath.Join(sysBusUSBDevices, g.busid)) } _ = waitForGadgetNodesGone(g.nodes) diff --git a/service/usbip/linux_test.go b/service/usbip/linux_test.go index 979d6839b..b07294b0d 100644 --- a/service/usbip/linux_test.go +++ b/service/usbip/linux_test.go @@ -82,7 +82,7 @@ func duplicateConnFromFD(t *testing.T, fd uintptr, name string) net.Conn { func duplicateHandoffKernelConn(t *testing.T, handoff *kernelHandoffSession) net.Conn { t.Helper() - conn := duplicateConnFromFD(t, handoff.kernelFD(), "usbip-test-kernel") + conn := duplicateConnFromFD(t, handoff.file.Fd(), "usbip-test-kernel") require.NoError(t, handoff.closeKernelFD()) return conn } @@ -150,7 +150,7 @@ func requireKernelModule(t *testing.T, module string) { func requireUSBIPHost(t *testing.T) { t.Helper() - err := ensureHostDriver() + err := ensureKernelPath(sysUsbipHostDriver, "usbip-host", "usbip-host driver") if err != nil { t.Skipf("usbip-host unavailable: %v", err) } @@ -158,7 +158,7 @@ func requireUSBIPHost(t *testing.T) { func requireVHCI(t *testing.T) { t.Helper() - err := ensureVHCI() + err := ensureKernelPath(sysVHCIControllerV0, "vhci-hcd", "vhci_hcd.0") if err != nil { t.Skipf("vhci_hcd unavailable: %v", err) } @@ -222,13 +222,13 @@ func newTestUSBGadget(t *testing.T) *testUSBGadget { if err == nil { switch driver { case "usbip-host": - _ = hostUnbind(gadget.busid) - _ = hostMatchBusID(gadget.busid, false) - _ = bindToDriver(gadget.busid, "usb") + _ = writeSysfs(filepath.Join(sysUsbipHostDriver, "unbind"), gadget.busid) + _ = writeSysfs(filepath.Join(sysUsbipHostDriver, "match_busid"), "del "+gadget.busid) + _ = writeSysfs("/sys/bus/usb/drivers/usb/bind", gadget.busid) case "usb": case "": default: - _ = bindToDriver(gadget.busid, "usb") + _ = writeSysfs("/sys/bus/usb/drivers/usb/bind", gadget.busid) } } } @@ -280,8 +280,8 @@ func TestUSBIPConnHandoffDirectTCP(t *testing.T) { require.NoError(t, err) defer handoff.Close() - require.Equal(t, "direct", handoff.mode()) - requireStreamSocketFD(t, handoff.kernelFD()) + require.Nil(t, handoff.relayConn) + requireStreamSocketFD(t, handoff.file.Fd()) handoff.Start(context.Background(), newTestLogger(t), "test", "direct") _, err = conn.Write([]byte("closed")) @@ -302,8 +302,8 @@ func TestUSBIPConnHandoffRelaySocketpairCopies(t *testing.T) { handoff, err := newKernelHandoffSession(opaqueConn{Conn: left}) require.NoError(t, err) defer handoff.Close() - require.Equal(t, "relay", handoff.mode()) - requireStreamSocketFD(t, handoff.kernelFD()) + require.NotNil(t, handoff.relayConn) + requireStreamSocketFD(t, handoff.file.Fd()) kernelConn := duplicateHandoffKernelConn(t, handoff) defer kernelConn.Close() @@ -338,7 +338,7 @@ func TestUSBIPLinuxSmoke(t *testing.T) { requireVHCI(t) gadget := newTestUSBGadget(t) - device, err := readSysfsDevice(gadget.busid, sysBusDevicePath(gadget.busid)) + device, err := readSysfsDevice(gadget.busid, filepath.Join(sysBusUSBDevices, gadget.busid)) require.NoError(t, err) require.Equal(t, gadget.busid, device.BusID) @@ -360,9 +360,9 @@ func TestUSBIPLinuxSmoke(t *testing.T) { require.NoError(t, err) require.Equal(t, usbipStatusAvailable, status) - require.NoError(t, hostUnbind(gadget.busid)) - require.NoError(t, hostMatchBusID(gadget.busid, false)) - require.NoError(t, bindToDriver(gadget.busid, "usb")) + require.NoError(t, writeSysfs(filepath.Join(sysUsbipHostDriver, "unbind"), gadget.busid)) + require.NoError(t, writeSysfs(filepath.Join(sysUsbipHostDriver, "match_busid"), "del "+gadget.busid)) + require.NoError(t, writeSysfs("/sys/bus/usb/drivers/usb/bind", gadget.busid)) deleteLinuxExport(host, gadget.busid) driver, err = currentDriver(gadget.busid) diff --git a/service/usbip/shared.go b/service/usbip/shared.go index 8048ba2dc..38e7e960b 100644 --- a/service/usbip/shared.go +++ b/service/usbip/shared.go @@ -17,17 +17,6 @@ type clientTarget struct { match option.USBIPDeviceMatch } -func (t clientTarget) description() string { - if t.fixedBusID != "" { - return describeMatch(option.USBIPDeviceMatch{BusID: t.fixedBusID}) - } - return describeMatch(t.match) -} - -func isBusIDOnlyMatch(m option.USBIPDeviceMatch) bool { - return m.BusID != "" && m.VendorID == 0 && m.ProductID == 0 && m.Serial == "" -} - func sleepCtx(ctx context.Context, d time.Duration) bool { t := time.NewTimer(d) defer t.Stop() diff --git a/service/usbip/sysfs_linux.go b/service/usbip/sysfs_linux.go index b9c610c2a..8f6b34cc9 100644 --- a/service/usbip/sysfs_linux.go +++ b/service/usbip/sysfs_linux.go @@ -10,7 +10,6 @@ import ( "path/filepath" "strconv" "strings" - "sync" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/shell" @@ -24,8 +23,6 @@ const ( usbipStatusAvailable = 1 usbipStatusUsed = 2 usbipStatusError = 3 - - vhciStateUsed = 6 ) type sysfsDevice struct { @@ -47,15 +44,6 @@ type sysfsDevice struct { Interfaces []DeviceInterface } -func (d *sysfsDevice) key() DeviceKey { - return DeviceKey{ - BusID: d.BusID, - VendorID: d.VendorID, - ProductID: d.ProductID, - Serial: d.Serial, - } -} - func (d *sysfsDevice) toProtocol() DeviceInfoTruncated { var info DeviceInfoTruncated encodePathField(&info.Path, d.Path) @@ -75,28 +63,12 @@ func (d *sysfsDevice) toProtocol() DeviceInfoTruncated { return info } -func (d *sysfsDevice) toDeviceEntry() DeviceEntry { - return DeviceEntry{ - Info: d.toProtocol(), - Interfaces: d.Interfaces, - Serial: d.Serial, - } -} - type vhciStatusRecord struct { hub string port int state int } -func ensureHostDriver() error { - return ensureKernelPath(sysUsbipHostDriver, "usbip-host", "usbip-host driver") -} - -func ensureVHCI() error { - return ensureKernelPath(sysVHCIControllerV0, "vhci-hcd", "vhci_hcd.0") -} - func listUSBDevices() ([]sysfsDevice, error) { entries, err := os.ReadDir(sysBusUSBDevices) if err != nil { @@ -176,33 +148,6 @@ func currentDriver(busid string) (string, error) { return filepath.Base(link), nil } -func unbindFromDriver(busid, driver string) error { - path := filepath.Join("/sys/bus/usb/drivers", driver, "unbind") - return writeSysfs(path, busid) -} - -func bindToDriver(busid, driver string) error { - path := filepath.Join("/sys/bus/usb/drivers", driver, "bind") - return writeSysfs(path, busid) -} - -func hostMatchBusID(busid string, add bool) error { - verb := "del" - if add { - verb = "add" - } - path := filepath.Join(sysUsbipHostDriver, "match_busid") - return writeSysfs(path, verb+" "+busid) -} - -func hostBind(busid string) error { - return writeSysfs(filepath.Join(sysUsbipHostDriver, "bind"), busid) -} - -func hostUnbind(busid string) error { - return writeSysfs(filepath.Join(sysUsbipHostDriver, "unbind"), busid) -} - func reloadHostDriver() error { modprobePath, err := findModprobePath() if err != nil { @@ -212,7 +157,7 @@ func reloadHostDriver() error { if err != nil { return E.Extend(E.Cause(err, "unload kernel module usbip-host"), strings.TrimSpace(output)) } - return ensureHostDriver() + return ensureKernelPath(sysUsbipHostDriver, "usbip-host", "usbip-host driver") } func readUsbipStatus(busid string) (int, error) { @@ -227,10 +172,6 @@ func readUsbipStatus(busid string) (int, error) { return v, nil } -func writeUsbipSockfd(busid string, fd int) error { - return writeSysfs(filepath.Join(sysBusUSBDevices, busid, "usbip_sockfd"), strconv.Itoa(fd)) -} - func vhciPickFreePort(speed uint32, skip map[int]struct{}) (int, error) { records, err := readVHCIStatus() if err != nil { @@ -249,56 +190,11 @@ func vhciPickFreePort(speed uint32, skip map[int]struct{}) (int, error) { return -1, E.New("no free ", targetHub, " vhci port") } -type vhciStatusFlight struct { - done chan struct{} - used map[int]bool - err error -} - -// vhciPortUsedAccess coalesces concurrent callers into a single -// status-file read: the first goroutine reads, later arrivals share its result. -var ( - vhciPortUsedAccess sync.Mutex - vhciPortUsedFlight *vhciStatusFlight -) - -func vhciUsedPorts() (map[int]bool, error) { - vhciPortUsedAccess.Lock() - if vhciPortUsedFlight != nil { - flight := vhciPortUsedFlight - vhciPortUsedAccess.Unlock() - <-flight.done - return flight.used, flight.err - } - flight := &vhciStatusFlight{done: make(chan struct{})} - vhciPortUsedFlight = flight - vhciPortUsedAccess.Unlock() - - records, err := readVHCIStatus() - if err == nil { - flight.used = make(map[int]bool, len(records)) - for _, record := range records { - flight.used[record.port] = record.state == vhciStateUsed - } - } - flight.err = err - - vhciPortUsedAccess.Lock() - vhciPortUsedFlight = nil - vhciPortUsedAccess.Unlock() - close(flight.done) - return flight.used, flight.err -} - func vhciAttach(port int, fd uintptr, devid uint32, speed uint32) error { line := fmt.Sprintf("%d %d %d %d", port, int(fd), devid, speed) return writeSysfs(filepath.Join(sysVHCIControllerV0, "attach"), line) } -func vhciDetach(port int) error { - return writeSysfs(filepath.Join(sysVHCIControllerV0, "detach"), strconv.Itoa(port)) -} - func readVHCIStatus() ([]vhciStatusRecord, error) { raw, err := os.ReadFile(filepath.Join(sysVHCIControllerV0, "status")) if err != nil {