Linuxにおけるtcpdump完全マスター:パケットキャプチャ、トラフィックフィルタリング、ネットワークトラブルシューティング

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

クイックスタート(5分)

半年前、秒間約3,500リクエストを処理していた弊社の決済ゲートウェイノードで、突発的な504 Gateway Timeoutが散発するようになりました。プライベートAWS VPC内に配置された最小構成のヘッドレスUbuntuインスタンスだったため、WiresharkのようなGUIツールの利用は論外でした。サーバー上で直接生のワイヤートラフィックを調査するしかありませんでした。この障害をきっかけに、私たちはtcpdumpをデフォルトの一次切り分けツールとして活用するようになり、現在でもソケットレベルのパケットドロップを突き止める最速の手段であり続けています。

使い始めるのは数分で完了します。多くのエンタープライズ向けディストリビューションにはtcpdumpがプリインストールされています。最小構成のDebian、Alpine、またはRHELコンテナイメージから作業を始める場合は、まずインストールしてください:

# Debian/Ubuntuの場合
sudo apt-get update && sudo apt-get install -y tcpdump

# RHEL/AlmaLinux/Rockyの場合
sudo dnf install -y tcpdump

キャプチャ可能なネットワークインターフェースを確認します:

sudo tcpdump -D

これを実行すると、eth0やens3といったアクティブな物理/仮想インターフェース、さらに疑似インターフェースであるanyが表示されます。DNSルックアップを待たずに特定のインターフェースでリアルタイムのトラフィック監視を開始するには、次のように実行します:

sudo tcpdump -i eth0 -n

-nフラグは逆引きDNS解決を無効化します。PTRクエリをスキップすることで、アクティブなフローを調査している間にtcpdumpが上流のDNSリゾルバへ過度な問い合わせを送るのを防ぎます。十分なサンプルが集まったらCtrl+Cを押してください。コマンドが終了し、キャプチャされたフレーム数、フィルタに一致した数、カーネルバッファでドロップされた数が集計されたサマリーが表示されます。

詳細解説

フィルタリングを行わないパケットダンプは、ターミナルのバッファを一瞬で溢れさせてしまいます。10 Gbpsリンクでは、Ctrl+Cを押す間もなく5万パケット以上がスクロールして流れてしまうことがあります。この膨大なトラフィックの流れを正しく把握するには、ワイヤーフォーマットの読み解き方と、Berkeley Packet Filter(BPF)を用いた正確なフィルタリング技術を身につける必要があります。

tcpdumpの出力フォーマットを読み解く

tcpdump -nn -Sでキャプチャした典型的なTCPパケットのヘッダー行は以下のようになります:

14:22:01.408219 IP 192.168.1.50.44322 > 10.0.0.15.80: Flags [P.], seq 1827401:1827945, ack 392019, win 502, length 544

主な構成要素は次のとおりです:

  • タイムスタンプ: マイクロ秒単位の高精度なシステム時刻(14:22:01.408219)。
  • 送信元 > 送信先: クライアント192.168.1.50:44322からWebサーバー10.0.0.15:80へデータを送信。
  • TCPフラグ: [P.]はPush(P)とAcknowledge(.)を表します。一般的なフラグには、[S](SYN)、[S.](SYN-ACK)、[F](FIN)、[R](RST)、[.](純粋なACK)などがあります。
  • シーケンス番号とACK番号: ペイロードの送信を追跡するバイトオフセットです。ここではseq 1827401:1827945により、544バイトのデータが送信されたことが確認できます(1,827,945 – 1,827,401 = 544)。
  • ウィンドウサイズ & 長さ: アドバタイズされた受信バッファサイズ(win 502)とセグメントのペイロードサイズ(length 544)です。

Berkeley Packet Filter(BPF)によるターゲットを絞ったフィルタリング

BPF式は、パケットがユーザ空間のメモリーバッファに到達する前にカーネル空間内で実行されます。不要なフレームを早い段階で破棄することで、CPU負荷のスパイクを防ぎ、リングバッファのオーバーフローを防止できます。IPアドレス、CIDRブロック、ポート、トランスポートプロトコルによってキャプチャ対象を絞り込むことが可能です:

# 特定のクライアントIPに関連するHTTPおよびHTTPSトラフィックをキャプチャ
sudo tcpdump -i eth0 -nn "host 192.168.1.100 and (port 80 or port 443)"

# 外部データベースのサブネット宛てのアウトバウンドトラフィックを監視
sudo tcpdump -i eth0 -nn "dst net 10.10.0.0/16 and port 5432"

and、or、notといった論理演算子を組み合わせることで、操作中のターミナルセッションによるSSHのノイズを除外できます:

sudo tcpdump -i any -nn "not port 22"

高度な活用法

