tcpdumpやWiresharkで取得したpcapファイルがある。その中から何かを調べたい。リモートサーバーにSSH接続中だったり、CIパイプラインの中で作業しているなら、WiresharkのGUIは使えない。tsharkがそのギャップを埋めてくれる — WiresharkのフルDissectionエンジンに対応したCLIフロントエンドだ。フィールド抽出の構文を覚えてしまえば、生のパケットキャプチャがシェルから直接クエリできる構造化データに変わる。
本番環境のインシデント調査でこれを使ったことがある — 複数ノードのキャプチャを同時にパースし、100MBを超えるファイルも処理した。文句ひとつ言わずこなしてくれる。
アプローチ比較:tshark vs tcpdump vs スクリプト解析
既存のpcapファイルを解析する場合、現実的な選択肢は3つある:
オプション1:tcpdumpで読み直す
tcpdump -r file.pcapでpcapを再生するのは軽量でどこでも使える。ただし、tcpdumpの出力はプロトコル認識が限定的なライン指向のテキストだ — ざっと確認するには向いているが、特定フィールドの抽出やプロトコル別バイト数の計算には向かない。
オプション2:Pythonで生バイトをパースする(scapy / dpkt)
scapyならPythonで完全なコントロールが得られる。パケットを1つずつ繰り返し処理して、好きな統計を構築できる。ただし欠点は深刻だ:セットアップのオーバーヘッド、50MBを超えるファイルでの処理速度の低下、そしてWiresharkがすでに正しく処理しているプロトコルパーシング(自分では気づかないエッジケースを含む)を再実装する羽目になる。
オプション3:フィールド抽出を使ったtshark
tsharkはWiresharkと一緒に提供され、3000以上のプロトコルをすぐに理解できる。Displayフィルタ(WiresharkのGUIと同じ構文)でフィルタリングし、名前付きフィールドをタブ区切りの列として抽出し、出力をawk、sort、またはPythonにパイプできる。pcapファイルから具体的な答えへの最短ルートだ。
メリットとデメリット
tsharkの強み
- WiresharkのフルDissectionエンジン — TLSハンドシェイク、HTTP/2、DNS over TCPなど、正確なプロトコルパーシング
- Displayフィルタの構文は表現豊かでドキュメントも充実している
- フィールド名は安定していて予測しやすい(
ip.src、tcp.dstport、http.request.uri) - 組み込みの統計コマンド(
-zフラグ)で外部処理を完全にスキップできる - ライブインターフェースと既存のpcapファイルの両方に同じフラグで動作する
tsharkの弱点
- バイナリフットプリントが大きい — WiresharkはQtと多数のDissectorライブラリを引き込む
- 会話テーブルの構築時に非常に大きなキャプチャでメモリ消費が増加する
-z統計フラグはメジャーバージョン間で構文が一貫していない- 最小限のサーバーイメージにはデフォルトでインストールされていない
代わりにtcpdumpを選ぶタイミング
キャプチャをざっと確認したり、パケットがホストに届いたかを確認するだけなら、tcpdumpの方が入力が速くて常に使える。「DNSクエリでNXDOMAINが返った件数は?」や「最もバイト数を送信したIPは?」といった具体的な質問があるときにtsharkを使おう。
推奨セットアップ
解析マシンにtsharkをインストールする — キャプチャノードには不要で、pcapファイルをコピーするだけでいい:
# Debian / Ubuntu
sudo apt install tshark -y
# RHEL / Rocky / AlmaLinux
sudo dnf install wireshark-cli -y
# Arch
sudo pacman -S wireshark-cli
Debianのインストーラーは、rootではないユーザーがパケットをキャプチャできるかどうかを尋ねてくる。「はい」を選んで、自分をグループに追加しよう:
sudo usermod -aG wireshark $USER
newgrp wireshark
バージョンを確認しよう — フィールド名はメジャーリリース間で変わることがある:
tshark --version | head -1
実装ガイド
ステップ1:pcapファイルをすばやく検査する
フィルタリングの前に、まずざっと確認しよう:
# パケット総数を数える
tshark -r capture.pcap 2>/dev/null | wc -l
# プロトコルサマリー付きで最初の20パケットを表示する
tshark -r capture.pcap -c 20
2>/dev/nullはスクリプトを汚すインターフェース/バージョンバナーを抑制する。パイプで使うすべてのtsharkコマンドに追加しよう。
ステップ2:Displayフィルタでプロトコルをフィルタリングする
DisplayフィルタはWiresharkのGUIフィルタバーと同じ構文を使う。tcpdumpのBPF構文との重要な違い:Displayフィルタは生バイトではなく、デコードされたプロトコルフィールドに対して動作する。つまり、特定のオフセットでバイトをマッチさせようとする代わりにdns.flags.rcodeでフィルタリングできる。
# DNS通信のみ
tshark -r capture.pcap -Y 'dns' 2>/dev/null
# HTTPリクエスト(レスポンスは除く)
tshark -r capture.pcap -Y 'http.request' 2>/dev/null
# TCP RSTパケット — 接続拒否の検出に役立つ
tshark -r capture.pcap -Y 'tcp.flags.reset == 1' 2>/dev/null
# NXDOMAINを返したDNSクエリ
tshark -r capture.pcap -Y 'dns.flags.rcode == 3' 2>/dev/null
# 2つの特定ホスト間のトラフィック
tshark -r capture.pcap -Y 'ip.addr == 10.0.1.5 and ip.addr == 10.0.1.1' 2>/dev/null
ステップ3:特定フィールドを列として抽出する
-T fieldsフラグと-e fieldnameを組み合わせると、awk、sort、またはPythonですぐに使えるタブ区切り出力が得られる。手動パーシングは不要 — デコードされたプロトコルデータからの列抽出だけだ。
# すべてのTCPパケットの送信元IP、宛先IP、宛先ポートを抽出する
tshark -r capture.pcap -Y 'tcp' -T fields \
-e ip.src \
-e ip.dst \
-e tcp.dstport \
2>/dev/null
# DNSクエリ名とレスポンスコードを抽出する
tshark -r capture.pcap -Y 'dns' -T fields \
-e dns.qry.name \
-e dns.flags.rcode \
2>/dev/null
# すべてのHTTPリクエストのメソッド、ホスト、URIを抽出する
tshark -r capture.pcap -Y 'http.request' -T fields \
-e http.request.method \
-e http.host \
-e http.request.uri \
2>/dev/null
正確なフィールド名がわからない?Wiresharkでパケット詳細ペインのフィールドにカーソルを合わせると、下部のステータスバーに名前が表示される。またはフィールドレジストリから直接検索することもできる:
tshark -G fields 2>/dev/null | grep -i 'user.agent'
ステップ4:-zでトラフィック統計を構築する
-zフラグはtsharkの組み込み統計モジュールを呼び出す。フィールド出力を外部ツールにパイプするより速い — すべてが1回のパスで実行されるからだ — 中間出力なし、余分なシェルプロセスもなし。
# プロトコル階層 — プロトコル別のパーセンテージ内訳
tshark -r capture.pcap -z io,phs -q 2>/dev/null
# IPごとのトップトーカー(転送バイト数)
tshark -r capture.pcap -z conv,ip -q 2>/dev/null | sort -k 6 -rn | head -20
# DNSクエリ/レスポンスのサマリー
tshark -r capture.pcap -z dns,tree -q 2>/dev/null
# HTTPリクエストメソッドとレスポンスコード
tshark -r capture.pcap -z http,tree -q 2>/dev/null
# TCP接続サマリー(ペアごとのSYN/FIN/RST数)
tshark -r capture.pcap -z conv,tcp -q 2>/dev/null
-qフラグはパケットごとの出力を抑制し、統計テーブルだけを表示する。
ステップ5:カスタム統計のためにフィールド抽出とUnixツールを組み合わせる
組み込みの統計がすべての質問をカバーするわけではない。カスタムの答えが必要な場合、フィールド出力を標準のUnixツールにパイプしよう:
# ドメインごとのDNSクエリ数を頻度順でカウントする
tshark -r capture.pcap -Y 'dns.flags.response == 0' -T fields \
-e dns.qry.name 2>/dev/null \
| sort | uniq -c | sort -rn | head -20
# 接続先の上位ポートを探す
tshark -r capture.pcap -Y 'tcp.flags.syn == 1 and tcp.flags.ack == 0' \
-T fields -e tcp.dstport 2>/dev/null \
| sort | uniq -c | sort -rn
# すべてのHTTP User-Agent文字列を抽出する(ボット検出に役立つ)
tshark -r capture.pcap -Y 'http.user_agent' -T fields \
-e http.user_agent 2>/dev/null \
| sort -u
ステップ6:フィルタリングしたパケットを新しいpcapにエクスポートする
500MBのファイルを移動させずに同僚と共有したり、別のツールに渡したりするために、キャプチャの一部だけが必要な場合もある:
# DNS通信のみを新しいファイルに保存する
tshark -r capture.pcap -Y 'dns' -w dns_only.pcap 2>/dev/null
# 特定ホストへ/からのトラフィックを保存する
tshark -r capture.pcap -Y 'ip.addr == 192.168.1.100' -w host_traffic.pcap 2>/dev/null
実用的なスクリプト:pcapクイックサマリー
インシデントのトリアージ用に用意しているシェルスクリプトを紹介する。pcapファイルを渡すと、10秒以内に全体像を把握できる:
#!/bin/bash
# 使い方:./pcap-summary.sh capture.pcap
FILE="${1:-capture.pcap}"
echo "=== パケット数 ==="
tshark -r "$FILE" 2>/dev/null | wc -l
echo "=== プロトコル内訳 ==="
tshark -r "$FILE" -z io,phs -q 2>/dev/null
echo "=== 上位10トーカー ==="
tshark -r "$FILE" -z conv,ip -q 2>/dev/null \
| tail -n +8 | sort -k6 -rn | head -10
echo "=== DNS失敗(NXDOMAIN) ==="
tshark -r "$FILE" -Y 'dns.flags.rcode == 3' -T fields \
-e dns.qry.name 2>/dev/null | sort | uniq -c | sort -rn
echo "=== TCP RSTの送信元 ==="
tshark -r "$FILE" -Y 'tcp.flags.reset == 1' -T fields \
-e ip.src -e tcp.srcport 2>/dev/null | sort | uniq -c | sort -rn | head -10
高スループットのサービスでは、pcapファイルはあっという間にギガバイト単位に膨れ上がる。最も効果的な2つのことがある:Displayフィルタをできるだけ絞り込む(絞り込むほど、tsharkの走査量が減る)、そして常に-zと-qをセットで使う。-qなしでは、統計テーブルと並んでパケットごとの出力がターミナルに溢れてしまう。
注意すべきこと
- 複数値フィールド:1つのパケットに複数の質問が含まれている場合、
dns.qry.nameに対して複数の値が返されることがある。tsharkはデフォルトでカンマで区切る — 1行に1つの値が必要な場合は-E separator=\nを使おう。 - 暗号化通信:セッションキーファイルを提供すれば、tsharkはTLSを復号できる:
-o tls.keylog_file:/path/to/sslkeylog.log。このファイルを生成するには、ブラウザやアプリケーションでSSLKEYLOGFILEを設定しよう。 - 大容量ファイル:1GBを超えるキャプチャは、まず
editcap -c 100000 big.pcap split/out.pcapで分割してから、各チャンクを並行処理しよう。
