
Sniffer live a livello di sistema per trasferimenti USB in eBPF — decodifica il traffico USB inline (control SETUP, SCSI, HID) da due hook URB universali. Niente usbmon, niente sniffer hardware. Portabile CO-RE.

Un feed in tempo reale e colorizzato del traffico USB a livello di sistema — costruito sui due punti di passaggio URB universali attraverso cui passa ogni driver host-controller, quindi funziona su xHCI/EHCI/OHCI/dwc allo stesso modo, senza tracepoint per-controller e senza usbmon. Pienamente portabile CO-RE.
| fentry hook | cosa ci dice |
|---|---|
usb_submit_urb | una richiesta di trasferimento è stata accodata (device, endpoint, tipo, payload) |
usb_hcd_giveback_urb | è stato completato (stato, byte trasferiti, latenza, payload) |
Una lru_hash indicizzata dal puntatore URB unisce i due eventi: submit registra un tempo di inizio, il completamento lo rilegge per calcolare la latenza submit→complete, poi lo elimina. Questo rispecchia l'abbinamento richiesta/risposta di httpbody — SUBMIT è la "richiesta" (ciò che l'host invia), COMPLETE la "risposta" (ciò che il dispositivo restituisce).
I control transfer vedono decodificato il loro pacchetto SETUP di 8 byte nel nome standard della richiesta (GET_DESCRIPTOR, SET_CONFIGURATION, …); gli stadi di dati vengono mostrati come testo quando appaiono testuali e come hexdump altrimenti.
L'output è una riga per evento (compatto). La prima volta che appare un dispositivo, riceve una riga legenda ▸ (bus-dev, vid:pid, prodotto, velocità di link); dopo di che ogni riga riporta solo il tag breve DEV, così le colonne a sinistra restano allineate e scansionabili sotto traffico intenso. Ogni riga mostra tempo, tipo (SUBMIT/CMPLT), tipo di trasferimento, epNdir, la freccia di direzione (← device→host IN, → host→device OUT), conteggi di byte, stato, latenza e il driver kernel proprietario, poi un · e il dettaglio più utile (SETUP decodificato, comando SCSI o una breve anteprima del payload). Passa --hex per l'hexdump completo su più righe. I byte esadecimali sono colorati in base alla classe di valore (null blu, ASCII stampabile ciano, spazi bianchi verdi, altri caratteri di controllo magenta, alto/non-ASCII giallo) su TTY; l'output tramite pipe è in chiaro.
usbmon. I pacchetti SETUP e i payload vengono decodificati mentre interagisci con il dispositivo.READ(10) lba=… blocks=…, WRITE(10), CSW PASS/FAIL).--errors-only mette in evidenza stall (EPIPE), timeout, babble ed errori CRC su tutti i dispositivi contemporaneamente.INT o scritture di controllo SET_REPORT che non hai innescato.--json emette NDJSON; invialo tramite pipe a jq o a un file per confrontare i payload tra diverse esecuzioni.curl -fsSL https://yeet.cx | sh
Poi eseguilo direttamente da GitHub — yeet scarica l'esempio e lo compila per te, senza bisogno di clonare:
yeet run github:yeet-src/usbsnoop
Per compilare invece da un checkout locale:
make
Scarica il BTF del kernel in vmlinux.h (per struct urb, usb_device e il device descriptor), poi compila. Richiede clang, bpftool e un kernel con BTF.
yeet run . # all devices, runs until Ctrl-C
yeet run . -- --secs 30 # stop after 30s (prints a summary)
yeet run . -- --vid 0x320f # one vendor
yeet run . -- --vendor-id 0x046d --product-id 0xc52b # one device by id
yeet run . -- --bus 3 --dev 4 # one device by bus address
yeet run . -- --type control,int # only these transfer types
yeet run . -- --no-data # metadata only, skip payload capture
yeet run . -- --max-data 64 # cap rendered payload at 64 bytes
yeet run . -- --errors-only # only failed completions (stalls, timeouts)
yeet run . -- --hex # full multi-line hexdump per transfer
yeet run . -- --json | jq . # NDJSON, one object per event
Tutto il filtraggio avviene lato kernel, quindi il traffico filtrato non raggiunge mai lo spazio utente.
Ogni riga di evento termina con il driver kernel proprietario tra parentesi quadre ([hid_irq_in], [usb_api_blocking_completion]) — urb->complete viene simbolizzato in-kernel tramite bpf_snprintf("%ps"), quindi non serve alcuna interrogazione di /proc/kallsyms. I bulk transfer di mass-storage decodificano il loro wrapper Bulk-Only Transport nel comando SCSI (CBW READ(10) lba=… blocks=… / CSW PASS). Su un'uscita a tempo (raggiungimento di --secs) vengono stampati un riepilogo per dispositivo e un istogramma delle latenze in log2; un'uscita con Ctrl-C lo salta (non c'è alcun hook di segnale visibile a JS).
Il traffico bulk (mass storage e simili) spesso passa allo stack un array struct scatterlist (urb->sg) invece di un singolo transfer_buffer lineare, quindi il payload è sparso su più pagine. usbsnoop percorre quell'array e copia i byte di ogni segmento, ma raggiungerli significa tradurre una pagina nel suo indirizzo virtuale di kernel — l'inverso di page_to_virt di x86-64, che richiede page_offset_base e vmemmap_base del kernel in esecuzione (entrambi randomizzati da KASLR).
L'isolato JS non può leggere /proc/kallsyms e il loader non ha supporto ksym, quindi passi i due indirizzi dei simboli e il lato BPF li dereferenzia:
yeet run . -- \
--page-offset-base 0x$(sudo awk '$3=="page_offset_base"{print $1}' /proc/kallsyms) \
--vmemmap-base 0x$(sudo awk '$3=="vmemmap_base"{print $1}' /proc/kallsyms)
Senza questi flag, i trasferimenti SG mostrano comunque tutti i metadati, solo nessun byte di payload — il comportamento precedente. Questo percorso è solo x86-64: sulle altre architetture lascia i flag disattivati.
actual/requested. Ogni record dell'anello porta un data[16384] completo, quindi l'anello da 8 MiB contiene ~512 eventi.--page-offset-base / --vmemmap-base di cui sopra e un host x86-64; ogni segmento viene catturato fino a una pagina e vengono percorsi solo i primi 64 segmenti di un trasferimento.| flag | predefinito | significato |
|---|
--secs | forever | quanto tempo eseguire; ometti per eseguire fino a Ctrl-C (un numero ferma e stampa un riepilogo) |
--vid, --vendor-id | any | filtra per vendor id (hex 0x1d6b o decimale) |
--pid, --product-id | any | filtra per product id |
--bus | any | filtra per numero di bus |
--dev | any | filtra per indirizzo del dispositivo |
--type | all | csv di iso, int, control, bulk |
--no-data | off | non leggere i buffer di trasferimento (solo metadati) |
--max-data | 4096 | numero massimo di byte di payload mostrati per evento |
--errors-only | off | mostra solo i completamenti non-OK (salta SUBMIT e OK) |
--hex | off | hexdump completo su più righe per trasferimento (anteprima inline compatta altrimenti) |
--json | off | emette NDJSON (un oggetto per evento) invece della vista TTY |
--page-offset-base | off | indirizzo kernel page_offset_base (hex) — abilita la cattura del payload SG (x86-64) |
--vmemmap-base | off | indirizzo kernel vmemmap_base (hex) — da abbinare a --page-offset-base |