
eBPFによるライブでシステム全体のUSB転送スニファー — 2つのユニバーサルURBフックからUSBトラフィックをインラインでデコードします(コントロールSETUP、SCSI、HID)。usbmon不要、ハードウェアスニファー不要。CO-REポータブル。

システム全体のUSBトラフィックをリアルタイムで色分け表示 — すべてのホストコントローラドライバが通過する2つの汎用URBチョークポイントに基づいて構築されているため、xHCI/EHCI/OHCI/dwcで動作し、コントローラごとのトレースポイントやusbmonは不要です。完全にCO-REポータブル。
| fentryフック | 示す内容 |
|---|---|
usb_submit_urb | 転送がキューされた(デバイス、エンドポイント、タイプ、ペイロード) |
usb_hcd_giveback_urb | 完了した(ステータス、転送バイト数、レイテンシ、ペイロード) |
URBポインタをキーとするlru_hashが2つを結合します。サブミットは開始時刻を記録し、完了時にその時刻を読み出してサブミット→完了のレイテンシを計算し、削除します。これはhttpbodyのリクエスト/レスポンスペアリングを反映しています — SUBMITが「リクエスト」(ホストが送信するもの)、COMPLETEが「レスポンス」(デバイスが返すもの)です。
コントロール転送では、8バイトのSETUPパケットがデコードされ、標準的なリクエスト名(GET_DESCRIPTOR, SET_CONFIGURATIONなど)が表示されます。データステージは、テキストのように見える場合はテキストとして表示され、それ以外の場合は16進ダンプとして表示されます。
出力はイベントごとに1行(コンパクト)。デバイスが初めて出現すると、▸凡例行(bus-dev、vid:pid、製品名、リンク速度)が表示されます。その後は各行に短いDEVタグのみが付くため、左側の列は揃ったままで、トラフィックが多い場合でもスキャンしやすくなります。各行には、時刻、種類(SUBMIT/CMPLT)、転送タイプ、epNdir、方向矢印(← デバイス→ホストIN、→ ホスト→デバイスOUT)、バイト数、ステータス、レイテンシ、所有するカーネルドライバ、そして·の後に最も有用な詳細(デコードされたSETUP、SCSIコマンド、または短いペイロードプレビュー)が表示されます。代わりに完全な複数行16進ダンプを表示するには--hexを指定します。16進バイトはTTY上では値クラスごとに色分けされます(nullは青、印字可能ASCIIはシアン、空白は緑、その他の制御文字はマゼンタ、高位/非ASCIIは黄)。パイプ出力ではプレーンです。
usbmonのセットアップなしで、デバイスの列挙、ベンダーコントロールリクエスト、HIDレポートのやり取りをライブで監視できます。デバイスを操作すると、SETUPパケットとペイロードがデコードされます。READ(10) lba=… blocks=…, WRITE(10), CSW PASS/FAIL)にデコードされます。--errors-onlyを使用すると、すべてのデバイスにわたってストール(EPIPE)、タイムアウト、バブル、CRCエラーが一度に表示されます。INTレポートやSET_REPORTコントロール書き込みとして現れます。--jsonでNDJSONを出力。jqにパイプするかファイルに保存して、複数回の実行間でペイロードを比較できます。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 . # すべてのデバイス、Ctrl-Cまで実行
yeet run . -- --secs 30 # 30秒後に停止(サマリーを表示)
yeet run . -- --vid 0x320f # 特定のベンダー
yeet run . -- --vendor-id 0x046d --product-id 0xc52b # IDでデバイスを指定
yeet run . -- --bus 3 --dev 4 # バスアドレスでデバイスを指定
yeet run . -- --type control,int # これらの転送タイプのみ
yeet run . -- --no-data # メタデータのみ、ペイロードキャプチャをスキップ
yeet run . -- --max-data 64 # 表示するペイロードを64バイトに制限
yeet run . -- --errors-only # 失敗した完了のみ(ストール、タイムアウト)
yeet run . -- --hex # 転送ごとに完全な複数行16進ダンプ
yeet run . -- --json | jq . # NDJSON、イベントごとに1つのオブジェクト
すべてのフィルタリングはカーネル側で行われるため、フィルタリングされたトラフィックはユーザー空間に到達しません。
各イベント行の末尾には、所有するカーネルドライバが角括弧で表示されます([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サポートがないため、2つのシンボルアドレスを渡すと、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ホストが必要です。各セグメントは最大1ページまでキャプチャされ、転送の最初の64セグメントのみが走査されます。| フラグ | デフォルト | 意味 |
|---|
--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のカンマ区切り |
--no-data | off | 転送バッファを読み取らない(メタデータのみ) |
--max-data | 4096 | イベントごとに表示するペイロードの最大バイト数 |
--errors-only | off | OKでない完了のみ表示(SUBMITとOKをスキップ) |
--hex | off | 転送ごとに完全な複数行16進ダンプ(それ以外はコンパクトなインラインプレビュー) |
--json | off | TTYビューの代わりにNDJSONを出力(イベントごとに1オブジェクト) |
--page-offset-base | off | カーネルpage_offset_baseアドレス(16進) — SGペイロードキャプチャを有効化(x86-64) |
--vmemmap-base | off | カーネルvmemmap_baseアドレス(16進) — --page-offset-baseと併用 |