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.
This commit is contained in:
世界
2026-05-13 13:20:31 +08:00
parent ea57b1c928
commit 14778f5961
13 changed files with 151 additions and 293 deletions
+3 -3
View File
@@ -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
}
+1 -17
View File
@@ -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
-12
View File
@@ -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)
+6 -13
View File
@@ -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]
+28 -10
View File
@@ -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
+23 -40
View File
@@ -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
}
+9 -13
View File
@@ -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}:
-11
View File
@@ -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
+49 -35
View File
@@ -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)
+16 -8
View File
@@ -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)
+15 -15
View File
@@ -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)
-11
View File
@@ -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()
+1 -105
View File
@@ -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 {