【実務・中級編】 UDPベースのtracerouteにおける高位ポート番号の動的割り当て – トラブルシューティング&ネットワーク運用監視実践ガイド

夜中の3時、データセンターの監視モニターに真っ赤なアラートが点滅する。海外リージョンとの間で数パケットのロスが発生し、APIのレイテンシが跳ね上がっている。こんな時、君なら最初にどのコマンドを叩く?

「とりあえず ping だろ」と思ったそこの君、甘い。現代の堅牢なクラウドインフラや厳重にファイアウォール(FW)で固められたエンタープライズネットワークでは、ICMP(Ping)なんてものはセキュリティ上の理由やQoSの帯域制限で綺麗にドロップされるか、レートリミットをかけられて使い物にならないことがザラにある。

そこで真価を発揮するのが traceroute だ。パケットが目的地のルーターを渡り歩く軌跡をあぶり出す、ネットワークエンジニアの「聴診器」である。しかし、この traceroute が裏側でどんな挙動をしているか、パケットの気持ちになって考えたことはあるだろうか?

今回は、UNIX系OSの traceroute がデフォルトで採用している「UDPベースの高位ポート番号の動的割り当て」という、実務で知っておくべき極上のネットワーク仕様について、現場の泥臭い知見を交えて徹底的に解説しよう。

—

1. なぜUDP?なぜ「33434番以降」なのか?

traceroute の仕組みの基本を思い出してほしい。パケットのIPヘッダーにあるTTL(Time To Live:生存時間)を 1、2、3……とインクリメントしながら送信し、途中のルーターがTTL切れを検知した際に送り返してくる ICMP Time Exceeded (TTL exceeded in-transit)のメッセージをキャッチして経路を特定する。

Windowsの tracert はデフォルトでICMP Echo Request(Ping)を使うが、LinuxやmacOSなどのUNIX系OSの標準 traceroute は、UDPパケットを使用する。ここで一つの疑問が湧くはずだ。

「なぜ、わざわざUDPを使うのか?」

答えはシンプルで、「宛先ホストの未知のポートに対して適当なUDPパケットを送りつけることで、最終的に宛先到達時に確実にエラー(Port Unreachable)を返させるため」だ。

TCPだと3ウェイハンドシェイクが必要になるし、ICMPだとOSによっては応答を無視されることが多い。しかし、UDPであれば、存在しない高位ポートにパケットを投げつければ、RFC 792の規定により、宛先OSのネットワークスタックは必ず ICMP Destination Unreachable (Port Unreachable) を返してこざるを得ない。この「怒りのエラー返信」をトラップすることで、 traceroute は「あ、ここに宛先がいるな」と認識するのだ。

そして、この目的のためにUNIX系OSの traceroute がデフォルトで使用するのが、33434番ポートである。

標準的なポートの範囲と仕様

  • 初期ポート番号: 通常 33434 からスタートする。
  • ポートの動的インクリメント: 送信するパケットごとに、あるいはホップごとにポート番号は 33434、33435、33436……とインクリメントされていく。

なぜこの中途半端な番号なのか?
これは、Webで使われる 80 (HTTP) や 443 (HTTPS)、SSHの 22 などの「よく使われるウェルノウンポート(Well-known ports)」や、ミドルウェアが一時的に使う動的ポート(Ephemeral ports)と競合しない、かつ覚えやすい(あるいは偶然被らない)領域として、Van Jacobson氏がオリジナルの traceroute を実装した際に割り当てた歴史的経緯(デファクトスタンダード)に基づく。

—

2. パケットのライフサイクル:通信フローの裏側

実際に traceroute 8.8.8.8 を叩いたとき、ネットワーク上では一体何が起きているのか。そのシーケンスを覗いてみよう。

Client (自分)                Router A (ホップ1)          Google DNS (8.8.8.8)
    |                             |                           |
    |-- UDP (Dst:33434, TTL=1) -->|                           |
    |                             |                           |
    |<-- ICMP Time Exceeded ------|                           | (TTL切れでRouter Aが激怒)
    |                             |                           |
    |-- UDP (Dst:33435, TTL=2) ------------------------------>|
    |                             |                           |
    |                             |                           | (ポート閉じてるので返す)
    |<-- ICMP Port Unreachable -------------------------------| (ゴールの合図!)

1. 第1波(TTL=1, Port:33434):
送信元からTTL=1のUDPパケットが発射される。直近のルーター(Router A)に届いた瞬間、ルーターはTTLを1減算し「0」になったためパケットを破棄。そして送信元へ ICMP Time Exceeded を送り返す。これで1ホップ目が判明。
2. 第2波(TTL=2, Port:33435):
次はTTLを2にし、ポート番号を 33435 に進めて送信。今度はRouter Aを通過し、次のルーターでTTL切れを起こして同様に位置が特定される。
3. 最終波(TTL=N, Port:33434 + N – 1):
ついに宛先の 8.8.8.8 に到達。しかし、指定された高位ポート(例: 33445 など)で待ち受けているアプリケーションなど存在しないため、宛先ホストは ICMP Port Unreachable を返す。 traceroute はこのエラーを受け取ると、「無事に目的地に届いたな」と判断してループを終了する。

—

3. 現場で直面する「罠」:ファイアウォールとポートブロック

