夜中の3時、PagerDutyの鋭いアラート音で叩き起こされる。
「Web APIのレイテンシが急増、一部のクライアントからバックエンドへの疎通が取れない」
こういう障害の時、君ならどうする?
とりあえずお決まりのように ping を打つよな。でも、返ってくるのは無慈悲な Request timeout の嵐。ここで焦って「死活監視が全滅だ!回線事業者(キャリア)の障害だ!」なんて騒ぎ立てたら、翌朝のポストモーテム(事後障害分析)でインフラチームの主導権を失うことになる。
ちょっと待て。パケットの気持ちになって考えてみろよ。
現代の堅牢なデータセンターやクラウド環境において、エッジのファイアウォール(FW)やロードバランサー(LB)が、ICMPエコー要求(ping)をセキュリティポリシーとしてバッサリ捨てているのは「仕様」として当たり前のことだ。ICMPが通らないからといって、そこにたどり着く経路(ルート)まで死んでいるとは限らない。
そこで登場するのが、今回の主役である tcptraceroute だ。
今回は、ルーティングの迷宮に迷い込んだTCPパケットをどう追跡するか、その泥臭くて美しい仕組みを現場の視点から紐解いていこう。
—
1. なぜ標準の traceroute ではダメなのか?
おさらいだが、通常の traceroute(あるいはLinuxの traceroute コマンドデフォルト)は、主に2つのプロトコルを使ってルーティングパスを暴く。
1. UDPパケット方式(主にBSD系):宛先の適当な高位ポート(33434番以降)に向けて、TTL(Time To Live)を1から順にインクリメントしながらUDPパケットを送りつける。ルーターがTTL切れを検知すると返してくる ICMP Time Exceeded をキャッチして経路を描画する。
2. ICMPエコー方式(主にWindowsやLinuxの -I オプション):ICMP Echo RequestのTTLをいじって同様のことをやる。
だが、これには大きな罠がある。
多くのモダンなWeb APIサーバーの前段にあるFWやセキュリティグループ(SG)は、「謎のUDPポートへのトラフィック」 や 「ICMP」 を冷徹にドロップ(破棄)するように設定されている。そのため、自社サービスのAPIエンドポイント(例: api.example.com:443)に対して通常の traceroute を実行しても、途中のルーターまでは見えても、肝心の「宛先サーバーの手前」でパケットが迷子になってしまうのだ。
「実際にクライアントが叩いているポート(443や80)に対して、通信は本当に届いているのか?」
「途中のどのホップで、どのファイアウォールがこのセッションを遮断しているのか?」
これらを正確に突き止めるために、「TCPのSYNパケット」をあえてtracerouteのプローブとして利用する tcptraceroute が必要になる。
—
2. tcptraceroute の裏側:SYNパケットとTTLのダンス
tcptraceroute の挙動は、実は非常にエレガントだ。
通常のTCP 3ウェイハンドシェイク(SYN -> SYN-ACK -> ACK)の最初のステップである SYNパケット の性質を巧みに利用している。
通信のシーケンスを追ってみよう。
[自分 (Client)] [途中のルーター (Router A)] [宛先サーバー (Target)]
| | |
|--- (1) SYN, TTL=1 ---------------------------------->| |
| <-- (2) ICMP Time Exceeded -----------------------| |
| (ルーターAがTTL切れを通知) | |
| | |
|--- (3) SYN, TTL=2 ------------------------------------------------------------------------>|
| <-- (4) TCP SYN-ACK (または RST) -------------------------------------------------------|
(宛先到達!ポートが開いていればSYN-ACK、閉じていればRST)
1. TTL = 1 の送信: まず、宛先の特定のTCPポート(例: 443)に向けて、TTLを 1 に設定したTCP SYNパケットを射出する。
2. ルーターからの悲鳴 (ICMP): すぐ隣のルーター(ホップ1)を通過する際、ルーターはTTLを1つ減らす。結果、TTLが 0 になるため、ルーターはパケットを破棄し、送信元へ ICMP Time Exceeded を送り返す。これで1つ目のホップのIPアドレスが判明する。
3. TTLをインクリメント: 次にTTLを 2 にして同様に送る。これを宛先に届くまで繰り返す。
4. 宛先からの直接応答: ついにTTLが十分になり、パケットが宛先サーバーのポートに到達すると、奇跡が起きる。
- ポートが開いている場合: サーバーは正常なTCPセッションのつもりで
SYN-ACKを返してくる。 - ポートが閉じている場合: サーバーは拒絶の意志を示す
RSTを返してくる。
ここで重要なのは、「宛先サーバーからの応答(SYN-ACKやRST)が返ってきた時点で、その宛先ポートへのルーティングおよびレイヤー4までの疎通が完全に担保された」 という事実だ。
途中のFWが当該ポートの通信をブロックしていれば、このSYN-ACKやRSTは返ってこないため、「どのルーターまでは進んだが、どこから先でパケットが消えたのか」が一目瞭然になる。
—
3. 実践:CLIでの tcptraceroute 活用術
百聞は一見に如かず。実際に現場でどう使うかを見ていこう。
多くのLinuxディストリビューションでは、tcptraceroute コマンドがパッケージとして用意されている。
# Ubuntu / Debian の場合
sudo apt-get install tcptraceroute
# RHEL / CentOS / AlmaLinux の場合 (epelリポジトリが必要)
sudo dnf install tcptraceroute
基本的なコマンド実行例
例えば、社内の特定のAPIサーバー(api.internal.net)のHTTPSポート(443)に対するパスを診断したいとする。
# api.internal.net の 443番ポートに向けてtcptracerouteを実行
sudo tcptraceroute api.internal.net 443
実行結果の読み方(出力イメージ):
Selected device: eth0, address: 192.168.10.50
Using TCP port 443, seq 1, ack 0
1 192.168.10.1 1.124 ms 0.952 ms 0.888 ms (ゲートウェイ)
2 10.100.0.1 2.411 ms 2.302 ms 2.215 ms (社内コアスイッチ)
3 * * * (ここでセキュリティFWがICMPをドロップしている)
4 203.0.113.50 14.512 ms 14.301 ms 14.289 ms (宛先APIサーバーのエッジルーター)
5 192.168.100.5 15.012 ms 14.955 ms 14.890 ms [open] (宛先サーバー到達・ポートオープン)
ホップ3が * * * になっているのは、途中のFWが ICMP Time Exceeded を返すのを無効化(あるいはレートリミット)しているからだ。しかし、ホップ4以降のパケットは無事に通過し、最終的にホップ5で [open] というステータスとともに宛先からの応答を捉えている。
「ICMPは捨てられているが、443番ポートのTCPセッションは確実に確立可能である」 という極めて重要な事実を、このワンコマンドで確信に変えることができるのだ。
—
4. PythonとScapyを使った「自作」tcptraceroute
現場のエンジニアたるもの、既製のツールが使えない制限された環境(例えば、特定のセキュリティコンテナ内など)に放り込まれることも珍しくない。そんな時、Pythonとパケット操作ライブラリである Scapy を使えば、数行のスクリプトで独自の tcptraceroute を即席で組み上げることができる。
以下に、実務のデバッグで役立つミニマムなスクリプトの例を示す。
#!/usr/bin/env python3
import sys
from scapy.all import IP, TCP, sr1, ICMP
def custom_tcptraceroute(target_ip, target_port, max_ttl=30):
print(f"Tracing route to {target_ip} on port {target_port} with TCP SYN...")
for ttl in range(1, max_ttl + 1):
# IPヘッダーのTTLをインクリメントし、TCPのSYNパケットを構築
pkt = IP(dst=target_ip, ttl=ttl) / TCP(dport=target_port, flags="S")
# パケットを送信し、最初に応答があったパケットを1秒間待つ
reply = sr1(pkt, timeout=1, verbose=0)
if reply is None:
# タイムアウト(途中のルーターがICMPを返さない場合など)
print(f"{ttl:2d} * * * (Timeout)")
continue
# 応答がICMPの場合(途中のルーターでのTTL切れ)
if reply.haslayer(ICMP):
if reply.getlayer(ICMP).type == 11: # Time Exceeded
print(f"{ttl:2d} {reply.src} (TTL Expired from router)")
else:
print(f"{ttl:2d} {reply.src} (ICMP Type: {reply.getlayer(ICMP).type})")
# 応答がTCPの場合(宛先サーバーに到達!)
elif reply.haslayer(TCP):
tcp_flags = reply.getlayer(TCP).flags
# SYN-ACK (0x12) または RST (0x04) が返ってきた場合
status = "[OPEN]" if "A" in str(tcp_flags) else "[CLOSED/FILTERED]"
print(f"{ttl:2d} {reply.src} {status} -> Destination reached!")
break
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python3 tcptrc.py <Target_IP> <Port>")
sys.exit(1)
target = sys.argv[1]
port = int(sys.argv[2])
custom_tcptraceroute(target, port)
このスクリプトを走らせれば、OSのネットワークスタックや既存ツールの挙動に依存せず、自分の手でパケットの往来を完全にコントロールしながらルーティングとポートの状態を同時に検査できる。インフラの深部をハックするようなこの感覚、エンジニアならワクワクするはずだ。
—
5. シニアからの現場の教訓(Tips)
最後に、実際の障害対応現場でこのテクニックを使う際の「生きた知見」をいくつか授けておこう。
1. ファイアウォールの「ステートフルインスペクション」に注意する
tcptraceroute はSYNパケットを単発で送りつけるため、厳格なステートフルFWを挟む環境では、往路のSYNに対してFWがセッションテーブルを生成し、後続のプローブ(異なるTTLのSYN)がアノマリー(異常通信)と判定されてドロップされることがある。診断を行う際は、監視アラートやセキュリティチームへの事前連絡を忘れないこと。
2. クラウド環境(AWS/GCP/Azure)における挙動の差異
パブリッククラウドのマネージドNATゲートウェイやロードバランサーは、ICMP Time Exceededの生成を抑制しているケースが多い。クラウド間のルーティング調査でホップが途中で途切れても、「パケットがロスしている」と早と抜けせず、エンドポイント自体のTCP応答([open] や RST)が返ってきているかどうかに集中しよう。
3. 代替ツールとしての nmap --traceroute
もし専用の tcptraceroute がインストールできない環境であっても、みんな大好き nmap には --traceroute オプションが備わっている。nmap -p 443 --traceroute api.example.com と叩けば、内部的にTCP SYNを用いた洗練されたパケット追跡を実行してくれる。実務ではこちらの方が手っ取り早い場面も多い。
ネットワークのトラブルシューティングは、見えないパケットの動きを脳内でどれだけ正確にレンダリングできるかの勝負だ。「pingが通らないからダメだ」という思考停止の罠から抜け出し、レイヤー4のTCPセマンティクスを武器にルートを切り裂く。この技術を身につければ、どんなに複雑なネットワーク障害が起きようとも、君は冷静に真実へとたどり着けるはずだ。
さあ、ログとパケットが君を呼んでいる。現場に戻ろうか。
コメント