diff --git a/bin/usb-client b/bin/usb-client index 935aac9..5423199 100755 Binary files a/bin/usb-client and b/bin/usb-client differ diff --git a/bin/usb-client.exe b/bin/usb-client.exe index b5b1d92..0d1235a 100755 Binary files a/bin/usb-client.exe and b/bin/usb-client.exe differ diff --git a/bin/usb-relay b/bin/usb-relay index 1324538..a002ca8 100755 Binary files a/bin/usb-relay and b/bin/usb-relay differ diff --git a/internal/client/socket_linux.go b/internal/client/socket_linux.go index 10abb10..cc0aa02 100644 --- a/internal/client/socket_linux.go +++ b/internal/client/socket_linux.go @@ -42,7 +42,10 @@ func createVHCIAttachment(_ context.Context, granted *protocol.DeviceGranted, _ return nil, -1, fmt.Errorf("VHCI attach: %w", err) } - // The VHCI driver now owns vhciFD, so we don't close it + // The VHCI driver holds a kernel reference to the socket via sockfd_lookup, + // so we can close our copy of the fd to avoid leaking it. + unix.Close(vhciFD) + // Create a net.Conn from the tunnel FD tunnelFile := fdToFile(tunnelFD, "usb-tunnel") tunnelConn, err := net.FileConn(tunnelFile) @@ -74,9 +77,9 @@ func fdToFile(fd int, name string) *os.File { } // fixVHCIDevicePermissions waits for the VHCI-attached device to create -// device nodes (e.g. /dev/video*) and sets them to world-accessible. -// VHCI-created devices don't get normal udev rules applied, so they -// default to root-only access. +// device nodes (e.g. /dev/video*, /dev/input/event*, /dev/hidraw*) and sets +// them to world-accessible. VHCI-created devices don't get normal udev +// rules applied, so they default to root-only access. func fixVHCIDevicePermissions(port int) { // Wait for the device to finish enumerating and create device nodes. // The kernel needs time to enumerate descriptors and bind drivers. @@ -101,12 +104,13 @@ func fixVHCIDevicePermissions(port int) { if err := os.Chmod(devPath, 0666); err == nil { log.Printf("[use] set permissions 0666 on %s", devPath) found = true + } else { + log.Printf("[use] chmod %s failed: %v", devPath, err) } } // sound devices → /dev/snd/* if parent == "sound" && strings.HasPrefix(d.Name(), "card") { - // For sound cards, chmod all related device nodes sndDir := filepath.Join(path, "device") if _, err := os.Stat(sndDir); err == nil { filepath.WalkDir("/dev/snd", func(sndPath string, sd os.DirEntry, err error) error { @@ -123,6 +127,20 @@ func fixVHCIDevicePermissions(port int) { devPath := "/dev/input/" + d.Name() if err := os.Chmod(devPath, 0666); err == nil { log.Printf("[use] set permissions 0666 on %s", devPath) + found = true + } else { + log.Printf("[use] chmod %s failed: %v", devPath, err) + } + } + + // hidraw devices → /dev/hidrawN + if parent == "hidraw" && strings.HasPrefix(d.Name(), "hidraw") { + devPath := "/dev/" + d.Name() + if err := os.Chmod(devPath, 0666); err == nil { + log.Printf("[use] set permissions 0666 on %s", devPath) + found = true + } else { + log.Printf("[use] chmod %s failed: %v", devPath, err) } } @@ -133,4 +151,6 @@ func fixVHCIDevicePermissions(port int) { return } } + + log.Printf("[use] fixVHCIDevicePermissions: no device nodes found after 7.5s (port %d)", port) } diff --git a/internal/usbip/server.go b/internal/usbip/server.go index 13c68fb..6ed5f07 100644 --- a/internal/usbip/server.go +++ b/internal/usbip/server.go @@ -414,16 +414,11 @@ func (s *Server) handleCmdSubmit(r io.Reader, hdr *URBHeader, retChan chan<- []b // device's endpoint state without updating the kernel's internal USB // subsystem, breaking all subsequent SETINTERFACE and SUBMITURB calls // (ESRCH / EHOSTUNREACH). - // Just reset host-side data toggles to DATA0 (matching the fresh VHCI - // state on the use-side) and return success. - log.Printf("[usbip-server] SET_CONFIGURATION(%d) intercepted (device already configured), resetting endpoint toggles", wValue) - for epNum := range s.epTypes { - if epNum == 0 { - continue - } - s.handle.ResetEndpoint(uint32(epNum)) - s.handle.ResetEndpoint(uint32(epNum) | 0x80) - } + // Do NOT reset host-side data toggles either: after DisconnectClaimInterface + // the host and device toggles are already in sync. Resetting host-side + // toggles to DATA0 would create a mismatch (device still at its current + // toggle), causing the first interrupt packet to be silently discarded. + log.Printf("[usbip-server] SET_CONFIGURATION(%d) intercepted (device already configured)", wValue) default: // Generic OUT control transfer @@ -647,9 +642,27 @@ func (s *Server) reapLoop(retChan chan<- []byte, done <-chan struct{}) { continue } + dirStr := "OUT" + if pending.direction == DirIn { + dirStr = "IN" + } + urbType := s.getURBType(uint8(pending.endpoint)) + typeNames := map[uint8]string{0: "ISO", 1: "INT", 2: "CTRL", 3: "BULK"} + if urbInfo.Status != 0 { - log.Printf("[usbip-server] URB completed: seq=%d ep=%d status=%d actual=%d iso=%v", - pending.seqNum, pending.endpoint, urbInfo.Status, urbInfo.ActualLength, pending.isISO) + log.Printf("[usbip-server] URB completed: seq=%d EP%d %s type=%s status=%d actual=%d", + pending.seqNum, pending.endpoint, dirStr, typeNames[urbType], urbInfo.Status, urbInfo.ActualLength) + } else if urbType == 1 { // interrupt — always log for HID debugging + hexStr := "" + if pending.direction == DirIn && urbInfo.ActualLength > 0 { + n := int(urbInfo.ActualLength) + if n > 16 { + n = 16 + } + hexStr = fmt.Sprintf(" data=%x", pending.buffer[:n]) + } + log.Printf("[usbip-server] INT completed: seq=%d EP%d %s actual=%d%s", + pending.seqNum, pending.endpoint, dirStr, urbInfo.ActualLength, hexStr) } var resp []byte