
Sniffer vivo de transferências USB em todo o sistema em eBPF — decodifica tráfego USB inline (controle SETUP, SCSI, HID) a partir de dois ganchos URB universais. Sem usbmon, sem sniffer de hardware. Portátil CO-RE.

Um feed em tempo real e colorido do tráfego USB em todo o sistema — construído sobre os dois pontos de estrangulamento universais de URB pelos quais todo driver de host-controller passa, funcionando igualmente em xHCI/EHCI/OHCI/dwc, sem tracepoints por controlador e sem usbmon. Totalmente portátil via CO-RE.
| fentry hook | o que nos informa |
|---|---|
usb_submit_urb | uma transferência foi enfileirada (dispositivo, endpoint, tipo, payload) |
usb_hcd_giveback_urb | foi concluída (status, bytes movidos, latência, payload) |
Um lru_hash indexado pelo ponteiro do URB costura os dois: o submit registra um tempo inicial, a conclusão o lê de volta para a latência submit→complete e então o exclui. Isso espelha o pareamento requisição/resposta do httpbody — SUBMIT é a "requisição" (o que o host envia), COMPLETE a "resposta" (o que o dispositivo retorna).
Transferências de controle têm seu pacote SETUP de 8 bytes decodificado no nome de requisição padrão (GET_DESCRIPTOR, SET_CONFIGURATION, …); os estágios de dados são renderizados como texto quando parecem textuais e como hexdump caso contrário.
A saída é uma linha por evento (compacta). Na primeira vez em que um dispositivo aparece, ele recebe uma linha de legenda ▸ (bus-dev, vid:pid, produto, velocidade do link); depois disso, cada linha carrega apenas a tag curta DEV, para que as colunas à esquerda permaneçam alinhadas e escaneáveis sob tráfego intenso. Cada linha mostra tempo, tipo (SUBMIT/CMPLT), tipo de transferência, epNdir, a seta de direção (← dispositivo→host IN, → host→dispositivo OUT), contagens de bytes, status, latência e o driver do kernel responsável, seguidos de um · e o detalhe mais útil (SETUP decodificado, comando SCSI ou uma prévia curta do payload). Passe --hex para o hexdump multilinha completo. Em um TTY, os bytes hex são coloridos por classe de valor (null azul, ASCII imprimível ciano, espaços em branco verde, outros controles magenta, alto/não-ASCII amarelo); saída via pipe é sem cor.
usbmon. Pacotes SETUP e payloads são decodificados enquanto você interage com o dispositivo.READ(10) lba=… blocks=…, WRITE(10), CSW PASS/FAIL).--errors-only exibe stalls (EPIPE), timeouts, babble e erros de CRC em todos os dispositivos de uma vez.INT ou escritas de controle SET_REPORT que você não disparou.--json emite NDJSON; use pipe para jq ou para um arquivo e faça diff dos payloads entre execuções.curl -fsSL https://yeet.cx | sh
Em seguida, execute-o diretamente do GitHub — o yeet busca o exemplo e o compila para você, sem necessidade de clone:
yeet run github:yeet-src/usbsnoop
Para compilar a partir de um checkout local:
make
Despeja o BTF do kernel em vmlinux.h (para struct urb, usb_device e o descritor de dispositivo) e então compila. Requer clang, bpftool e um kernel com BTF.
yeet run . # todos os dispositivos, executa até Ctrl-C
yeet run . -- --secs 30 # para após 30s (imprime um resumo)
yeet run . -- --vid 0x320f # um vendor
yeet run . -- --vendor-id 0x046d --product-id 0xc52b # um dispositivo por id
yeet run . -- --bus 3 --dev 4 # um dispositivo por endereço de barramento
yeet run . -- --type control,int # apenas esses tipos de transferência
yeet run . -- --no-data # somente metadados, sem captura de payload
yeet run . -- --max-data 64 # limita o payload renderizado a 64 bytes
yeet run . -- --errors-only # apenas conclusões com falha (stalls, timeouts)
yeet run . -- --hex # hexdump multilinha completo por transferência
yeet run . -- --json | jq . # NDJSON, um objeto por evento
Toda a filtragem acontece no lado do kernel; portanto, o tráfego filtrado nunca chega ao userspace.
Cada linha de evento termina com o driver do kernel responsável entre colchetes ([hid_irq_in], [usb_api_blocking_completion]) — urb->complete é simbolizado no kernel via bpf_snprintf("%ps"), portanto não é necessária uma consulta a /proc/kallsyms. Transferências bulk de mass-storage decodificam seu wrapper Bulk-Only Transport no comando SCSI (CBW READ(10) lba=… blocks=… / CSW PASS). Numa saída por tempo (ao atingir --secs), são impressos um resumo por dispositivo e um histograma de latência em log2; uma saída por Ctrl-C os ignora (não há hook de sinal visível em JS).
O tráfego bulk (mass storage e afins) frequentemente entrega à pilha um array struct scatterlist (urb->sg) em vez de um único transfer_buffer linear, de modo que o payload fica espalhado por páginas. O usbsnoop percorre esse array e copia os bytes de cada segmento, mas alcançá-los significa traduzir uma página para seu endereço virtual do kernel — o inverso de page_to_virt do x86-64, que precisa de page_offset_base e vmemmap_base do kernel em execução (ambos randomizados por KASLR).
O isolado JS não consegue ler /proc/kallsyms e o loader não tem suporte a ksym, então você passa os dois endereços de símbolo e o lado BPF os dereferencia:
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)
Sem essas flags, as transferências SG ainda exibem metadados completos, apenas sem os bytes do payload — o comportamento anterior. Esse caminho é somente x86-64: em outras arquiteturas, deixe as flags desativadas.
actual/requested. Cada registro do anel carrega um data[16384] completo, portanto o anel de 8 MiB comporta ~512 eventos.--page-offset-base / --vmemmap-base acima e de um host x86-64; cada segmento é capturado até uma página, e apenas os primeiros 64 segmentos de uma transferência são percorridos.| flag | padrão | significado |
|---|
--secs | para sempre | por quanto tempo executar; omita para executar até Ctrl-C (um número interrompe e imprime um resumo) |
--vid, --vendor-id | qualquer | filtra por id de vendor (hex 0x1d6b ou decimal) |
--pid, --product-id | qualquer | filtra por id do produto |
--bus | qualquer | filtra por número do barramento |
--dev | qualquer | filtra por endereço do dispositivo |
--type | todos | csv de iso, int, control, bulk |
--no-data | desativado | não lê os buffers de transferência (somente metadados) |
--max-data | 4096 | máximo de bytes de payload renderizado por evento |
--errors-only | desativado | mostra apenas conclusões não-OK (ignora SUBMIT e OK) |
--hex | desativado | hexdump multilinha completo por transferência (caso contrário, prévia inline compacta) |
--json | desativado | emite NDJSON (um objeto por evento) em vez da visão TTY |
--page-offset-base | desativado | endereço do page_offset_base do kernel (hex) — permite captura de payload SG (x86-64) |
--vmemmap-base | desativado | endereço do vmemmap_base do kernel (hex) — usado junto com --page-offset-base |