
eBPF 기반의 실시간 시스템 전역 USB 전송 스니퍼 — 두 개의 범용 URB 후크로부터 USB 트래픽을 인라인으로 디코딩합니다 (control SETUP, SCSI, HID). usbmon 불필요, 하드웨어 스니퍼 불필요. CO-RE 이식 가능.

모든 호스트 컨트롤러 드라이버가 통과하는 두 개의 범용 URB 병목 지점을 기반으로 구축된 시스템 전체의 실시간 색상화된 USB 트래픽 피드로, xHCI/EHCI/OHCI/dwc에서 동일하게 작동하며 컨트롤러별 트레이스포인트나 usbmon이 필요하지 않습니다. 완전히 CO-RE 이식 가능합니다.
| fentry 후크 | 알려주는 정보 |
|---|---|
usb_submit_urb | 전송이 대기됨 (장치, 엔드포인트, 유형, 페이로드) |
usb_hcd_giveback_urb | 완료됨 (상태, 이동된 바이트, 지연 시간, 페이로드) |
URB 포인터로 키가 지정된 lru_hash가 두 개를 연결합니다: 제출은 시작 시간을 기록하고, 완료는 제출→완료 지연 시간을 위해 이를 읽어온 후 삭제합니다. 이는 httpbody의 요청/응답 쌍을 반영합니다 — SUBMIT은 "요청"(호스트가 보내는 것), COMPLETE는 "응답"(장치가 반환하는 것)입니다.
제어 전송은 8바이트 SETUP 패킷이 표준 요청 이름(GET_DESCRIPTOR, SET_CONFIGURATION, …)으로 디코딩됩니다; 데이터 단계는 텍스트처럼 보이면 텍스트로, 그렇지 않으면 hexdump로 렌더링됩니다.
출력은 이벤트당 한 줄(간결). 장치가 처음 나타나면 ▸ 범례 줄(bus-dev, vid:pid, 제품, 링크 속도)을 표시합니다; 그 후 각 행은 짧은 DEV 태그만 포함하므로 왼쪽 열이 정렬된 상태로 유지되어 많은 트래픽에서도 스캔 가능합니다. 각 행은 시간, 종류(SUBMIT/CMPLT), 전송 유형, epNdir, 방향 화살표(← 장치→호스트 IN, → 호스트→장치 OUT), 바이트 수, 상태, 지연 시간, 소유 커널 드라이버를 표시한 다음 ·과 가장 유용한 세부 정보(디코딩된 SETUP, SCSI 명령 또는 짧은 페이로드 미리보기)를 표시합니다. 대신 전체 여러 줄 hexdump를 보려면 --hex를 전달하세요. Hex 바이트는 TTY에서 값 클래스별로 색상이 지정됩니다(null 파란색, 인쇄 가능한 ASCII 시안, 공백 녹색, 기타 제어 마젠타, 높은/비-ASCII 노란색); 파이프된 출력은 일반 텍스트입니다.
usbmon 설정이 필요 없습니다. SETUP 패킷과 페이로드는 장치를 조작할 때 디코딩됩니다.READ(10) lba=… blocks=…, WRITE(10), CSW PASS/FAIL).--errors-only는 모든 장치에서 스톨(EPIPE), 타임아웃, 버블, CRC 오류를 한 번에 표시합니다.INT 보고서나 사용자가 트리거하지 않은 SET_REPORT 제어 쓰기로 나타납니다.--json은 NDJSON을 내보냅니다; jq나 파일로 파이프하여 실행 간 페이로드를 diff할 수 있습니다.curl -fsSL https://yeet.cx | sh
그런 다음 GitHub에서 바로 실행합니다 — yeet이 예제를 가져와 빌드하므로 클론이 필요하지 않습니다:
yeet run github:yeet-src/usbsnoop
로컬 체크아웃에서 빌드하려면:
make
커널의 BTF를 vmlinux.h(struct urb, usb_device, 장치 디스크립터용)로 덤프한 후 컴파일합니다. clang, bpftool, 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
모든 필터링은 커널 측에서 수행되므로 필터링된 트래픽은 사용자 공간에 도달하지 않습니다.
각 이벤트 줄은 대괄호 안에 소유 커널 드라이버로 끝납니다([hid_irq_in], [usb_api_blocking_completion]) — urb->complete은 bpf_snprintf("%ps")를 통해 커널 내에서 심볼화되므로 /proc/kallsyms 조회가 필요하지 않습니다. 대용량 저장장치 벌크 전송은 Bulk-Only Transport 래퍼를 SCSI 명령으로 디코딩합니다(CBW READ(10) lba=… blocks=… / CSW PASS). 시간 제한 종료(--secs 도달) 시 장치별 요약과 log2 지연 시간 히스토그램이 출력됩니다; Ctrl-C 종료는 이를 건너뜁니다(JS에서 볼 수 있는 신호 훅이 없습니다).
벌크 트래픽(대용량 저장장치 등)은 종종 단일 선형 transfer_buffer 대신 struct scatterlist 배열(urb->sg)을 스택에 전달하므로 페이로드가 페이지 전체에 분산됩니다. usbsnoop은 해당 배열을 탐색하고 각 세그먼트의 바이트를 복사하지만, 접근하려면 페이지를 커널 가상 주소로 변환해야 합니다 — 이는 x86-64의 page_to_virt의 역으로, 실행 중인 커널의 page_offset_base와 vmemmap_base(둘 다 KASLR 무작위화됨)가 필요합니다.
JS 격리 환경은 /proc/kallsyms를 읽을 수 없고 로더에 ksym 지원이 없으므로 두 심볼 주소를 전달하면 BPF 측에서 이를 역참조합니다:
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)
해당 플래그가 없으면 SG 전송은 여전히 전체 메타데이터를 표시하지만 페이로드 바이트는 표시되지 않습니다 — 이전 동작과 동일합니다. 이 경로는 x86-64 전용입니다: 다른 아키텍처에서는 플래그를 생략하세요.
actual/requested 길이를 보고합니다. 각 링 레코드는 전체 data[16384]를 포함하므로 8 MiB 링에는 약 512개의 이벤트가 저장됩니다.--page-offset-base / --vmemmap-base 플래그와 x86-64 호스트가 필요합니다; 각 세그먼트는 최대 한 페이지까지 캡처되며, 전송의 처음 64개 세그먼트만 탐색됩니다.| flag | default | 의미 |
|---|
--secs | forever | 실행 시간; 생략하면 Ctrl-C까지 실행 (숫자를 지정하면 중지 후 요약 출력) |
--vid, --vendor-id | any | 벤더 ID로 필터링 (16진수 0x1d6b 또는 10진수) |
--pid, --product-id | any | 제품 ID로 필터링 |
--bus | any | 버스 번호로 필터링 |
--dev | any | 장치 주소로 필터링 |
--type | all | iso, int, control, bulk의 csv |
--no-data | off | 전송 버퍼를 읽지 않음 (메타데이터만) |
--max-data | 4096 | 이벤트당 렌더링되는 최대 페이로드 바이트 |
--errors-only | off | OK가 아닌 완료만 표시 (SUBMIT 및 OK 건너뜀) |
--hex | off | 전송당 전체 여러 줄 hexdump (그렇지 않으면 간결한 인라인 미리보기) |
--json | off | TTY 보기 대신 NDJSON (이벤트당 하나의 객체) 내보내기 |
--page-offset-base | off | 커널 page_offset_base 주소 (16진수) — SG 페이로드 캡처 활성화 (x86-64) |
--vmemmap-base | off | 커널 vmemmap_base 주소 (16진수) — --page-offset-base와 함께 사용 |