深夜のNOC(ネットワークオペレーションセンター)の静寂を切り裂くのは、決まってアラート音ではない。冷えたコーヒーのマグカップを置く音と、モニタの青白い光に照らされたエンジニアたちの乾いたキーボードの打鍵音だ。「東京のユーザーから、西海岸のデータセンターへのレイテンシが急に跳ね上がった」「BGPのどこかで変なルーティングループを踏んでいる気配がある」。そんな修羅場で、私たちが真っ先に叩くコマンドが何か、お分かりだろうか。
pingで往復の遅延を確認し、次に取り出すのは決まってtracerouteだ。だが、日々の運用でルーティングの異常やISP間の非効率なハンドオフに直面するシニアエンジニアにとって、単にIPアドレスの羅列を見るだけのtracerouteは、暗闇で手探りをするようなものに過ぎない。我々が知りたいのは「どのルーターを通ったか」ではなく、「どのAutonomous System(AS:自律システム)を経由し、どの商用バックボーンのどのピアリングポイントで無駄な遠回りを強いられているか」という、インターネットの骨格そのものなのだ。
今回は、tracerouteにAS情報や地理的文脈を付加し、パケットレベルの挙動から経路最適化、さらにはRTT(Round Trip Time)削減やカーネルチューニングに至るまで、泥臭い実務の現場で培った知見を余すことなく紐解いていこう。
—
1. パケットの旅路:tracerouteの内部挙動とTTLのカラクリ
tracerouteがどのようにして宛先までの経路を描き出すのか。そのプリミティブな仕組みを理解しておくことは、高度なトラブルシューティングの前提となる。
ルーターは通常、受け取ったIPパケットのIPヘッダーにあるTTL(Time To Live、IPv6の場合はHop Limit)フィールドを1つデクリメントし、値が0になった瞬間にそのパケットを破棄する。同時に、送信元に対してICMP Time Exceeded(タイプ11、コード0)メッセージを送り返す。これがtracerouteの基本原理だ。
しかし、現代のネットワーク運用において、デフォルトのtraceroute(多くのOSではUDPまたはICMP Echoを使用)をそのまま信じるのは危険だ。ファイアウォールやロードバランサー(ECMP:Equal-Cost Multi-Path)が介在する環境では、パケットごとに異なる経路を通るため、偽りのトポロジが描かれてしまうことがある。
そのため、実務ではTCP SYNパケットを用いたtraceroute(tcptracerouteやnmap --tracerouteなど)を多用する。
# 特定の宛先ポート(例: 443/TCP)を指定してTCPベースのtracerouteを実行
# これにより、ステートフルファイアウォールやL4ロードバランサーの挙動を模倣できる
sudo traceroute -T -p 443 -n target.example.com
ここで-nオプション(逆引きDNSを引かない)をあえて最初につけているのは、DNSの遅延に惑わされず、まずはパケットの生データを高速に捉えるためだ。だが、ここからが本番である。IPアドレスの羅列から、そのバックボーンの正体を暴くには「AS情報」の付加が不可欠となる。
—
2. WhoisとAS番号(ASN):経路の「所有者」を特定する
インターネットはASの巨大なパッチワークだ。Amazon、Google、Cloudflare、そして国内外の各ISP(NTT、KDDI、IIJなど)がそれぞれのAS番号(ASN)を持ち、BGP(Border Gateway Protocol)で経路情報を交換し合っている。
パケットがどのASをホップしているかを暴くには、tracerouteの出力を加工するか、最初からAS情報を付加して出力してくれるツールを使うのが定石だ。最も手軽で強力なのが、whoisクエリを統合したbgpscanner的なアプローチ、あるいはRIPE NCCが提供するripe-atlasや、古くから愛用されているmtr(My Traceroute)のAS表示機能である。
# mtrコマンドを用いて、リアルタイムにパケットロスとAS情報を同時に監視する
# --aslookupオプションにより、各ホップのIPアドレスからBGPテーブルを引いてASNを表示
mtr --aslookup -b -c 100 target.example.com
このコマンドを叩くと、画面上には次のような情報が展開される。
HOST: noc-gateway.internal Loss% Snt Last Avg Best Wrst StDev
1. AS??? 192.168.1.1 0.0% 100 0.4 0.5 0.3 1.2 0.1
2. AS2497 inet-gw.ocn.ad.jp 0.0% 100 2.1 2.3 1.9 4.5 0.3
3. AS2497 ntt-backbone.net 0.0% 100 3.2 3.1 2.8 5.0 0.4
4. AS2914 ntt.net (NTT America) 0.0% 100 12.4 12.5 12.1 14.2 0.5
5. AS15169 google.net 0.0% 100 13.0 13.1 12.8 15.5 0.4
ここで注目すべきは、ホップ3(AS2497:NTT)からホップ4(AS2914:NTT Americaのグローバルバックボーン)へトランジットが変わる瞬間だ。日本国内から米国のサーバーへ向かう際、どのISPの海底ケーブル(トランジット回線)を通過し、どのIX(インターネットエクスチェンジ)でハンドオフが行われているかが、ASNを見ることで一目瞭然になる。
—
3. 経路最適化分析:ホット・ポテト・ルーティングとコールド・ポテト・ルーティングの罠
インフラアーキテクトやテックリードがASホップを分析する最大の理由は、「無駄なトランジットコストの削減」と「レイテンシ(RTT)の極小化」にある。
ここで、BGPのルーティングポリシーにおける古典的かつ極めて重要な概念を思い出してほしい。
1. ホット・ポテト・ルーティング(Hot-Potato Routing): 自ASのネットワーク内をできるだけ短い距離で通過させ、隣接する他ASへ「熱い芋(ホット・ポテト)」を投げ捨てるように早く引き渡す方式。トランジットコストを抑えられるが、最適経路とは限らない。
2. コールド・ポテト・ルーティング(Cold-Potato Routing): 自ASのバックボーンを可能な限り長く引き回し、宛先の直近のピアリングポイントまで自社網内で運んでから他ASに引き渡す方式。レイテンシやパケットロス率の制御には優れるが、コストがかかる。
もし、東京から大阪へ向かうトラフィックのtracerouteを取った際、パケットが一度アメリカ西海岸のASを経由して(いわゆる「太平洋一周コース」)戻ってくるという珍妙な経路(※実際の障害や設定ミスで稀に起きる)を発見したとしたらどうだろう? これは、BGPのAS-PATHプリペンドの設定ミスや、ISP間のピアリングポリシーの変更による「次善の経路(Suboptimal Route)」の踏み込みに他ならない。
こうした経路異常を検出し、自社のBGPアナウンスメントを調整したり、マルチホーム環境におけるローカルプリファレンス(Local Preference)の値をいじって適切なISPへトラフィックを誘導することが、我々エンジニアの腕の見所となる。
—
4. パケットとカーネルの極限チューニング:RTT削減とTCPバッファの最適化
経路の非効率性がASNレベルで判明し、それを物理的あるいはBGPポリシー的に修正したら、次はエンドポイント(Linuxカーネル)側のチューニングだ。どれほど美しい経路を通っても、OSのネットワークスタックがボトルネックになっていては意味がない。
特に、地理的距離がある遠隔地(例: 東京ーサンフランシスコ間、RTTが約100ms)との間で最大スループットを引き出すには、帯域遅延積(BDP: Bandwidth-Delay Product)に見合ったTCPウィンドウサイズの設定が不可欠である。
以下のカーネルパラメータは、高スループット・低遅延を追求するプロダクション環境のLinuxサーバー(/etc/sysctl.conf)において、私たちが必ず投入する鉄板の設定だ。
# --- Linuxカーネルネットワーク最適化設定 ---
# 最大TCP受信バッファおよび送信バッファのサイズ(バイト単位)
# 10Gbps回線かつRTT 100ms環境を想定し、余裕を持ったサイズを確保
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
# TCP自動チューニングで使用されるメモリの最小値、デフォルト値、最大値(ページ単位またはバイト単位)
# 構文: [最小値] [デフォルト値] [最大値]
net.ipv4.tcp_rmem = 4096 87380 33554432
net.ipv4.tcp_wmem = 4096 65536 33554432
# 輻輳制御アルゴリズムの変更
# 従来のCUBICに加え、パケットロスに強く帯域を最大限に活用するBBR(Bottleneck Bandwidth and RTT)を採用
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# ウィンドウ スケーリング(Window Scaling、RFC 1323)の有効化
# これにより、64KBを超える大きなTCPウィンドウサイズが可能になる
net.ipv4.tcp_window_scaling = 1
# 選択確認応答(SACK: Selective Acknowledgement)の有効化
# パケットロス発生時の無駄な再送を防ぎ、スループットの低下を抑制
net.ipv4.tcp_sack = 1
特に、Googleが開発した輻輳制御アルゴリズムであるBBR(Bottleneck Bandwidth and RTT)の導入は、tracerouteで見つかった長距離・高遅延な経路上のパフォーマンスを劇的に改善する。パケットロスを「輻輳(混雑)」ではなく「単なるドロップ」と誤認してウィンドウサイズを縮小してしまうCUBICの悪癖を、BBRは鮮やかに回避してくれるのだ。
—
5. 自動化と監視:PythonによるASパス追跡スクリプトの実装
手動でtracerouteやwhoisを叩くのはインシデント対応の初期段階だけだ。プロダクション環境では、主要なバックボーンやクラウドリージョンへの経路変動を常時監視し、予期せぬASホップの増加やトランジットの変更を検知する仕組みが求められる。
以下に、Pythonを用いて指定した宛先までの経路をトレースし、各ホップのIPアドレスからWhois情報を引いてAS情報を抽出するスクリプトの骨子を示す。実務では、これをPrometheusのカスタムexporterやSlack通知Botに組み込んで運用している。
#!/usr/bin/env python3
"""
Advanced Traceroute AS-Path Inspector
指定された宛先IPに対するtracerouteを実行し、各ホップのIPとAS情報をマッピングするスクリプト
"""
import subprocess
import re
import sys
import ipaddress
from ipwhois import IPWhois
def run_traceroute(target):
"""
Scapyや生ソケットを使わず、OSのtracerouteコマンドを安全に呼び出して出力をパースする
"""
print(f"[*] Tracing route to {target} ...")
# IPv4を対象に、最大ホップ数30でTCPポート443を狙う
cmd = ["traceroute", "-T", "-p", "443", "-n", "-m", "30", target]
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=60)
return result.stdout.splitlines()
except subprocess.CalledProcessError as e:
print(f"[!] Traceroute failed: {e}", file=sys.stderr)
return []
except subprocess.TimeoutExpired:
print("[!] Traceroute timed out.", file=sys.stderr)
return []
def parse_and_enrich_route(lines):
"""
tracerouteの出力からIPアドレスを抽出し、ipwhoisライブラリでASNと組織名を取得する
"""
# IPアドレスを抽出するための正規表現
ip_pattern = re.compile(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b')
print(f"{'Hop':<4} | {'IP Address':<15} | {'ASN':<8} | {'Organization'}")
print("-" * 65)
for line in lines:
# ヘッダー行をスキップ
if line.startswith("traceroute") or not line.strip():
continue
# ホップ番号の取得
parts = line.strip().split()
if not parts[0].isdigit():
continue
hop_num = parts[0]
# 行からIPアドレスを検索
match = ip_pattern.search(line)
if not match:
print(f"{hop_num:<4} | {'* (Timeout/No Response)':<40}")
continue
ip_addr = match.group(0)
# プライベートIPやローカルホストの場合はWhoisをスキップ
try:
ip_obj = ipaddress.ip_address(ip_addr)
if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_reserved:
print(f"{hop_num:<4} | {ip_addr:<15} | {'N/A':<8} | {'Private/Local Network'}")
continue
except ValueError:
continue
# IPWhoisを使ってASNをクエリ
try:
obj = IPWhois(ip_addr)
results = obj.lookup_rdap(depth=1)
asn = results.get('asn', 'UNKNOWN')
asn_desc = results.get('asn_description', 'UNKNOWN')
print(f"{hop_num:<4} | {ip_addr:<15} | {f'AS{asn}':<8} | {asn_desc}")
except Exception:
print(f"{hop_num:<4} | {ip_addr:<15} | {'AS?':<8} | {'Lookup Failed'}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <target_hostname_or_ip>")
sys.exit(1)
target_host = sys.argv[1]
raw_output = run_traceroute(target_host)
if raw_output:
parse_and_enrich_route(raw_output)
このスクリプトを定期実行し、例えば「昨日まで経由していなかった低品質なトランジットASが経路に出現した」あるいは「ASホップ数が急に増加した」といったトポロジの変化を検知した瞬間、即座にインシデントチャネルへアラートを飛ばす。これが、プロフェッショナルなNOCが備えるべき「攻めの監視」の姿だ。
—
6. おわりに:パケットに語らせる技術
ネットワークの世界に魔法はない。そこにあるのは、無数のルーターがBGPのルールに従って導き出す数学的な経路と、光ファイバーの中を秒速20万キロメートルで駆け抜ける電子の物理法則だけだ。
トラブルシューティングの現場で迷ったとき、私たちが頼るべきは勘や経験則ではなく、パケットが刻んだ泥臭いログそのものである。tracerouteから得られるIPアドレスの向こう側に広がるASの巨大な生態系を見通し、カーネルの奥深くにあるバッファや輻輳制御に至るまでをコントロールする――それこそが、真のインフラストラクチャー・エンジニアの矜持である。
さあ、モニタの向こう側でパケットたちが次のルーターへ飛び立とうとしている。今日も最高の経路を見つけに行こう。
コメント