diff --git a/bin/usb-client b/bin/usb-client index 0e01fa2..fa36f0b 100755 Binary files a/bin/usb-client and b/bin/usb-client differ diff --git a/bin/usb-relay b/bin/usb-relay index 9a263dd..6d6fdb8 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 65a0769..ca83e81 100644 --- a/internal/client/socket_linux.go +++ b/internal/client/socket_linux.go @@ -7,6 +7,7 @@ import ( "log" "os" "path/filepath" + "strings" "time" "golang.org/x/sys/unix" @@ -35,48 +36,58 @@ func fdToFile(fd int, name string) *os.File { // 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 - for attempt := 0; attempt < 10; attempt++ { + // Wait for the device to finish enumerating and create device nodes. + // The kernel needs time to enumerate descriptors and bind drivers. + for attempt := 0; attempt < 15; attempt++ { time.Sleep(500 * time.Millisecond) - // Find device nodes created by this VHCI port - // VHCI creates devices under /sys/devices/platform/vhci_hcd.0/usbN/ - // We look for video4linux devices - matches, _ := filepath.Glob("/sys/devices/platform/vhci_hcd.0/usb*/*/video4linux/video*") - for _, m := range matches { - devName := filepath.Base(m) - devPath := "/dev/" + devName - if _, err := os.Stat(devPath); err == nil { + found := false + + // Walk the VHCI sysfs tree to find device nodes at any depth. + // Paths look like: vhci_hcd.0/usb3/3-1/3-1:1.0/video4linux/video0 + filepath.WalkDir("/sys/devices/platform/vhci_hcd.0", func(path string, d os.DirEntry, err error) error { + if err != nil { + return nil + } + + dir := filepath.Dir(path) + parent := filepath.Base(dir) + + // video4linux devices → /dev/videoN + if parent == "video4linux" && strings.HasPrefix(d.Name(), "video") { + devPath := "/dev/" + d.Name() + if err := os.Chmod(devPath, 0666); err == nil { + log.Printf("[use] set permissions 0666 on %s", devPath) + found = true + } + } + + // 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 { + if err == nil && !sd.IsDir() { + os.Chmod(sndPath, 0666) + } + return nil + }) + } + } + + // input devices → /dev/input/eventN + if strings.HasPrefix(d.Name(), "event") && strings.Contains(path, "/input/input") { + devPath := "/dev/input/" + d.Name() if err := os.Chmod(devPath, 0666); err == nil { log.Printf("[use] set permissions 0666 on %s", devPath) } } - } - // Also check for other common device types (input, sound, etc.) - for _, subsys := range []string{"sound/card*", "input/input*/event*"} { - sysMatches, _ := filepath.Glob("/sys/devices/platform/vhci_hcd.0/usb*/*/" + subsys) - for _, m := range sysMatches { - devName := filepath.Base(m) - devPath := "/dev/" + devName - // For sound devices, also check /dev/snd/ - if _, err := os.Stat(devPath); err == nil { - os.Chmod(devPath, 0666) - } - devPath = "/dev/snd/" + devName - if _, err := os.Stat(devPath); err == nil { - os.Chmod(devPath, 0666) - } - } - } + return nil + }) - if attempt == 0 { - continue // first iteration: always wait more - } - - // Check if we found any video devices - if len(matches) > 0 { + if attempt >= 2 && found { return } } diff --git a/internal/client/use.go b/internal/client/use.go index 37e6919..112126f 100644 --- a/internal/client/use.go +++ b/internal/client/use.go @@ -272,6 +272,7 @@ func (um *UseManager) tunnelReadLoop(tunnel *useTunnel) { } if err := um.client.SendTunnelData(tunnel.id, buf[:n]); err != nil { + log.Printf("[use] tunnel send error: %v", err) return } } @@ -326,11 +327,17 @@ func (um *UseManager) handleTunnelData(tunnelID string, data []byte) { um.mu.RUnlock() if !exists { + log.Printf("[use] tunnel data for unknown tunnel %s (%d bytes)", tunnelID[:8], len(data)) return } // Write to the tunnel socket (relay -> VHCI) - tunnel.conn.Write(data) + n, err := tunnel.conn.Write(data) + if err != nil { + log.Printf("[use] tunnel write error: %v", err) + } else if n != len(data) { + log.Printf("[use] tunnel short write: %d/%d", n, len(data)) + } } func (um *UseManager) handleClientLeft(msg *protocol.ClientLeft) { diff --git a/internal/usbip/server.go b/internal/usbip/server.go index 449c82b..f00c5cd 100644 --- a/internal/usbip/server.go +++ b/internal/usbip/server.go @@ -421,10 +421,15 @@ func (s *Server) handleCmdSubmit(r io.Reader, hdr *URBHeader, retChan chan<- []b 5000, buf, ) if err != nil { + log.Printf("[usbip-server] CTRL OUT seq=%d failed: %v", hdr.SeqNum, err) status = -32 // -EPIPE + } else { + log.Printf("[usbip-server] CTRL OUT seq=%d OK (bmReqType=0x%02x bReq=0x%02x wVal=0x%04x)", + hdr.SeqNum, bmRequestType, bRequest, wValue) } } + log.Printf("[usbip-server] CTRL OUT seq=%d → response status=%d", hdr.SeqNum, status) resp, err := BuildRetSubmit(hdr.SeqNum, hdr.DevID, hdr.Direction, hdr.Endpoint, status, nil) if err != nil { return err @@ -559,6 +564,8 @@ func (s *Server) handleCmdUnlink(r io.Reader, hdr *URBHeader, retChan chan<- []b return err } + log.Printf("[usbip-server] UNLINK seq=%d target_seq=%d", hdr.SeqNum, body.UnlinkSeqNum) + s.mu.Lock() pending, exists := s.pendingURBs[body.UnlinkSeqNum] if exists {