【実務・中級編】 tracerouteのTTLエージングを利用したホップバイホップ経路特定 – トラブルシューティング&ネットワーク運用監視実践ガイド

夜中の3時、データセンターの監視モニターに真っ赤なアラートが点灯した。
「東京リージョンから大阪DRサイトへのバックアップトラフィックがロスしている。APIのレスポンスタイムが異常値だ」

こんな時、君ならどうする?
いきなりクラウドのコンソールや、ルータの設定ファイルを眺めて途方に暮れるかい? それとも、手元の端末から無言で ping を叩くだけで満足するだろうか。
ダメだ、それではただの「祈祷師」と変わらない。パケットがどこで息絶えているのか、その足跡を正確に追う。ネットワークエンジニアにとって、それは医師の聴診器のようなものだ。

今回は、数々の修羅場を潜り抜けてきたNOC(ネットワークオペレーションセンター)の現場から、誰もが日常的に使っているのに、その裏側の挙動を意外と見落としがちなしのぎの技術――tracerouteの真髄について話をしよう。

—

1. 現場のシニアが教える、なぜ ping だけでは生き残れないのか

インフラ運用やWeb APIの設計・デバッグをしていると、「特定のマイクロサービス間だけ通信がタイムアウトする」「CDNのキャッシュノードからオリジンへのフォールバックが失敗する」といった不可解な現象に直面する。

ここで ping を打つと、こう返ってくる。
Request timed out. または 100% packet loss。

「おっと、落ちてるな」で終わらせては、シニアエンジニアの看板に泥を塗る。
そのパケットは、どのプロバイダのどのルータの、どのインターフェースで地面に叩きつけられたのか?それを特定しなければ、上流のキャリアにエスカレーションすらできない。

ここで登場するのが、パケットの「寿命」を利用したホップバイホップの経路特定、すなわち traceroute の出番だ。

—

2. 理論とシーケンス:パケットに課された「寿命(TTL)」のドラマ

traceroute の仕組みを支えているのは、IPヘッダーに含まれる TTL(Time To Live) という、切なくも美しい仕様だ。RFC 791(IPv4)および RFC 2460(IPv6では Hop Limit と呼ばれる)に規定されている。

勘違いしている人がたまにいるが、この「Live」は時間(秒)ではない。ルータを1台通過する(ホップする)ごとに、この数値が「1」ずつデクリメント(減算)されるカウンターだ。
もしTTLが 0 になったルータにパケットが到着すると、そのルータは「おや、寿命のようだね」と悲しげにそのパケットを破棄し、送信元へ向かって ICMP Time Exceeded (Type 11, Code 0) というお悔やみメッセージを送り返す。

traceroute は、この仕様をハック(逆用)している。

通信シーケンスのリアル

1. 第1波(TTL = 1)の送信
traceroute は送信元から、TTLを 1 に設定したプローブパケット(実装やOSによってUDP、ICMP Echo、あるいはTCP SYNが使われる)を飛ばす。

2. 直近のルータでの力尽き
すぐ隣のデフォルトゲートウェイ(ルータA)にパケットが届く。ルータAはTTLを1引いて 0 にし、「寿命切れだ!」とパケットをドロップ。ルータA自身のIPアドレスを送信元とする ICMP Time Exceeded をこちらに送り返す。

3. 往復時間の計測と表示
手元の端末は、そのICMPを受信するまでの時間を測定し、「ホップ1:ルータA(X.X.X.X)までの往復時間は〇ミリ秒」と画面に描画する。

4. インクリメントと再挑戦
次は TTL = 2 にしてパケットを送り出す。ルータAを無事に通過し、次のルータBで力尽きてタイムエクスレッドが返ってくる。これを目的地に到達するまで繰り返す。

[自分 (Client)] ---> (TTL=1) ---> [Router A] (ここで寿命切れ: ICMP Time Exceededを返却)
[自分 (Client)] ---> (TTL=2) ---> [Router A] ---> [Router B] (ここで寿命切れ: ICMP Time Exceededを返却)
[自分 (Client)] ---> (TTL=3) ---> [Router A] ---> [Router B] ---> [Target Server] (目的地到達: ICMP Port Unreachable等を返却)

目的地に到達すると、UDPの場合は宛先ポート番号がわざと未開放のランダムな高位ポートに設定されているため、宛先ホスト自身が ICMP Destination Unreachable (Port Unreachable) を返し、traceroute は「あ、着いたな」と察知してループを抜ける。これが一連の流れだ。

—

3. 実務で使える!環境別のコマンド・コード実装例

さて、理屈はここまでだ。実務では手を動かしてなんぼである。
インフラのネットワーク診断から、アプリケーション層での経路・接続性検証まで、現場で即座に使えるスニペットを共有しよう。

A. ネットワークエンジニアの基本:CLIによる実践的な診断

Linuxの traceroute や、Windowsの tracert はおなじみだが、デフォルトのUDPモードは途中のファイアウォール(FW)やセキュリティグループにブロックされがちだ。
そのため、実務では TCP SYNパケット を使うモード(-T オプション)を好むことが多い。Web(HTTP/HTTPS)と同じポートを叩くため、途中のフィルタリングを抜けやすいからだ。

