夜中のNOCで私が絶望した「消えたルータ」の話
あれは真夜中の3時、グローバル展開するWeb API基盤で突如として発生した「一部の特定リージョンからのレイテンシ急増」アラートでした。私は当時、NOCの最前線でキーボードを叩きまくっていました。
「おい、バックボーンのどこかでパケットがブラックホールに吸い込まれているぞ。tracerouteを叩け!」
若いエンジニアが焦った声で標準的な traceroute コマンドを実行しました。しかし、返ってきた画面を見て、私たちは頭を抱えました。
ホップごとに表示されるルータのIPアドレスが、まるでピンボールのように右へ左へと揺れ動き、本来たどり着くはずのない冗長パスの迷宮を描き出したのです。ある行では応答が返り、次の行ではタイムアウト、挙句の果てには存在しないループパスを延々と指し示しました。
「先輩、このルータ、壊れてます……? 経路が日替わり定食みたいに毎回変わります!」
違う。機器が壊れているのではなく、現代のハイパースケール・データセンターを支える ECMP(Equal-Cost Multi-Path) が、私たちの浅はかなパケットを翻弄していただけでした。
標準的な traceroute は、パケットを飛ばすごとにUDPやICMPのポート番号(あるいはTTL)を適当にインクリメントします。現代のロードバランサーやルータは、パケットの5タプル(送信元IP、宛先IP、プロトコル、送信元ポート、宛先ポート)をハッシュ関数にかけ、複数の等コストパスにトラフィックを分散(ロードバランス)させます。つまり、ポート番号が変わるたびに、パケットが通る物理的なルートそのものが変わってしまっていたのです。
これでは、障害調査において「どのホップでパケットがドロップしているのか」を正確に特定できるはずがありません。この泥沼から私たちを救い出してくれる技術こそが、今回解説する TCPベースのtraceroute(Paris tracerouteの概念) です。
—
なぜ標準のtracerouteはECMP環境で無力なのか?
ネットワーク運用において、レイヤー3(IP層)のルーティングだけでなく、レイヤー4(トランスポート層)の挙動を理解することは、シニアエンジニアとしての必須スキルです。
現代のデータセンターやクラウドのコアネットワークは、単一の太い回線ではなく、何本もの平行な物理回線を束ねたECMPで構成されています。これにより帯域幅の拡張と耐障害性を担保していますが、これがトラブルシューティング時には牙を剥きます。
フローハッシュの罠
ネットワーク機器(L3スイッチやルータ)は、パケットをルーティングする際、その都度全ヘッダーを精査して重い処理をするわけではありません。パケットが最初に流入した際、ハッシュアルゴリズム(一般にはCRCやシスコの独自ハッシュなど)を用いてフローを識別し、そのフローに属するパケットを常に同じ物理インターフェースへ割り当てます。
標準の traceroute(例えばUDP版)の通信フローを想像してください。
1. TTL=1 で送信。宛先ポートは 33434。ハッシュ値 H1 が計算され、Path Aを通る。
2. TTL=2 で送信。宛先ポートは 33435 にインクリメントされる。ハッシュ値 H2 が計算され、今度は Path Bを通る。
3. TTL=3 で送信。宛先ポートは 33436。ハッシュ値 H3 が計算され、Path Cを通る。
これでは、「目的地までの単一の旅路」を追っているのではなく、「毎回違うルートを通る別人の旅」を継ぎはぎしているようなものです。これでは正確な遅延測定も、パケットロスの真犯人の特定もできません。
—
Paris tracerouteの原理:同一フローハッシュの維持
この問題を鮮やかに解決したのが、ルー・マッセン(Paris)らの研究チームが提唱した「Paris traceroute」の概念です。
そのアプローチは極めてシンプルかつ堅牢です。
「TTLがいくつであっても、ルータがハッシュ計算に使うL4のキー(送信元ポート・宛先ポートなど)を完全に固定し、常に同一のフローハッシュ(Identical Flow Hash)を維持する」
これにより、パケットはまるで同じ列車に乗っているかのように、完全に同一の物理パス(ECMPの特定レーン)を駆け抜けます。障害調査において「狙ったパスのどこが壊れているか」をピンポイントで射抜くことが可能になるのです。
—
実践:TCPベースのtracerouteによるデバッグ手法
実務の現場では、ICMPやUDPのtracerouteはファイアウォール(FW)やセキュリティグループによって容赦なくドロップされることが多々あります。しかし、Web APIやアプリケーション通信で使われる TCP(特にポート 80や 443) であれば、通常のトラフィックに偽装できるため、FWの検査をすり抜けやすくなります。
ここでは、Linux環境で広く使われている現代的なネットワーク診断ツール tcptraceroute や、Pythonを用いたカスタム診断スクリプトの活用法を解説します。
1. ツールによる実践(tcptraceroute)
実務で最も手っ取り早いのは、L4のTCPパケットのポートを固定してトレーシングを行うツールを使うことです。
# 宛先 APIサーバー (api.example.com) の HTTPSポート (443) に向けて
# TCP SYNパケットを用いたtracerouteを実行する
# -n: DNS逆引きを無効化してレスポンスを高速化
# -p 443: ポート番号を443に固定し、ECMPのフローハッシュを維持する
sudo tcptraceroute -n -p 443 api.example.com
【実務Tips】
このコマンドを叩いた際、途中のルータから ICMP Time Exceeded が返ってくるホップと、全く応答しない(Asterisk * が並ぶ)ホップが存在します。インフラエンジニアとして知っておくべきは、「ルータのコントロールプレーン(CPU)はルーティング処理で忙しいため、TTL切れのICMP生成をドロップ・レートリミットすることがよくある」という事実です。
応答がない=即ち障害、とは限らない点に注意してください。肝心なのは「最終宛先(ポート443)までTCPの SYN-ACK あるいは RST が返ってくるか」というエンドツーエンドの導通です。
2. Pythonを用いたカスタムTCP Tracerouteの 구현(概念コード)
インフラの自動化や、自社製監視SaaSのプローブ(死活・経路監視エージェント)を開発する際、OS標準のコマンドでは物足りないことがあります。Pythonの scapy ライブラリを用いると、L4のポートを完全に制御した洗練されたパリスタイルのトレーサーouteを自作できます。
以下のコードは、TCPポートを固定してTTLをインクリメントしながら、同一フローハッシュを維持してパケットを送り出す仕組みの骨子です。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from scapy.all import IP, TCP, sr1, conf
import sys
def tcp_paris_traceroute(target_ip, target_port=443, max_ttl=30):
"""
TCPポートを固定し、同一フローハッシュを維持しながらECMP環境下の経路を正確に追跡するスクリプト
"""
print(f"[*] Tracing route to {target_ip} on TCP port {target_port} (ECMP Fixed Flow)")
# 応答を高速に受け取るため、L3のレイヤー設定を調整
conf.verb = 0
for ttl in range(1, max_ttl + 1):
# L4のポート情報(送信元・宛先)を固定することで、
# ルータのECMPハッシュ値がTTLの変化に関わらず一定に保たれるようにする
ip_packet = IP(dst=target_ip, ttl=ttl)
tcp_packet = TCP(sport=55555, dport=target_port, flags="S", seq=1000)
packet = ip_packet / tcp_packet
start_time = time_ns() if 'time_ns' in globals() else 0 # 簡易計測用
reply = sr1(packet, timeout=2.0)
if reply is None:
print(f"{ttl:2d} * * * Request timed out.")
elif reply.haslayer(TCP):
# 宛先サーバに到達した場合(SYN-ACKまたはRSTが返ってきた)
print(f"{ttl:2d} {reply.src} [Target Reached]")
break
elif reply.haslayer(ICMP):
icmp_type = reply.getlayer(ICMP).type
if icmp_type == 11: # Time Exceeded (TTL構造体切れ)
print(f"{ttl:2d} {reply.src} (TTL expired in transit)")
else:
print(f"{ttl:2d} {reply.src} [ICMP Type: {icmp_type}]")
else:
print(f"{ttl:2d} {reply.src} [Unknown response]")
if __name__ == "__main__":
import time
# 実行例: python3 paris_trace.py 192.0.2.1
if len(sys.argv) > 1:
target = sys.argv[1]
tcp_paris_traceroute(target)
else:
print("Usage: python3 script.py <Target_IP>")
このように、パケットの「血統書」とも言えるL4ヘッダーを厳格にコントロールすることで、ネットワークの迷宮を正確にマッピングできるようになります。
—
シニアエンジニアからの現場の教訓
障害対応の現場において、ツールが吐き出す結果をうのみにするのは最も危険です。特にECMP環境が絡むモダンなクラウドネイティブ・アーキテクチャでは以下の鉄則を忘れないでください。
1. 「ブレる経路」を疑え
もし通常の traceroute でホップごとのIPがガチャガチャ変わるなら、それはパケットロスの原因ではなく、単にECMPによって別のレーンを走っているだけです。慌てず騒がず、TCPポート固定型の診断に切り替えましょう。
2. セキュリティデバイスの存在を念頭に置く
クラウドのVPC(AWSのNAT GatewayやGCPのCloud VPNなど)や次世代ファイアウォール(NGFW)は、TTL切れのICMPパケットに対して意図的にレートリミットをかけたり、応答を抑制したりします。「途中で途切れている=即ち障害」と早計に判断せず、エンドポイント間の双方向の疎通やアプリケーション層のメトリクス(HTTP 5xxエラー率やTCP再送率)と必ずクロスチェックしてください。
ネットワークは生き物です。パケットが物理ファイバーの光の海をどのように泳ぎ、どのルータのASICでハッシュ計算されて目的地にたどり着くのか。その一連の挙動を頭の中でイメージできるようになれば、どんな難解な障害も怖くありません。さあ、次のアラートが鳴る前に、手元の検証環境でパケットを飛ばしてみましょう。
コメント