シニアエンジニアとして幾多の障害現場を潜り抜けてきて、この仕様に起因するトラブルには何度も泣かされてきた。

現代のインフラ、特にAWSやGCPなどのパブリッククラウド環境や、企業の厳格な次世代ファイアウォール(NGFW)の背後にあるサーバーに対して標準の traceroute を実行すると、途中で星印 (* * *) が延々と並ぶ現象に遭遇するはずだ。

原因と対策

  • 原因: セキュリティポリシーにより、外からの 33434 番以降のUDPパケットや、それに対するICMP応答(特に Destination Unreachable や Time Exceeded)がファイアウォールで厳しくフィルタリング(ドロップ)されている。
  • 実務での回避策:

UDPがダメならTCPやICMPを使えばいい。多くのインフラエンジニアは、パケットロス調査や経路調査を行う際、次のようなオプションを使い分ける。

# TCPのSYNパケットを使ってtracerouteを実行する(標準ポート80や443を狙う)
# これなら通常のWebトラフィックに偽装できるため、FWのルールを抜けやすい
sudo traceroute -T -p 443 api.example.com

# もしくはICMPベースでtracerouteを実行する(Windowsのtracertに近い挙動)
sudo traceroute -I api.example.com

特にWeb APIの設計やインフラの疎通確認を行う際、「APIサーバーへの経路が見えない!」と慌ててネットワーク管理者に泣きつく前に、自身のクライアントから目的のポート(例えば 443)に対してTCPベースの traceroute が打てるか試してみるのがプロの作法だ。

—

4. 実務で役立つ!コードと設定のプラクティス

ネットワーク診断だけでなく、アプリケーション層(Web API)のインフラ設計や、コンテナ環境でのデバッグにおいて、この「ポートの動的割り当て」の概念は非常に重要になる。ここでは、実務で役立つTipsとコードスニペットを紹介しよう。

A. Pythonを用いた簡易UDPポート疎通&診断スクリプト

自社サービスのバックエンドから、特定の死活監視用UDPサーバーに対してパケットを投げ、タイムアウトや応答をハンドリングする際の基本形だ。

import socket
import sys

def check_udp_port(target_ip, target_port, timeout=3.0):
    """
    指定されたIPとUDPポートに対してソケットを作成し、
    パケット送信とICMPエラーの検知(タイムアウト判定)を行う実用的なスニペット
    """
    # IPv4 (AF_INET) と UDP (SOCK_DGRAM) を指定してソケットを生成
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.settimeout(timeout)

    try:
        print(f"[*] 宛先 {target_ip}:{target_port} へUDPプローブを送信中...")
        
        # ダミーのペイロードを送信
        # 実際のtracerouteはこの際、IPヘッダーのTTLをソケットオプションで操作している
        sock.sendto(b"X" * 32, (target_ip, target_port))

        # 応答を待機 (UDPはコネレスだが、Port Unreachableが返るとsocket.errorになることがある)
        data, addr = sock.recvfrom(1024)
        print(f"[+] 応答を受信しました: {addr} から {len(data)} バイト")
        
    except socket.timeout:
        print("[-] タイムアウトしました。パケットが破棄されたか、ファイアウォールにブロックされています。")
    except ConnectionRefusedError:
        print("[+] 宛先からポート到達不能 (Port Unreachable) エラーが返されました(正常な拒否応答)。")
    except Exception as e:
        print(f"[!] 予期せぬエラーが発生しました: {e}")
    finally:
        sock.close()

if __name__ == "__main__":
    # 例: Google Public DNS に対してテスト
    check_udp_port("8.8.8.8", 33434)

B. Linuxファイアウォール (iptables / nftables) での注意点

自社でLinuxサーバーやKubernetesのノードを構築・運用している場合、パケットフィルターの設定ミスで traceroute の応答が遮断され、外部からのトラブルシューティングを困難にしているケースがある。

もしインフラ管理者として、適切な診断を許可しつつセキュリティを保ちたい場合は、以下のようにICMPのエラーメッセージ(特に Destination Unreachable や Time Exceeded)を安易にすべて落とさない配慮が必要だ。

# 【iptablesの例】ICMPの過剰なレートリミットをかけつつ、必要なエラー通知を通す設定
# すべてのICMPを遮断すると、tracerouteやMTUパス発見(Path MTU Discovery)が壊れて通信不良の原因になる
iptables -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT
iptables -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT

—

5. シニアからのメッセージ

ネットワークのトラブルシューティングにおいて、目の前の現象(「つながらない」)だけに囚われてはいけない。パケットがどのプロトコルを使い、どのポートを叩き、途中のルーターやファイアウォールでどのようなハンドリングを受けているか。その一連のライフサイクルを頭の中でスラスラと再現できるようになってこそ、一流のインフラ・バックエンドエンジニアだ。

「なぜ33434番なのか?」という小さな疑問を深掘りした先には、OSのネットワークスタックの挙動や、RFCの仕様、そしてセキュリティと可観測性(Observability)のトレードオフという、エンジニアリングの本質が詰まっている。

次にネットワークの迷宮に迷い込んだときは、ぜひ今回のUDPポートの挙動と traceroute の裏側を思い出してほしい。きっと思い描いたパケットの軌跡が、君を正解へと導いてくれるはずだ。

コメント

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