【実務・中級編】 tracerouteにおけるUDPポートバースト方式とICMP方式の挙動差異 – トラブルシューティング&ネットワーク運用監視実践ガイド

なぜ「届かない」のか?tracerouteの裏側で起きているUDPとICMPの静かな戦い

ネットワークエンジニアにとって、tracerouteは聴診器のようなものだ。しかし、多くのエンジニアは「結果が出ればいい」というスタンスで、その挙動を深く追求することはない。

深夜の障害対応中、特定のセグメントでだけtracerouteがタイムアウトし、パケットがブラックホールに吸い込まれていく光景を何度見たことか。その原因の多くは、実はパケットの「中身」にある。今日は、Unix系とWindows系でなぜ挙動が異なるのか、そしてそれが実務のファイアウォール運用にどう影響するのかを、現場の視点から解説しよう。

—

1. UDPポートバースト(Unix系) vs ICMP Echo(Windows系)

まず、この2つの方式の決定的な違いを押さえておこう。

Unix系(Linux/macOS)の流儀:UDPによる「追い出し」

Unix系は、宛先ポート番号を 33434 からインクリメントしながら、UDPパケットを送出する。

  • 仕組み: TTL(Time To Live)を 1 から順に上げながら送り、ルーターから返ってくる「ICMP Time Exceeded(タイプ11)」を受信して経路を特定する。
  • 到達時: 宛先ホストに到達すると、UDPポートが閉じていると仮定して「ICMP Port Unreachable(タイプ3・コード3)」が返ってくる。これを検知して終了する。

Windows系(tracert)の流儀:ICMPによる「対話」

Windowsの tracert は、ICMP Echo Request(タイプ8) をそのまま送る。

  • 仕組み: TTLを順次上げていく点は同じだが、返ってくるのは「ICMP Echo Reply(タイプ0)」だ。
  • 到達時: 宛先ホストから直接「ICMP Echo Reply」が返ってくれば、そこで探索完了となる。

—

2. ファイアウォール透過性という「現場の壁」

実務において、なぜ「Unixでは通るのにWindowsでは通らない(あるいはその逆)」が発生するのか。答えはシンプルだ。ファイアウォール(FW)やロードバランサーのポリシーが、特定のプロトコルをフィルタリングしているからだ。

多くのセキュアな環境では、以下の設定がなされていることが多い。

1. UDPポート全拒否: 「外部からのUDPは悪」という固定観念のもと、高位ポートのUDPを全て遮断している場合、Linuxの traceroute は宛先までの経路すら表示できない。
2. ICMPの制限: 攻撃者がネットワーク構成を把握するのを防ぐため、ICMP Echo Request をドロップするFWは非常に多い。この場合、Windowsの tracert は途中で完全に沈黙する。

対策:実務で使える診断コマンドの使い分け

もし traceroute が役に立たない場合、我々が取る手段は一つ。「プロトコルを変える」ことだ。Linux環境であれば、-I オプションを使ってICMPモードに切り替えるのが定石である。

# ICMPを使用してtracerouteを実行(UDPポート遮断環境での切り分けに有効)
sudo traceroute -I 8.8.8.8

# TCP SYNパケットを用いたtraceroute(Webサーバーへの疎通確認に最強の手段)
# 80番ポートが空いているなら、これが最も信頼できる
sudo traceroute -T -p 80 8.8.8.8

—

3. Web API開発者が知るべき「疎通確認」のコード例

インフラ担当者だけでなく、APIを開発するエンジニアにとっても、パケットの挙動理解は重要だ。例えば、Pythonで特定のポートが開いているかをチェックする場合、traceroute を待つよりも、直接 socket でコネクションを試行する方が確実だ。

以下は、Pythonで特定のホストの特定ポートに対して、疎通確認を行うシンプルなスニペットだ。

import socket

def check_port(host, port, timeout=3):
    """
    指定されたホストとポートにTCP接続を試みる。
    ICMPがブロックされていても、TCPが開いていれば疎通可能と判断できる。
    """
    try:
        with socket.create_connection((host, port), timeout=timeout):
            print(f"[OK] {host}:{port} は接続可能です。")
            return True
    except (socket.timeout, ConnectionRefusedError, OSError):
        print(f"[NG] {host}:{port} に接続できませんでした。")
        return False

# 実務での利用例
check_port("api.example.com", 443)

—

シニアエンジニアからの助言

最後に、現場でトラブルシュートを行う際の「心得」を伝えておく。

traceroute や ping は、あくまで「そのパケットが通ったか」を教えてくれるだけであり、「アプリケーションの通信が正常か」を保証するものではない。特にクラウド環境の Security Group や、オンプレのステートフルなFWを通過する際、traceroute は「通るのに、APIのデータは通らない」という現象(フラグメント問題やMTUの問題)によく直面する。

「ツールを過信せず、常にパケットが何者(L3/L4/L7)として振る舞っているかを想像せよ。」

これが、数々の深夜の障害を乗り越えてきた私からの教訓だ。次にネットワークの調査を行う際は、ぜひ -I や -T オプションを試してみてほしい。見えていなかった「壁」の正体が、鮮明に見えてくるはずだ。

コメント

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