# USB Server USB-Sharing ueber Netzwerk mit Relay-Server fuer NAT-Traversal. ## Architektur ``` ┌──────────────────┐ ┌──────────────┐ ┌──────────────────┐ │ Client (both) │──ws/wss─│ Relay Server │─ws/wss──│ Client (both) │ │ gibt Geraete │ │ (Docker) │ │ gibt Geraete │ │ frei UND │ │ gruppiert │ │ frei UND │ │ empfaengt sie │ │ nach Hash │ │ empfaengt sie │ │ Web-UI :8080 │ │ │ │ Web-UI :8080 │ └────────┬─────────┘ └──────────────┘ └────────┬─────────┘ │ │ Physische USB-Geraete Virtuelle USB-Geraete (usbdevfs) (vhci-hcd) ``` **Relay-Server:** WebSocket-Vermittler. Braucht keine Konfiguration - verbindet alle Clients die den gleichen Hash haben. Als Docker-Container deploybar. Er vermittelt die Verbindungen und reicht Tunneldaten weiter, ohne das USB/IP-Protokoll zu interpretieren. **Verbindungswege:** Nach der Vermittlung durch den Relay versuchen zwei Clients, sich **direkt** zu verbinden. Klappt das - im gleichen LAN, ueber VPN, bei oeffentlicher IP oder Portfreigabe - laeuft der USB-Verkehr an der Relay vorbei. Das spart eine komplette Netzwerkstrecke, was bei USB/IP spuerbar ist: jeder Transfer ist ein eigener Round-Trip, und die Latenz addiert sich auf. Scheitert der Versuch (beide hinter NAT ohne Portfreigabe), laeuft alles unveraendert weiter ueber den Relay. **Verschluesselung:** Tunneldaten sind Ende-zu-Ende mit AES-256-GCM verschluesselt, auf beiden Wegen. Der Schluessel wird aus den **drei Tokens** abgeleitet, nicht aus dem Hash - der Relay bekommt nur den Hash und kann daraus die Tokens nicht zurueckrechnen. Er sieht also weder USB-Daten noch kann er sich als Peer ausgeben. > **Was der Relay trotzdem sieht und kann:** Metadaten - wer in welcher Gruppe > ist, welche Geraete angeboten werden, wann welches angefordert wird, und wie > viel Verkehr fließt. Der Hash gruppiert, er authentifiziert nicht: wer ihn > kennt, kann der Gruppe beitreten, Geraetelisten sehen und Geraete anfordern. > Die Tunneldaten kann so jemand ohne die Tokens aber nicht lesen. > > **Clients ohne Tokens:** Wer nur den Hash konfiguriert hat, kann keine > Schluessel ableiten. Solche Clients funktionieren weiter, aber unverschluesselt > und ohne Direktverbindung - und ein Peer, der verschluesselt, lehnt sie ab. > Die Web-UI zeigt im Kopf an, welcher Fall vorliegt. Abhilfe: die drei Tokens > auf alle Clients kopieren (Token-Tab). Die Gruppen-ID aendert sich dadurch > **nicht**, bestehende Setups bleiben also verbunden. **Client:** Kann in drei Modi betrieben werden: - **both:** Gibt lokale Geraete frei *und* verbindet sich mit fremden. Das ist der uebliche Fall in einer Gruppe, in der jede Maschine Hardware verleiht und ausleiht. - **share:** Gibt nur lokale USB-Geraete frei. Geraete werden erst dann vom System getrennt wenn ein Use-Client sie anfordert. - **use:** Empfaengt nur. Zeigt verfuegbare Geraete aller Share-Clients an, einzeln verbindbar/trennbar. **Gruppierung:** 3 zufaellige Tokens werden zu einem SHA256-Hash kombiniert. Alle Clients mit dem gleichen Hash gehoeren zusammen. ## Quick Start ### 1. Relay-Server starten ```bash # Mit Docker docker compose up -d # Oder direkt make relay ./bin/usb-relay --port 8443 ``` ### 2. Tokens generieren Auf dem ersten Client: ```bash ./bin/usb-client generate-token ``` Ausgabe: ``` Token 1: aB3dE... Token 2: xY7zW... Token 3: mN4pQ... Hash: a1b2c3d4e5... ``` Die 3 Tokens auf alle weiteren Clients kopieren. ### 3. Clients starten Auf jeder Maschine, die Geraete freigeben *und* empfangen soll: ```bash sudo ./bin/usb-client both --relay ws://relay-server:8443 ``` Root wird auf der Share-Seite gebraucht: das Oeffnen der Geraete unter `/dev/bus/usb/`, das Loesen der Kernel-Treiber und das spaetere Zurueckbinden laufen ueber privilegierte Schnittstellen. Wer die Rollen trennen will, startet stattdessen nur eine Seite: ```bash sudo ./bin/usb-client share --relay ws://relay-server:8443 # nur freigeben sudo ./bin/usb-client use --relay ws://relay-server:8443 # nur empfangen ``` Web-UI oeffnen: http://localhost:8080 ## Installation ### Go installieren #### Linux (Debian/Ubuntu) ```bash # Aktuelle Version herunterladen (go1.25.0 oder neuer) wget https://go.dev/dl/go1.25.0.linux-amd64.tar.gz # Altes Go entfernen und neues installieren sudo rm -rf /usr/local/go sudo tar -C /usr/local -xzf go1.25.0.linux-amd64.tar.gz # PATH einrichten - ans Ende von ~/.bashrc oder ~/.profile anfuegen: echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc source ~/.bashrc # Pruefen go version ``` #### Windows 1. Installer herunterladen: https://go.dev/dl/ (Datei `go1.25.0.windows-amd64.msi`) 2. MSI-Installer ausfuehren - installiert Go nach `C:\Program Files\Go` 3. Der Installer richtet den PATH automatisch ein 4. Neue Eingabeaufforderung oeffnen und pruefen: ```cmd go version ``` Falls der PATH nicht automatisch gesetzt wurde: 1. Windows-Taste > "Umgebungsvariablen" suchen > "Umgebungsvariablen fuer dieses Konto bearbeiten" 2. Variable `Path` bearbeiten > Neu > `C:\Program Files\Go\bin` hinzufuegen 3. Eingabeaufforderung neu oeffnen ### Make installieren #### Linux (Debian/Ubuntu) ```bash sudo apt update sudo apt install make ``` #### Linux (Fedora/RHEL) ```bash sudo dnf install make ``` #### Windows **Option A: Chocolatey** (empfohlen) ```powershell # Chocolatey installieren (Admin-PowerShell): Set-ExecutionPolicy Bypass -Scope Process -Force [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) # Make installieren: choco install make ``` **Option B: MSYS2** 1. MSYS2 von https://www.msys2.org herunterladen und installieren 2. In der MSYS2-Shell: ```bash pacman -S make ``` 3. `C:\msys64\usr\bin` zum Windows PATH hinzufuegen **Option C: Ohne Make** - Go-Befehle direkt ausfuehren (siehe unten) ### Plattform-Unterstuetzung | Funktion | Linux | Windows | Android | macOS | |----------|-------|---------|---------|-------| | Share-Modus | Ja | Ungetestet (eigener Treiber noetig, siehe unten) | Ja, ueber App-Bridge | Nein (braucht IOKit) | | Use-Modus | Ja (vhci-hcd) | Ja (usbip-win2) | Nein (kein vhci-hcd) | Nein (braucht Treiber) | | Kombinierter Modus (`both`) | Ja | Nein | Nein | Nein | | Relay-Server | Ja | Ja | - | Ja | | Web-UI / Config | Ja | Ja | - | Ja | **Windows Share:** Der Code ist vorhanden (`driver/windows/` plus die Go-Anbindung), aber der Filtertreiber wurde nie gebaut oder getestet. Er braucht das WDK zum Bauen und ein EV-Zertifikat zum Verteilen. Details und ehrliche Einordnung in [driver/windows/README.md](driver/windows/README.md). **Android Share:** Das Go-Binary laeuft dort unveraendert, aber eine App darf `/dev/bus/usb` nicht oeffnen. Eine kleine App holt deshalb die Berechtigung ueber das Framework und reicht den Dateideskriptor an den Client durch — siehe [android/README.md](android/README.md). Die Go-Seite dieser Bruecke ist fertig und getestet, die App-Seite ist eine ungetestete Referenzimplementierung. **Docker und USB:** Auf einem Linux-Host koennen Geraete in den Container durchgereicht werden (`/dev/bus/usb` mounten, `privileged: true`). Auf macOS und Windows geht das **nicht**: Docker laeuft dort in einer Linux-VM, die die USB-Hardware des Hosts nie zu sehen bekommt. Container teilen sich den Kernel des Hosts — auf Linux ist das derselbe Kernel, der die Geraete verwaltet, auf den anderen Plattformen nicht. ### Architekturen Der Linux-Client laeuft unveraendert auf jeder Architektur - usbdevfs und vhci-hcd sind nicht architekturspezifisch. `make release` baut fuer alle: | Ziel | Typische Geraete | |------|------------------| | linux/amd64 | normale PCs, Intel-basierte Synology | | linux/arm64 | Raspberry Pi 3/4/5 (64-Bit-OS), ARM-Synology | | linux/arm | Raspberry Pi mit 32-Bit-OS, aeltere ARM-Boards | | linux/386 | alte 32-Bit-x86-Rechner | | linux/mips64le, linux/mipsle | diverse NAS- und Router-Plattformen | | linux/riscv64 | RISC-V-Boards | | windows/amd64, windows/arm64 | Windows (nur Use-Modus) | Voraussetzung bleibt in allen Faellen: Share braucht Zugriff auf `/dev/bus/usb` (Root), Use braucht das Kernel-Modul `vhci-hcd`. Auf NAS-Systemen mit eigenem Kernel ist `vhci-hcd` haeufig nicht vorhanden - solche Geraete taugen dann als Share-Client, nicht als Use-Client. **Windows Use-Modus:** Benoetigt den [usbip-win2](https://github.com/vadimgrn/usbip-win2/releases) VHCI-Treiber (WHKL-zertifiziert, Microsoft-signiert). Der Client erkennt automatisch ob usbip-win2 installiert ist. **Windows Share-Modus:** Nicht verfuegbar - erfordert Linux-spezifische Kernel-Schnittstelle (usbdevfs). ### Voraussetzungen (Laufzeit) **Linux (Share-Modus):** - Root-Rechte (fuer USB-Geraetezugriff) **Linux (Use-Modus):** - Kernel-Modul `vhci-hcd`: ```bash sudo modprobe vhci-hcd ``` - Fuer automatisches Laden bei Boot: ```bash echo "vhci-hcd" | sudo tee /etc/modules-load.d/vhci-hcd.conf ``` ### Bauen #### Mit Make ```bash # Alles bauen (Relay + Client, Linux) make all # Nur Relay-Server make relay # Nur Client (Linux) make client # Client fuer Windows (Cross-Compilation von Linux) make client-windows ``` #### Ohne Make (Go direkt) ```bash # Linux Relay-Server CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/usb-relay ./cmd/usb-relay/ # Linux Client CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/usb-client ./cmd/usb-client/ # Windows Client (Cross-Compilation von Linux) CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o bin/usb-client.exe ./cmd/usb-client/ ``` Unter Windows (cmd.exe): ```cmd set CGO_ENABLED=0 go build -ldflags="-s -w" -o bin\usb-client.exe ./cmd/usb-client/ ``` Unter Windows (PowerShell): ```powershell $env:CGO_ENABLED=0 go build -ldflags="-s -w" -o bin\usb-client.exe ./cmd/usb-client/ ``` ### Docker (Relay-Server) ```bash docker compose up -d ``` Der Relay lauscht auf Port 8443. ## CLI Befehle ``` usb-client generate-token # 3 Tokens + Hash generieren usb-client both [optionen] # Freigeben und Empfangen gleichzeitig usb-client share [optionen] # Nur freigeben usb-client use [optionen] # Nur empfangen usb-client list # Lokale USB-Geraete auflisten usb-client list -v # Mit Interfaces und Endpunkten (Diagnose) usb-client gui # Nur Web-UI starten usb-client config # Konfiguration anzeigen usb-client config set [optionen] # Konfiguration aendern usb-client install-service # Als systemd-Service installieren usb-client uninstall-service # Service deinstallieren ``` ### Optionen ``` --config Config-Datei (Standard: ~/.usb-server/config.json) --relay Relay-Server (z.B. ws://localhost:8443) --hash Gruppen-Hash --name Client-Name --web-port Web-UI Port (Standard: 8080) --no-gui Web-UI deaktivieren ``` ### Netzwerk Fuer Direktverbindungen oeffnet jeder Share-faehige Client einen TCP-Port (standardmaessig zufaellig). Zwei Faelle brauchen Handarbeit: - **Feste Portfreigabe:** `direct_port` in der Config setzen und diesen Port in Firewall/Router weiterleiten. Dann finden auch Peers hinter NAT hierher. - **Gar keine Direktverbindung gewuenscht:** `disable_direct: true` setzt alles zurueck auf den reinen Relay-Betrieb. Ob eine Verbindung direkt zustande kam, steht im Log des Use-Clients (`direct connection to ... established` gegen `using the relay`). ### Umgebungsvariablen ``` USBSRV_DEBUG=1 Protokolliert jeden einzelnen USB-Transfer (URB). Nur zur Fehlersuche - ein aktives Video- oder Audiogeraet erzeugt tausende URBs pro Sekunde, und das Protokollieren kostet dann mehr Zeit als das Weiterleiten. ``` ## Fehlersuche ### Erste Anlaufstelle ```bash usb-client diag ``` Sagt fuer die jeweilige Maschine, ob Freigeben und Empfangen moeglich sind, was im Weg steht und was dagegen hilft. Laeuft auf Linux, Windows und macOS und prueft plattformspezifisch das Richtige: Rechte auf `/dev/bus/usb` und das `vhci-hcd`-Modul unter Linux, Treiberstatus und Testsignierung unter Windows, die IOKit-Lage unter macOS. Fuer jedes Geraet steht dabei, ob es freigegeben werden kann - und wenn nicht, warum. Zum Weitergeben: ```bash usb-client diag -json # maschinenlesbar usb-client diag -out report.txt # zusaetzlich in eine Datei usb-client diag -id meinreport # zum konfigurierten Relay hochladen ``` Der Upload legt den Report unter `/diag/` ab, wo er 24 Stunden liegt. Das erspart es, von einer schwer erreichbaren Maschine - headless NAS, Windows-Rechner mitten im Treiber-Debugging - tausende Zeilen von Hand zu kopieren. > Der Report enthaelt Hostname, Geraeteliste und OS-Version. Beim Hochladen > auf einen Relay kann jeder mit der ID ihn lesen. Auf einem oeffentlichen > Relay also eine schwer zu erratende ID waehlen, oder den Report lokal > speichern und selbst weitergeben. ### Haeufige Faelle **Geraet verbindet sich, liefert aber keine Daten.** `usb-client list -v` auf der Share-Seite zeigt, wie jeder Endpunkt eingestuft wird. Der Transfertyp dort entscheidet, wie die Transfers zum Geraet geschickt werden - steht bei einem Interrupt-Endpunkt `bulk`, weist der Kernel die Uebertragungen ab. Ohne Root-Rechte koennen die vollstaendigen Deskriptoren nicht gelesen werden; dann erscheint ein Hinweis statt der Endpunktliste. **Geraet bleibt nach einem Absturz auf "in Benutzung".** Beim Verbindungsverlust zum Relay geben beide Seiten ihre Geraete automatisch wieder frei. Bleibt trotzdem eines haengen, loest ein Neustart des Share-Clients die Bindung; die Kernel-Treiber werden dabei ueber sysfs `authorized` neu gebunden. **Verbindung bricht regelmaessig ab.** Client und Relay senden alle 20 Sekunden WebSocket-Pings und trennen nach 60 Sekunden Stille. Liegt ein Proxy dazwischen, der WebSockets frueher schliesst, muss dessen Timeout hoeher liegen als 60 Sekunden. ## Web-UI Die Web-UI ist unter http://localhost:8080 erreichbar und bietet: - **Geraete-Tab:** Liste aller verfuegbaren/verbundenen USB-Geraete - **Einstellungen:** Relay-Adresse, Modus, Name konfigurieren - **Token:** Tokens generieren oder eintragen - **Service:** Als Systemdienst installieren/deinstallieren ## Konfiguration Die Konfiguration wird in `~/.usb-server/config.json` gespeichert: ```json { "relay_addr": "ws://localhost:8443", "hash": "a1b2c3d4...", "mode": "use", "name": "Mein-PC", "web_port": 8080, "token1": "...", "token2": "...", "token3": "...", "auto_connect": [ {"vendor_id": "1234", "product_id": "5678"}, {"bus_id": "1-1.4"} ] } ``` ## Als Service installieren ```bash # Service installieren (braucht root) sudo ./bin/usb-client install-service # Service Status pruefen systemctl status usb-client # Service deinstallieren sudo ./bin/usb-client uninstall-service ``` ## Sicherheit - **Tunnel-Verschluesselung:** USB-Daten werden Ende-zu-Ende mit AES-256-GCM verschluesselt - sowohl ueber den Relay als auch bei Direktverbindung. Der Schluessel wird per HKDF aus den 3 Tokens abgeleitet, pro Tunnel neu. Jeder Frame ist authentifiziert; manipulierte oder wiederholte Frames beenden den Tunnel. - **Gruppierung:** Die 3 Tokens sind das gemeinsame Geheimnis. Der Relay bekommt nur ihren SHA256-Hash und kann daraus die Tokens nicht zurueckrechnen - er kann also weder mitlesen noch sich als Peer ausgeben. - **Direktverbindungen** werden mit einem aus den Tokens abgeleiteten Token authentifiziert, das an die Tunnel-ID gebunden ist. Wer nur den Port erreicht, kommt nicht hinein. - **Transport-Verschluesselung:** Trotzdem `wss://` verwenden, wenn der Relay ueber das Internet laeuft. Das schuetzt die Steuernachrichten und Metadaten, die nicht Teil der Tunnel-Verschluesselung sind. **Grenzen:** Der Hash gruppiert, er authentifiziert nicht - wer ihn kennt, kann der Gruppe beitreten, Geraetelisten sehen und Geraete anfordern (ohne die Tokens aber keine Tunneldaten lesen). Der Relay sieht Metadaten: wer verbunden ist, welche Geraete angeboten und wann sie angefordert werden. Ein Client mit den Tokens hat vollen Zugriff auf alle freigegebenen Geraete der Gruppe; eine Rechteverwaltung pro Geraet oder Client gibt es nicht. Fuer TLS am Relay-Server empfiehlt sich ein Reverse-Proxy (nginx/traefik) mit Let's Encrypt: ```nginx server { listen 443 ssl; server_name relay.example.com; ssl_certificate /etc/letsencrypt/live/relay.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/relay.example.com/privkey.pem; location /ws { proxy_pass http://localhost:8443; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } ``` ## Technische Details - **Sprache:** Go (single binary, keine Runtime-Abhaengigkeiten) - **USB/IP:** Natives USB/IP-Protokoll (kein externes `usbip`-Paket noetig) - **USB-Zugriff:** Direkte usbdevfs-ioctls (kein CGO/libusb) - **VHCI:** Direkte sysfs-Schnittstelle zum vhci-hcd Kernel-Modul - **Relay:** WebSocket mit JSON-Kontrollnachrichten und Binary-Tunneldaten - **Tunnel:** USB/IP-Protokolldaten werden durch WebSocket-Binary-Frames getunnelt ## Verzeichnisstruktur ``` cmd/usb-relay/ - Relay-Server Binary cmd/usb-client/ - Client Binary (share + use Modi) internal/relay/ - WebSocket Relay & Hub internal/protocol/ - Nachrichtentypen internal/client/ - Client Core, Share & Use Manager internal/usbip/ - USB/IP Protokoll, Server, VHCI internal/usb/ - USB Enumeration & usbdevfs internal/token/ - Token-Generierung internal/config/ - Konfiguration internal/web/ - Web-UI (embedded) internal/service/ - Systemd Service ``` ## Lizenz Proprietaere Software. Alle Rechte vorbehalten. Siehe [LICENSE](LICENSE) fuer Details. ### Third-Party Lizenzen Diese Software verwendet Open-Source-Bibliotheken. Siehe [NOTICE](NOTICE) fuer Details.