深夜3時、データセンターの冷気が肌を刺すNOCルーム。監視モニターの隅で点滅するアラートの赤色を横目に、私は手元のマグカップのコーヒーを一口啜る。インフラエンジニアなら誰もが一度は直面する「なぜか特定区間の疎通が取れない」という悪夢。そんな時、私たちの手元には幾つもの診断ツールがある。LinuxやCisco IOSの世界ではお馴染みの traceroute、そしてWindows環境で何気なく叩かれる tracert。
一見すると、これらは「宛先までの経路を特定する便利なツール」という同じゴールを目指しているように見える。しかし、パケットキャプチャを開き、ワイヤー上の挙動を隅々まで観察したことがある者なら知っているはずだ。Windowsの tracert が奏でるパケットの旋律は、標準的なUNIX系OSのそれとはまったく異なる、独特のトーンを持っているということを。
今回は、このWindows固有のICMPベースの traceroute 実装に深く焦点を当て、パケットレベルの内部挙動、ファイアウォールやステートフル・インスペクションをいかにすり抜ける(あるいは阻まれる)のか、そしてインフラアーキテクトやセキュリティ専門家が知るべき実践的な知見を紐解いていこう。
—
1. パケットレベルの解剖学:UDP/TCP vs ICMP Echo
標準的なUNIX/Linuxの traceroute(現代のIP実装)のデフォルト挙動を思い出してほしい。彼らは通常、宛先ポート番号が確実に閉じているであろう高いポート範囲(33434から始まることが多い)に向かって、TTL(Time To Live)を1からインクリメントしながらUDPデータグラムを送信する。中継ルーターがTTL切れを検知すると、ソースIPアドレス宛に ICMP Time Exceeded (Type 11, Code 0) を送り返し、最終的な宛先に到達すると ICMP Port Unreachable (Type 3, Code 3) が返ることで、経路上のホップを特定する仕組みだ。
これに対し、Windowsの tracert はまったく異なるアプローチをとる。彼らがワイヤー上に放つのは、UDPでもTCP SYNでもなく、ICMP Echo Request (Type 8, Code 0) そのものなのだ。
[Windows Client] -- (ICMP Echo / TTL=1) --> [Router A]
[Windows Client] <-- (ICMP Time Exceeded) -- [Router A]
[Windows Client] -- (ICMP Echo / TTL=2) --> [Router A] --> [Router B]
[Windows Client] <-- (ICMP Time Exceeded) -- [Router B]
...
[Windows Client] -- (ICMP Echo / TTL=N) --> [Destination Host]
[Windows Client] <-- (ICMP Echo Reply) ----- [Destination Host]
この仕様の違いは、単なる「プロトコルの好み」ではない。ネットワークの設計、特にセキュリティ境界(ファイアウォールやロードバランサー)を通過する際の振る舞いに決定的な差異を生み出す。
—
2. ファイアウォール通過性のジレンマとステートフル・インスペクション
現代の堅牢なセキュリティアーキテクチャにおいて、ファイアウォールや次世代FW(NGFW)は、不審なトラフィックや不要なプロトコルの流入を防ぐために厳格なポリシーが適用されている。ここで、UDPベースの traceroute とWindowsのICMPベース tracert の運命が分かれる。
UDPベース traceroute の場合
多くの企業ネットワークやクラウドのセキュリティグループ(AWS Security Groupなど)では、高番号UDPポートへのインバウンドトラフィックは「未知のトラフィック」としてデフォルトでドロップされる。そのため、宛先の手前まではルーターがTTL切れのICMPを返してくれても、最後の宛先ホスト自体がポートを閉じていようが、セキュリティアプライアンスがパケットを闇に葬り、結果として最終ホップが * * * とタイムアウトになってしまうことが頻発する。
Windows tracert(ICMP Echo)の場合
一方で、Windowsの tracert は ICMP Echo Request を使う。これは ping でお馴染みのトラフィックだ。多くのネットワークにおいて、死活監視(Ping)の利便性を考慮し、ICMP Echo Request(Type 8)の通過は比較的寛容に許可されていることが多い(あるいは、少なくともUDPの高番号ポートよりはブロックされにくい)。
しかし、ここにセキュリティ上の「罠」と「アーキテクチャの妙」がある。ステートフル・インスペクションを行うファイアウォールは、パケットの往来の状態をトラッキングしている。
Windowsの tracert は、TTLを1から順にインクリメントしながら、同じ宛先IPに対して連続して ICMP Echo Request を送信する。このとき、ファイアウォール側から見るとどう映るだろうか?
1. TTL=1 のパケットが通過し、直近のルーターから ICMP Time Exceeded が返る。
2. TTL=2 のパケットが通過し、次のルーターから ICMP Time Exceeded が返る。
…
N. 最終的に宛先ホストから ICMP Echo Reply (Type 0) が返る。
ステートフルFWは、セッションテーブルに「ICMPエコーのシーケンス」を記録しようとするが、TTL切れによって途中のルーターから予期せぬICMPエラーが返ってくるため、インスペクションエンジンによってはステート管理が複雑化し、パケットをドロップしたり、あるいは逆に不完全なステートを保持させられたりする原因になる。
—
3. 実践:PythonによるWindows tracert 挙動のシミュレーション
インフラエンジニアとして、仕様書を読むだけではなく、実際のソケットレベルで何が起きているのかを把握しておくことは極めて重要だ。以下のPythonスクリプトは、Windowsの tracert と同様に、生ソケット(Raw Sockets)を使用してICMP Echo RequestのTTLを操作し、経路上のホップを自前で探索するミニマムな実装例である。
> 注意: 1ソケットの作成および低水準のパケット操作には、OSの管理者特権(Linuxなら root、Windowsなら Administrator)が必要となる。
import socket
import struct
import time
import sys
def checksum(source_string):
"""
ICMPパケットのチェックサムを計算する関数
ネットワークプロトコルスタックの基本に忠実に、16bitワードの1の補数和を算出する
"""
sum_val = 0
count_to = (len(source_string) // 2) * 2
count = 0
while count < count_to:
val = source_string[count + 1] * 256 + source_string[count]
sum_val = sum_val + val
count = count + 2
if count_to < len(source_string):
sum_val = sum_val + source_string[len(source_string) - 1]
sum_val = (sum_val >> 16) + (sum_val & 0xffff)
sum_val = sum_val + (sum_val >> 16)
answer = ~sum_val & 0xffff
answer = socket.htons(answer)
return answer
def icmp_traceroute(destination_host, max_hops=30):
dest_addr = socket.gethostbyname(destination_host)
print(f"Traceroute to {destination_host} ({dest_addr}), max hops: {max_hops}")
# ICMPプロトコル用の生ソケットを作成
icmp_proto = socket.getprotobyname('icmp')
for ttl in range(1, max_hops + 1):
# 送信用と受信用の一回限りのソケットを生成
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp_proto)
# ソケットのTTL(IP_TTL)オプションを設定
sock.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, struct.pack('B', ttl))
sock.settimeout(2.0)
# ICMP Header: Type(8: Echo Request), Code(0), Checksum(0暫定), ID(1234), Sequence(ttl)
# Pythonのstructを使い、ネットワークバイトオーダー(ビッグエンディアン)で構築
packet_id = 12345
header = struct.pack('bbHHh', 8, 0, 0, packet_id, ttl)
data = struct.pack('d', time.time()) # ペイロードとしてタイムスタンプを埋め込む
# チェックサムの計算
chk_sum = checksum(header + data)
header = struct.pack('bbHHh', 8, 0, socket.htons(chk_sum), packet_id, ttl)
packet = header + data
start_time = time.time()
try:
sock.sendto(packet, (dest_addr, 1))
curr_addr = None
curr_name = None
while True:
# ICMPパケットの返信を待ち受ける
eval_time = time.time()
_, curr_addr = sock.recvfrom(512)
rtt = (time.time() - start_time) * 1000
curr_addr = curr_addr[0]
break
except socket.timeout:
print(f"{ttl:2d} * * *Request timed out.")
sock.close()
continue
except Exception as e:
print(f"{ttl:2d} Error: {e}")
sock.close()
break
finally:
sock.close()
print(f"{ttl:2d} {curr_addr} {rtt:.2f} ms")
# 宛先IPに到達したらループを抜ける
if curr_addr == dest_addr:
print("Trace complete.")
break
if __name__ == "__main__":
# 実行例: python icmp_trace.py 8.8.8.8
target = sys.argv[1] if len(sys.argv) > 1 else "8.8.8.8"
icmp_traceroute(target)
このコードを実行すると、OSのネットワークスタックがどのようにIPヘッダーの TTL フィールドを操作し、ICMPエコーリクエストを組み立てているのかが手に取るようにわかるはずだ。
—
4. エンジニアが知るべきトラブルシューティングの勘所
現場でネットワークの不具合に直面したとき、Linuxの traceroute とWindowsの tracert の挙動の違いを理解していないと、根本原因を誤認する危険性がある。
1. 非対称ルーティング(Asymmetric Routing)の罠
パケットが往路と復路で異なるパスを通る現代の大規模クラウドネットワークにおいて、ICMPベースの tracert は往路の各ルーターからの ICMP Time Exceeded を期待する。しかし、セキュリティアプライアンスやロードバランサーがICMPトラフィックに対して厳格なレートリミット(Rate Limiting)をかけている場合、一部のホップだけが *(タイムアウト)になり、実際には正常にパケットが通過しているという「偽陽性」の障害に悩まされることがある。
2. QoSとパケット優先順位付け
ルーターのコントロールプレーン(CPU)は、データプレーンを通過する通常のUDP/TCPトラフィックの転送に比べ、自身宛てのICMPパケット処理やTTL切れ通知の生成を低優先度に設定していることが多い。そのため、高負荷なコアスイッチを通過する際、Windowsの tracert は実際のデータパケットよりも高いRTTを叩き出す傾向がある。計測結果のRTTをそのまま回線品質の絶対値として鵜呑みにしてはならない。
—
終わりに:ツールを疑い、パケットを愛せ
「なぜこのホップだけ応答がないのか?」
夜間の障害対応において、モニタリング画面の前でエンジニアが頭を抱える瞬間だ。そんな時、目の前のツール(それがWindowsの tracert であろうと、Linuxの traceroute であろうと、あるいは tcptraceroute であろうと)が、ワイヤー上で一体「何のプロトコルを喋っているのか」を正確にイメージできるかどうかが、プロフェッショナルとアマチュアを分ける境界線となる。
パケットは嘘をつかない。OSの仕様やセキュリティポリシーの隙間で、彼らは今日も寸分狂わずルーティングテーブルに従って飛び交っている。その微細な息遣いを聞き取る能力こそが、私たちインフラエンジニアの最大の武器なのだ。さあ、冷めたコーヒーを飲み干して、次のパケット解析に戻ろうか。
コメント