夜間帯、Slackのインシデントチャンネルが突如として騒がしくなる。「ある特定のリージョンから、APIのレイテンシーが跳ね上がっている」「一部のクライアントからパケットロスが報告されている」。
君たちも、こうした冷や汗をかくようなアラート対応を何度となく経験してきたことだろう。ダッシュボードのグラフは真っ赤に染まり、PaaSのステータスは「異常なし」。犯人はクラウド側ではなく、その手前の複雑怪奇に絡み合ったインターネットの「どこか」にある。
こんな時、君の相棒は何だ? そう、traceroute(あるいはLinuxのtracerouteコマンド、Windowsのtracert)だ。
しかし、ただ漫然と traceroute api.example.com と叩いて、ホップごとのRTT(Round Trip Time)を眺めているだけでは、百戦錬磨のインフラエンジニアとは言えない。今日のテーマは、パケットがインターネットの荒波をどう泳ぎ、往路と復路で全く違うルートを通る「非対称ルーティング(Asymmetric Routing)」という厄介な現象を、traceroute の挙動からいかに見抜くか、だ。
データセンターの深夜、冷たいサーバーファンの風を感じながら、先輩が後輩に手ほどきするつもりで、その実践的なノウハウを伝授しよう。
—
1. 非対称ルーティングとは何か? なぜトラブルの温床になるのか
まずは基礎知識のおさらいだ。インターネットは自律システム(AS: Autonomous System)の巨大なパッチワークでできている。BGP(Border Gateway Protocol)というダイナミック・ルーティングプロトコルが、世界中のルーター間で「こっちの方が宛先に早く着くぞ」と経路情報を交換し合いながら、パケットの道筋を決めている。
通常、通信は「往路」と「復路」で同じ経路を通る(対称ルーティング)ことが望ましい。しかし、マルチホーミング(複数のISPと契約している状態)や、広域イーサネット、クラウドの可用性ゾーン(AZ)をまたぐ複雑なネットワーク設計では、往路と復路で異なるパスが選ばれることが日常茶飯事だ。
非対称ルーティングが引き起こす悲劇
ルーターやファイアウォール(FW)、ロードバランサー(LB)の視点に立ってみてほしい。
モダンなセキュリティインフラやステートフル・インスペクションを行うFWは、すべてのTCPコネクションやセッションの状態をメモリ上に保持している。
もし、「往路」のパケットが FW-A を通過してサーバーに届いたのに、「復路」のパケットが負荷分散や経路変更のせいで全く別の FW-B から抜けようとしたらどうなるか?
FW-B からすれば、「おい、お前、俺のところでセッション張った覚えがないぞ?」ということになる。結果、SYNパケットやACKパケットが容赦なくドロップされ、原因不明のタイムアウトやコネクション切断を引き起こすのだ。
この「見えない断絶」を暴くための強力な武器が、traceroute とAS経路の分析である。
—
2. tracerouteの裏側:パケットはどのようにしてホップを暴くのか
traceroute は魔法の杖ではない。IPパケットのヘッダーにある TTL(Time To Live) という、文字通り「寿命」を表すフィールドの仕組みを巧妙に利用している。
1. TTL=1 のパケット発射: traceroute はまず、TTLを 1 に設定したUDPパケット(あるいはICMPエコーリクエスト、TCPパケット)を送信する。
2. 最初のルーターで寿命切れ: 宛先に向かう途中の最初のルーター(デフォルトゲートウェイなど)にパケットが着くと、ルーターはTTLを 1 減算する。結果、TTLが 0 になる。
3. ICMP Time Exceeded の返送: ルーターは「おっと、寿命が尽きたようだぜ」と、送信元(君の端末)に対して ICMP Time Exceeded (Type 11, Code 0) というエラーメッセージを返す。
4. ホップの特定: このエラーパケットの発信元IPアドレスを見ることで、1つ目のホップにいるルーターのIPが分かる。
5. TTLをインクリメント: 次はTTLを 2 にして送信し、2つ目のルーターのIPを暴く……これを宛先に届くまで繰り返す。
ここで重要なのは、「tracerouteが表示しているホップのIPは、あくまで『往路でエラーを返したルーター』のものである」という点だ。復路のパケットがどんな経路を通って手元に戻ってきたかは、標準の traceroute だけでは見えにくい。ここに大きな罠がある。
—
3. 実践!CLIによるAS経路と非対称ルーティングの暴き方
百聞は一見にしかず。実務で使えるコマンドラインのテクニックを見ていこう。
ステップ1: 単純なtracerouteからの脱却
まずは、DNSの逆引きやホスト名解決で時間をロスしないよう、-n オプション(IPアドレスのまま表示)をつけ、プロトコルをICMPやTCPに変更して実行する。
# ICMPエコーを使用したtraceroute(デフォルトはUDPやUDPポート宛てが多い)
# -n: DNS逆引きを無効化し、処理を高速化
# -I: ICMP ECHOを使用
traceroute -n -I 8.8.8.8
しかし、これだけでは「どのAS(Autonomous System)を通過しているか」が分からない。そこで登場するのが traceroute の拡張や、専門の診断ツールだ。
ステップ2: mtr(My Traceroute)によるリアルタイム監視
ネットワーク運用現場で traceroute の上位互換として愛用されているのが mtr だ。往路の各ホップにおけるパケットロス率や、RTTのゆらぎ(Jitter)をリアルタイムで視覚化してくれる。
# mtrをインストールして実行(Ubuntu/Debianの例)
sudo apt-get install mtr-tiny
mtr -n -r -c 100 api.example.com
出力結果の各ホップでパケットロス(Loss%)が急増している場所を見つける。もし、ホップ3でロスが「0%」なのに、ホップ4でいきなり「50%」になり、ホップ5以降でまた「0%」に戻るような現象が見られたら、それはルーターがコントロールプレージの保護のためにICMPの応答をレートリミットしているだけ(実通信には影響なし)の可能性が高い。
こうした「偽の障害」に惑わされないのが、ベテランエンジニアの眼力だ。
ステップ3: whoisとAS番号(ASN)の突合
パケットがどの通信事業者のネットワークを経由しているかを特定するには、IPアドレスからASN(Autonomous System Number)を引く必要がある。
# ipinfo.ioなどのAPIや、whoisコマンドを組み合わせる
whois 203.0.113.195 | grep -i "origin:"
もし、往路のパケットが国内のIX(インターネットエクスチェンジ)経由で綺麗に流れているにもかかわらず、復路のデータがなぜか北米の海底ケーブルを経由しているような非対称ルートを発見した場合、それはBGPの経路広告(ルートリークやプレフィックスのハイジャック、あるいは意図的なコスト設定ミス)が原因である可能性が極めて高い。
—
4. アプリケーション層(Python/API)からのネットワークパス診断
インフラエンジニアだけでなく、Web APIを設計・運用するバックエンドエンジニアも、ネットワークの泥臭い挙動を知っておく必要がある。例えば、特定のAPIクライアントからの接続断やレイテンシー悪化を調査する際、アプリケーション側からネットワーク経路の傾向を掴むためのスクリプト例を紹介しよう。
以下のPythonスクリプトは、ソケットのTTLを動的に変更しながら、OSのルーティングスタックを利用して簡易的なtracerouteの挙動を再現し、往路のホップ情報をJSON形式で出力するものだ。
import socket
import struct
import time
def trace_route(destination_host, max_hops=30, timeout=2.0):
"""
指定したホストへの簡易的な経路探索を行い、往路のホップごとの応答時間を計測する。
非対称ルーティングや特定のホップでの遅延をプログラムから検知するためのベースコード。
"""
try:
dest_addr = socket.gethostbyname(destination_host)
except socket.gaierror as e:
print(f"名前解決に失敗しました: {e}")
return
print(f"Tracing route to {destination_host} [{dest_addr}] with max {max_hops} hops:")
hops = []
for ttl in range(1, max_hops + 1):
# ICMPソケットを作成(管理者権限が必要な場合があります)
# IPPROTO_ICMP を使用してICMPエコーリクエストを送信
send_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
# ソケットのTTLオプションを設定
send_socket.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, struct.pack('I', ttl))
# 受信用のソケット
recv_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
recv_socket.settimeout(timeout)
# ダミーのICMPエコーリクエストパケット(Type 8, Code 0)
# チェックサム等の簡易実装
icmp_packet = b'\x08\x00\xf7\xff\x00\x01\x00\x01'
start_time = time.time()
try:
send_socket.sendto(icmp_packet, (dest_addr, 1))
# 応答を待つ
while True:
data, server = recv_socket.recvfrom(1024)
rtt = (time.time() - start_time) * 1000.0
# 宛先に到達したか、タイムアウトか、ICMP Time Exceededか判定
# server[0] には応答を返したルーターのIPが入る
hops.append({
"hop": ttl,
"ip": server[0],
"rtt_ms": round(rtt, 2)
})
break
except socket.timeout:
hops.append({
"hop": ttl,
"ip": "*",
"rtt_ms": None
})
finally:
send_socket.close()
recv_socket.close()
# 宛先に到達したら終了
if hops[-1]["ip"] == dest_addr:
print(f"到達しました: {dest_addr}")
break
return hops
if __name__ == "__main__":
# 実務での検証用(実行にはroot権限が必要な場合があります)
# results = trace_route("8.8.8.8")
# print(results)
pass
このスクリプトを定期実行し、往路のレイテンシーやホップ数の変動をDatadogやCloudWatchなどの監視基盤にメトリクスとして流し込んでおく。そうすれば、「最近、特定のISPからのAPIレスポンスが悪化しているが、それは自社サーバーの負荷ではなく、途中のキャリア網における非対称ルーティングによる迂回パスのせいでホップ数が倍増しているからだ」という高度な切り分けが、データに基づいて一瞬でできるようになる。
—
5. シニアエンジニアからの実務的アドバイス:非対称ルーティングにどう立ち向かうか
最後に、現場で非対称ルーティングに遭遇し、それが原因でトラブルを引き起こしていると判明したときの「処方箋」を授けよう。
1. パケットキャプチャ(tcpdump / Wireshark)を両端で同時に構える
片側のルーターやサーバーだけを見ていても真実は見えない。問題の通信が発生している「クライアント側(またはエッジ)」と「サーバー側」の双方で tcpdump を仕掛け、SYNパケットが来たのにSYN-ACKが逆のインターフェースから出ていっていないか、シーケンス番号のズレやドロップを徹底的に追うこと。
2. ファイアウォール・ステートテーブルの確認
ステートフルな機器が存在する場合、非対称ルーティングを許可する設定(asymmetric-routing enable や、パケットフィルタの緩和、ルートの対称性を強制するポリシーベースルーティング(PBR)の導入)を検討する。
3. BGPプレフィックスとルーティングポリシーの調整
もし意図しないキャリア経由でトラフィックが非対称になっている場合は、upstreamのISPに対してAS-Path Prepending(AS経路長の水増し)を依頼するか、自社のBGPルーティングポリシー(Local PreferenceやMED値)を見直し、往路と復路のパスを強制的に一致させる。
ネットワークは生き物だ。パケットは目に見えないが、traceroute や mtr、そしてシスコやLinuxのルーティングテーブルという「足あと」を丁寧にたどれば、必ず真実のルートが見えてくる。
障害対応の夜、焦る気持ちをグッと抑え、まずはTTLのカウントダウンに思いを馳せてみよう。君のその冷静な分析力が、止まったシステムを再び動き出させる最強のエンジンになるはずだ。
コメント