【実務・中級編】 TCPベースのtraceroute(Paris tracerouteなど)によるロードバランス配慮 – トラブルシューティング&ネットワーク運用監視実践ガイド

夜中の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でハッシュ計算されて目的地にたどり着くのか。その一連の挙動を頭の中でイメージできるようになれば、どんな難解な障害も怖くありません。さあ、次のアラートが鳴る前に、手元の検証環境でパケットを飛ばしてみましょう。

コメント

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