# 【Linux】宛先 8.8.8.8 に対して、TCPポート 80(HTTP)でtracerouteを実行
# -T: TCP SYNパケットを使用
# -p: 宛先ポートを指定
# -n: DNS逆引きをスキップして高速化
sudo traceroute -T -p 80 -n 8.8.8.8

もし、特定のホップで星印 (* * *) が並び続ける場合は、そのルータやFWが ICMP Time Exceeded の返送をレートリミットしているか、セキュリティポリシーで完全に捨てている証拠だ。焦る必要はない。「あぁ、ここはセキュリティが硬いんだな」と読み替えるのがプロの眼力だ。

—

B. Pythonを活用したカスタムネットワーク診断スクリプト

「APIが特定のロードバランサ配下でどのようなルーティングを通っているか、定期的にモニタリングしたい」
そんな要望には、Scapyなどのパケット操作ライブラリ、あるいは標準ライブラリを組み合わせたPythonスクリプトが役立つ。以下は、概念的なTCP tracerouteの挙動をシンプルに理解するための参考コードだ(※ソケットの低水準操作を含むため実行には適切な権限が必要)。

import socket
import sys
import time

def simple_tcp_traceroute(dest_ip, dest_port=80, max_hops=30):
    """
    指定されたIPとポートに対してホップバイホップでTCP接続を試み、
    ルーティングの経跡を簡易的に追うスクリプト
    """
    print(f"Traceroute to {dest_ip} via TCP port {dest_port}, max hops: {max_hops}")

    for ttl in range(1, max_hops + 1):
        # 1. 受信用ソケット(ICMPを受け取るため)
        recv_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
        recv_socket.settimeout(2.0)

        # 2. 送信用ソケット(TTLを設定するTCP用)
        send_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # IP_TTLソケットオプションで明示的にTTLを制御
        send_socket.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl)

        start_time = time.time()
        try:
            # 接続試行(非ブロックまたは短タイムアウト推奨だが簡易的にconnectを使用)
            send_socket.settimeout(2.0)
            send_socket.connect((dest_ip, dest_port))
            # 運良く(あるいは通常設計で)宛先に直接届いた場合
            rtt = (time.time() - start_time) * 1000
            print(f"{ttl:2d}  {dest_ip}  {rtt:.2f} ms (Reached Destination)")
            break
        except socket.timeout:
            # タイムアウトした場合、道中のルータからICMPが返ってきているか確認する
            try:
                data, addr = recv_socket.recvfrom(512)
                rtt = (time.time() - start_time) * 1000
                print(f"{ttl:2d}  {addr[0]}  {rtt:.2f} ms")
            except socket.timeout:
                print(f"{ttl:2d}  * * * (Request timed out)")
        finally:
            send_socket.close()
            recv_socket.close()

if __name__ == "__main__":
    # テストとしてGoogleのパブリックDNSなどを指定
    target = "8.8.8.8"
    simple_tcp_traceroute(target, dest_port=53)

—

C. Webアプリケーション・API設計者向けの視点:ネットワーク異常の切り分け

Web APIを設計する際、「クライアントから自社APIサーバーまでの経路」をサーバーサイドから知ることはできない(クライアントのローカル環境から見える景色と、クラウド上のサーバーから見える景色は全く違うからだ)。

しかし、ユーザーからの「APIが繋がらない」という問い合わせに対して、クライアントサイドで次のような診断コマンドを叩いてもらう案内を用意しておくと、サポートの質が劇的に変わる。

# Windows環境でのトラブルシューティング案内
# -h 30: 最大ホップ数
# -w 2000: タイムアウト時間(ミリ秒)
tracert -h 30 api.your-awesome-service.com

もし、自社のマネージド・ロードバランサ(ALB等)の手前にあるCDNのエッジまでは綺麗にパケットが届いているのに、その先のオリジン向けプライベートネットワークでパケットが迷子になっている場合、traceroute の結果の「どこでレイテンシが跳ね上がっているか」「どこから先が星印になっているか」が、インフラチームへの強力なエスカレーション資料になるのだ。

—

4. シニアからの現場の教訓(Tips)

最後に、長年の運用経験から得た教訓をいくつか授けよう。

1. 「片方向(非対称)ルーティング」の罠を忘れるな
インターネットは往路と復路で全く違う道を通ることが珍しくない(非対称ルーティング)。traceroute が示すのはあくまで「往路」の足跡だ。「往きはよいよい、帰りは怖い」という状態に気づくには、宛先側からも同様の調査を行う必要がある。
2. ICMPのレートリミットに惑わされるな
近年のルータやクラウドの仮想ルータ(VPCルータ等)は、セキュリティとCPU負荷軽減のため、ICMP Time Exceeded の送信を意図的に間引いている。途中のホップがすべて * になっていても、最終的な宛先までパケットが通っているなら、それは単なるルータの仕様であることが多い。パニックを起こさず、総合的に判断しよう。

障害対応の現場において、パケットの挙動を頭の中で立体的にイメージできるかどうかは、エンジニアとしての生存率を大きく左右する。
画面の向こう側で、パケットがどんな険しいルータの山を越え、あるいは冷酷なファイアウォールの壁に阻まれているのか。そのドラマに思いを馳せながら、今日もクールにキーボードを叩こう。

コメント

タイトルとURLをコピーしました