パケットの墓場を見たか?ICMP tracerouteの闇と、Windowsが隠す「お節介な親切心」の正体
夜中の3時、PagerDutyの甲高いアラート音で叩き起こされる。
「決済APIの応答速度が急激に悪化、一部でタイムアウト発生」
こういう修羅場で、まず何をする? もちろんダッシュボードを確認しつつ、手元の端末から ping や traceroute を叩くはずだ。パケットがどのルーターで足止めを食らっているのか、その「ボトルネックの急所」を暴き出すために。
しかし、ここでエンジニアがよくハマる罠がある。
「あれ? Macの traceroute だと途中のルーターのIPが見えるのに、Windowsの tracert だと星印 (*) ばかりで何も見えないぞ? ファイアウォールにブロックされてるのか?」
……ちょっと待て。本当にファイアウォールのせいか?
実はこれ、OSごとの「tracerouteの実装の差」を知っていれば、一発で見抜ける初歩的な違いなのだ。今回は、数々の修羅場を潜り抜けてきたNOCのシニアエンジニアである私から、ICMPベースのtracerouteが持つ知られざる挙動の差分と、現場で使える実践的なデバッグ手法を伝授しよう。
—
標準仕様(RFC)と実装の乖離:なぜOSで挙動が違うのか?
ネットワークの教科書(RFC 792 / RFC 1393など)を開けば、tracerouteの仕組みは美しい理論で書かれている。
ネットワーク層のパケットには TTL (Time To Live) あるいは Hop Limit という寿命カウンタが存在する。ルーターはこの値を経由するたびにデクリメントし、0になった瞬間にパケットを破棄、そして送信元へ ICMP Time Exceeded(タイプ11)という「お悔やみメッセージ」を送り返す仕様だ。これを利用して、TTLを1、2、3……とインクリメントしながらパケットを送り、経路上のルーターをあぶり出す。
ここで問題になるのが、「宛先までにどんなプロペトルのパケットを投げるか」という実装の選択だ。
1. UNIX / Linux / macOS の伝統(UDP方式)
多くのUNIX系OSやその系譜を引くツールは、「存在しない高番号ポート宛てのUDPパケット」を投げる。
- TTLを1ずつ増やしながら、UDPパケットを宛先ホストの適当なポート(通常は33434番以降)へ送信。
- 経由ルーターは
ICMP Time Exceededを返す。 - 最終的に宛先ホストに到達すると、宛先側は「そんなポート開いてねぇよ!」ということで、
ICMP Destination Unreachable(ポート到達不能:タイプ3、コード3)を返す。これで traceroute は「ゴールに到達した」と判断する。
2. Windowsの流儀(ICMP Echo Request方式)
一方で、Windowsの標準コマンドである tracert は、古き良きICMP Echo Request(Pingのパケット)をそのまま使う。
- TTLを増やしながら、中身はただの「Ping」を飛ばし続ける。
- 経由ルーターの挙動は同じで、
ICMP Time Exceededを返す。 - 最終的に宛先ホストに到達すると、今度はターゲット自身が
ICMP Echo Reply(Pingの応答)を返す。
この「UDPを使うか、ICMPを使うか」という違いが、現場でのトラブルシューティングにおいて運命の分かれ道になるのだ。
—
通信フロー(シーケンス)の比較:パケットの往来を追う
頭の中でパケットの旅をイメージできるように、それぞれのシーケンスを整理しておこう。
UNIX系 traceroute(UDPベース)のフロー
[Client] ---> UDP (port 33434, TTL=1) ---> [Router A] (TTL=0になり破棄)
[Client] <--- ICMP Time Exceeded <---------- [Router A]
[Client] ---> UDP (port 33435, TTL=2) ---> [Router A] ---> [Router B] (TTL=0になり破棄)
[Client] <--- ICMP Time Exceeded -------------------------- [Router B]
...
[Client] ---> UDP (port 33443, TTL=N) ---> [Target Host] (ポート閉塞)
[Client] <--- ICMP Port Unreachable <----------------------- [Target Host]
Windows tracert(ICMPベース)のフロー
[Client] ---> ICMP Echo Request (TTL=1) ---> [Router A] (TTL=0になり破棄)
[Client] <--- ICMP Time Exceeded <----------- [Router A]
[Client] ---> ICMP Echo Request (TTL=2) ---> [Router A] ---> [Router B] (TTL=0になり破棄)
[Client] <--- ICMP Time Exceeded --------------------------- [Router B]
...
[Client] ---> ICMP Echo Request (TTL=N) ---> [Target Host]
[Client] <--- ICMP Echo Reply (ゴール到達) ------------------- [Target Host]
—
なぜWindows版(ICMP)はファイアウォールに嫌われるのか?
現場でよくあるのが、「Linuxからなら traceroute が通るのに、Windowsの tracert だと途中から星印(タイムアウト)だらけになる」という現象だ。
理由は明快で、多くの企業の境界ファイアウォールやクラウドのセキュリティグループ(AWSのSecurity GroupやGCPの防火壁など)は、セキュリティ上の理由からICMP(特に外からのEcho Requestや、それに付随するトラフィック)を厳しく制限・ドロップしているからだ。
- UDP方式(UNIX系): ファイアウォールが「ICMPは通さないが、上位のステートフルインスペクションでUDPの戻りや高番号ポートへの通信をうまく処理する」場合、あるいは経由ルーターが単に
Time Exceededを律儀に返すため、経路が見えやすい。 - ICMP方式(Windows): そもそもファイアウォール自体が
ICMP Echoを嫌ってパケットを握りつぶすため、Windowsのtracertは途中で沈黙してしまうことが多い。
—
実務で使える!環境に応じたデバッグとコード検証
ここからは、インフラエンジニアやWeb API開発者が日々の運用で使える、実践的なTipsとコードスニペットを紹介しよう。
1. Linux環境でICMPベースのtracerouteを強制する
Linuxの標準 traceroute コマンドはデフォルトでUDPだが、-I オプションを付与することで、Windowsと同じ ICMP Echo(ICMP traceroute) に切り替えることができる。
ファイアウォールがUDPとICMPでどう反応が変わるかをテストする際に、非常に強力な武器となる。
# デフォルト(UDP方式)でパスを調査
traceroute api.example.com
# あえてWindowsと同じ「ICMP Echo方式」に切り替えてテストする
traceroute -I api.example.com
2. Pythonを用いたネットワーク死活・経路診断スクリプト
実務では、社内監視システムやAPIの死活・レイテンシ監視ツールを自作することも多い。Pythonの scapy ライブラリなどを使えば、カスタムICMPパケットを使った高度な診断スクリプトをサクッと書くことができる。
以下は、標準ライブラリではなく、実務の現場でネットワークの低レイヤーを叩く際のイメージを持ってもらうためのPython(擬似コード的アプローチ)の例だ。
import subprocess
import platform
import sys
def run_diagnostic_traceroute(target_host):
"""
OSの差異を考慮しつつ、適切なtracerouteコマンドを実行するラッパー関数
"""
current_os = platform.system()
print(f"[*] ターゲット [{target_host}] に対する経路診断を開始します(OS: {current_os})")
if current_os == "Windows":
# Windowsの場合は標準のtracertを実行(ICMPベース)
command = ["tracert", "-d", target_host]
else:
# Linux/macOSの場合はICMPモード(-I)を指定して実行
command = ["traceroute", "-I", "-n", target_host]
try:
# サブプロセスとしてコマンドを実行し、リアルタイムに出力を取得
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
# ログ出力やモニタリング基盤への転送をここにフックできる
print(f"DIAG: {output.strip()}")
rc = process.poll()
if rc == 0:
print("[+] 経路診断が正常に完了しました。")
else:
[-] print(f"[-] 診断コマンドが異常終了しました(終了コード: {rc})")
except Exception as e:
print(f"[!] 予期せぬエラーが発生しました: {e}", file=sys.stderr)
if __name__ == "__main__":
# テスト対象のAPIエンドポイントやゲートウェイを指定
target = "8.8.8.8"
run_diagnostic_traceroute(target)
3. クラウド環境(AWS / GCP)でのインフラ設定の注意点
もしあなたがクラウド上にAPIサーバーを構築しているなら、セキュリティグループやNACL(ネットワークアクセスコントロールリスト)の設定には細心の注意を払ってほしい。
「セキュリティを厳しくするために全てのICMPを拒否する」という設定をやりすぎると、クライアント側からの tracert が一切通らなくなり、お客様から「接続断の原因切り分けができない」というクレームの温床になる。
最低限、以下のICMPタイプはプライベートサブネットやパブリックALBの前面で許可、あるいは適切にコントロールすべきかをアーキテクトと議論しておこう。
- Type 3 (Destination Unreachable): パケットフィルタリングやMTUパス発見(Path MTU Discovery)に不可欠。これを完全に塞ぐと、巨大なペイロードを持つAPIリクエストが途中で謎のタイムアウトを起こす「黒い穴(Black Hole)」現象を引き起こす。
- Type 11 (Time Exceeded): 今回のテーマであるtracerouteの命綱。セキュリティ要件で隠蔽したい場合もあるが、トラブルシューティングの難易度が跳ね上がるトレードオフを理解しておくこと。
—
シニアエンジニアからの教訓
ネットワークのトラブルシューティングにおいて、「ツールが見せているデータが、本当に真実の姿とは限らない」という疑う視点を持つことが何よりも大切だ。
「Windowsのtracertで星印が出るから、ネットワークが死んでいる」と早合点してネットワークチームを呼び出し、蓋を開けてみたら単に社内FWがICMPを弾いていただけで、実際のAPI通信(TCP/UDP)は健全に流れていた……なんて笑えない話は、現場で毎日のように起きている。
OSごとの実装の違い、プロトコルの仕様、そして経由する機器がどのようなポリシーでパケットを処理しているのか。その裏側のストーリーをパケットの挙動から脳内に描き出せるようになってこそ、真のネットワーク・インフラエンジニアと言える。
さあ、冷えたコーヒーでも飲んで、次のトラブルシューティングに向かうとしよう。パケットはいつだって、嘘をつかないのだから。
コメント