# 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 <pfad>    Config-Datei (Standard: ~/.usb-server/config.json)
--relay <adresse>  Relay-Server (z.B. ws://localhost:8443)
--hash <hash>      Gruppen-Hash
--name <name>      Client-Name
--web-port <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 `<relay>/diag/<id>` 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.
