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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}:
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user