非対称ルーティングや突発的な上流からのリセットといったエッジケースを追跡する場合、基本的なポートフィルタだけでは不十分です。こうした難解な問題を解決可能にする2つのテクニックがあります。キャプチャをバイナリの.pcapファイルへストリーミング出力する方法と、TCPヘッダーの生バイトオフセットに対してフィルタリングを行う方法です。

オフライン調査のためのPCAPファイルの書き出しと読み込み

トラフィック急増時にターミナルの出力をリアルタイムで追うのは、重要な手がかりを見落とす原因になります。代わりに生のバイナリストリームを直接ディスクへ書き出し、ローカル環境でパースするかWiresharkに読み込ませて分析しましょう:

# ペイロード全体を最大50,000パケットまでcapture.pcapに保存
sudo tcpdump -i eth0 -nn -s 0 -c 50000 -w /tmp/api_traffic.pcap "port 8080"

リアルタイムでパケットを再キャプチャすることなく、保存したキャプチャファイルを分析するには:

tcpdump -nn -r /tmp/api_traffic.pcap "tcp[tcpflags] & tcp-rst != 0"

TCPハンドシェイクとコネクションリセットの分離・抽出

プロキシ経由のタイムアウトや切断されたセッションを診断するには、予期せぬRSTパケットやACKが返らないSYNを特定する必要があります。BPFを使用すると、制御フラグが格納されているTCPヘッダーの13バイト目に対してビット単位のチェックを実行できます:

# 初回の接続リクエスト(ACKを伴わないSYN)のみをキャプチャ
sudo tcpdump -i eth0 -nn "tcp[tcpflags] & (tcp-syn) != 0 and tcp[tcpflags] & (tcp-ack) == 0"

# 予期せぬ接続切断(TCP RSTパケット)を検出
sudo tcpdump -i eth0 -nn "tcp[tcpflags] & (tcp-rst) != 0"

暗号化されていないHTTPヘッダーや生のRedisクエリを調査する必要がある場合は、ASCII表示フラグ(-A)や16進数/ASCII同時表示フラグ(-X)を使用しつつ、実際にペイロードデータを含むパケットのみをフィルタリングします:

# 純粋なACKを除外し、ペイロードバイトをASCII形式で表示
sudo tcpdump -i eth0 -nn -A -s 0 "port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12:1] & 0xf0)>>2)) != 0)"

実践的な運用のヒント

高スループットなシステムでパケットキャプチャを行う際、パラメータを制限しないまま実行するとCPUやディスクI/Oに過度な負荷をかける恐れがあります。高負荷な本番環境で安全に監視を行うための運用ルールを以下に示します:

  • スナップ長(snaplen: -s)を制限する: 最近のバージョンのtcpdumpでは、デフォルトでパケット全体(262,144バイト)をキャプチャします。ルーティングメタデータとフラグのみが必要な場合は、-s 96でキャプチャサイズを制限してください。秒間12,000パケットを処理する4コア/16GBのAPIノードにおいて、スナップ長を96バイトに切り詰めたことで、ディスク書き込みスループットを18 MB/sから1.2 MB/s未満へと削減できました。
  • 常に-nと-nnを組み合わせて使用する: -nがIPの逆引きDNS解決を停止するのに対し、-nnはポート番号からサービス名への変換を停止します。これを指定しないと、tcpdumpは/etc/servicesを参照してポート443をhttpsへ、ポート3306をmysqlへと変換しようとします。両方のフラグを併用することで、トラフィック急増時のわずかなCPUレイテンシを削ぎ落とすことができます。
  • ファイルをローテーションしてディスク枯渇を防ぐ: 長時間のデバッグセッションは、気付かないうちにルートパーティションを逼迫させます。-C(100万バイト単位のファイルサイズ制限)と-W(最大ファイル数)を組み合わせて、サイズが制限された循環バッファ(リングバッファ)を構築しましょう:
# ローリングリングバッファ:各100MBのファイルを最大5世代保持
sudo tcpdump -i eth0 -nn -s 96 -C 100 -W 5 -w /var/log/traffic_roll.pcap "port 443"
  • カーネルのドロップ数を確認する: Ctrl+Cでキャプチャセッションを終了する際は、サマリー行のpackets dropped by kernel(カーネルによりドロップされたパケット数)を確認してください。この値がゼロでない場合、tcpdumpが読み出す前にソケットバッファが満杯になったことを意味します。ドロップが発生している場合は、-s 96でスナップ長を制限するか、BPF式をより厳密に絞り込むか、あるいは一時的なキャプチャファイルの書き出し先として/dev/shmなどのRAMディスクを直接指定してください。

BPF式とフラグオフセットの仕組みを一度理解してしまえば、tcpdumpは原因不明なネットワーク不具合を、明確で対策可能なデータへと変換してくれます。パケット配送に関する当て推量を排除し、ハンドシェイクの失敗やセッションの切断をわずか数分で特定できるようになるでしょう。

Share: