
2026/04/06 12:40
**Traceroute – 基本的な理解** | 目的 | ネットワーク経路と遅延時間の確認 | |------|-----------------------------------| | 主な機能 | <ul><li>パケットが通過するホップ(ルーター)を列挙</li><li>各ホップへの往復時間(RTT)を測定</li></ul> | ### 使い方の概要 1. **コマンド実行** - Windows: `tracert <ドメインまたはIP>` - Linux/macOS: `traceroute <ドメインまたはIP>` 2. **結果の構成** ``` 1 router1.example.com (192.0.2.1) 1.234 ms 1.210 ms 1.198 ms 2 isp-gateway.example.net (203.0.113.5) 12.567 ms 12.543 ms 12.530 ms … ``` 3. **情報の読み取り** - **ホップ番号**: パケットが通過したルーター数。 - **ホスト名/IP**: 各ルーターの識別子。 - **RTT(ms)**: TTLごとに送信された3回分の往復時間。平均値・最大値を参考にする。 ### 注意点 - 一部ネットワークでは ICMP/UDP の TTL‑Exceeded メッセージがブロックされ、`* * *` と表示されることがあります。 - `traceroute` のデフォルト設定は TTL を 1 から増加させ、各ホップで 3 回ずつ送信します。 - オプションでパケット数やプロトコルを変更可能(例: `-m 30`, `-I` 等)。 - 結果は「経路」の一部しか示さないため、完全なネットワークトポロジーではありません。 ### 応用例 | シナリオ | 確認できること | |----------|----------------| | 接続遅延が大きい | 遅延が集中しているホップを特定 | | パケットロス | `* * *` が多い箇所でルーターまたはファイアウォールの影響 | | ルーティング変更 | 新旧経路の比較 | --- **まとめ** Traceroute は、ネットワークパスと遅延を可視化するためのシンプルかつ強力なツールです。 正確に理解し、適切に解釈すれば、トラブルシューティングやネットワーク最適化に大いに役立ちます。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
著者は、標準の traceroute を模倣する純粋な Rust の traceroute ツールを構築しました。このツールは、TTL 値を段階的に低くしながら UDP プローブを送信し、ICMP 「Time Exceeded」応答を受信して各ホップを検出します。プローブは UDP ポート 33434 に送られ、パケットが宛先に到達すると ICMP 「Port Unreachable」(タイプ 3)が発生し、到着を示します。
socket2 クレートを使用して、プログラムは各プローブの TTL を設定し、応答を取得するために raw ICMP ソケット(SOCK_RAW、ICMPV4)を開きます。IP ヘッダー(12–15 バイト)から送信元アドレスを解析し、20 バイト目で ICMP タイプ(11 = Time‑Exceeded、3 = Destination Unreachable)を判定します。結果は ProbeResult 列挙型(Hop、Reached、Timeout)に包まれ、各項目には Instant::now() で測定された経過時間が付与されます。TTL ごとに 3 本のプローブを送信し、ホップ IP が変わった時のみ出力を表示することで、1 ホップあたり 3 つの RTT 値が得られます。ループはプローブが Reached を返した瞬間に終了するため、ツールは最大 TTL 15 まで走ることはありません。現在の実装では、システム traceroute が提供するいくつかの機能(DNS 逆引き、UDP ポートの増分指定、ICMP エコーモード
-I、TCP モード -T、IPv6 サポート)が欠けています。要約では、一部のホップが * と表示される理由を説明しています―これは通常 ICMP レートリミットやファイアウォールによるものであり、特権要件(raw ソケットは root が必要、macOS では IPPROTO_ICMP を持つ unprivileged SOCK_DGRAM が使用可能)についても触れています。著者は欠落している機能を追加し、WireGuard/Tailscale のコントロールプレーン内部を調査し、IPv6 サポートの拡張も検討しています。完全なソースコードは GitHub に公開されており、ネットワーク経路解析やカスタム診断に関心がある開発者向けに、軽量でオープンソースな代替手段として提供されています。
本文
Rustで書くトレーサウト – すっきり整理版
トレーサウトは何をするものか?
トレーサウトは、TTL(Time To Live)が減少していくパケットを送信し、ルータが TTL が 0 になったパケットをドロップした際に返される ICMP “Time Exceeded” メッセージを受信して経路を調べます。
各応答にはそのルータの IP アドレスが含まれているため、途中のすべてのホップを確認できます。
コアアイデア
- TTL を指定した UDP パケットを送る。
- 生 ICMP ソケットで応答を受信する。
- 各 ICMP メッセージから送信元 IP を取り出し、ホップを判定する。
1 回のプローブ(サンプルコード)
use socket2::{Domain, Protocol, SockAddr, Socket, Type}; use std::mem::MaybeUninit; use std::net::{Ipv4Addr, SocketAddrV4}; use std::time::{Duration, Instant}; fn probe(target: Ipv4Addr, ttl: u32) -> std::io::Result<ProbeResult> { // パケット送信用 UDP ソケット let send_sock = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?; send_sock.set_ttl_v4(ttl)?; // ICMP 応答受信用生ソケット let recv_sock = Socket::new( Domain::IPV4, Type::from(libc::SOCK_RAW), Some(Protocol::ICMPV4), )?; recv_sock.set_read_timeout(Some(Duration::from_secs(2)))?; let dest = SockAddr::from(SocketAddrV4::new(target, 33434)); let start = Instant::now(); send_sock.send_to(&[0u8; 32], &dest)?; let mut buf = [MaybeUninit::<u8>::uninit(); 512]; match recv_sock.recv(&mut buf) { Ok(n) => { let buf: &[u8] = unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, n) }; if buf.len() >= 21 { let ip = Ipv4Addr::new(buf[12], buf[13], buf[14], buf[15]); let elapsed = start.elapsed(); match buf[20] { 11 => Ok(ProbeResult::Hop(ip, elapsed)), 3 if ip == target => Ok(ProbeResult::Reached(ip, elapsed)), 3 => Ok(ProbeResult::Hop(ip, elapsed)), _ => Ok(ProbeResult::Timeout), } } else { Ok(ProbeResult::Timeout) } } Err(_) => Ok(ProbeResult::Timeout), } }
- TTL – パケットが通過できるホップ数を制御します。
- 生 ICMP ソケット – “Time Exceeded” を受信するために必要(
が必須)。sudo - ポート 33434 – 従来のトレーサウトで使われる宛先ポート。サービスはリッスンしていないので、最終的にパケットを受け取った際に “Port Unreachable” を返します。
結果型
enum ProbeResult { Hop(Ipv4Addr, Duration), // Type 11 – Time Exceeded Reached(Ipv4Addr, Duration), // Type 3 – Destination Unreachable Timeout, // タイムアウト(応答なし) }
メインループ
プログラムは TTL ごとに 3 本のプローブ を送り、宛先に到達したら停止します。
fn main() -> std::io::Result<()> { let target = Ipv4Addr::new(8, 8, 8, 8); // Google DNS for ttl in 1..=15 { let mut reached = false; let mut last_ip: Option<Ipv4Addr> = None; print!("{:>2} ", ttl); for _ in 0..3 { // 1 ホップあたり 3 本のプローブ match probe(target, ttl)? { ProbeResult::Hop(ip, rtt) | ProbeResult::Reached(ip, rtt) => { if last_ip != Some(ip) { print!("{} ", ip); last_ip = Some(ip); } print!("{:.3} ms ", rtt.as_secs_f64() * 1000.0); } ProbeResult::Timeout => print!("* "), } } println!(); if reached { break; } } Ok(()) }
実行例
1 <tailscale-ip> 5.713 ms 4.993 ms 4.739 ms 2 <router-ip> 5.355 ms 5.082 ms 4.998 ms 3 * * * 4 * * * 5 * * * 6 * * * 7 <isp-hop-1-ip> 15.658 ms 12.088 ms 11.362 ms 8 72.14.223.26 11.978 ms 12.555 ms 12.344 ms 9 * * * 10 8.8.8.8 14.246 ms 13.244 ms 12.892 ms
なぜ sudo
が必要か
sudoプログラムは 生 ICMP ソケット (
SOCK_RAW) を開きます。生ソケットはすべてのネットワークトラフィックを嗅ぎ取れるため、特権が要求されます。カーネルは root 権限を必要とするので
sudo が必須です。一方、システムにインストールされた traceroute バイナリは set‑uid ビットで実行され、明示的な sudo を不要にしています。
実装がカバーしている機能
| 機能 | 本物の traceroute | Rust 版 |
|---|---|---|
| TTL 増加 | ✔︎ | ✔︎ |
| ICMP タイプ確認 | ✔︎ | ✔︎ |
| RTT 計測 | ✔︎ | ✔︎ |
| ホップあたり 3 プローブ | ✔︎ | ✔︎ |
| DNS リバースルックアップ(オプション) | ✔︎ | ✘ |
| プローブポートの増分 | ✔︎ | ✘ (固定 33434) |
| ICMP Echo モード (-I) | ✔︎ | ✘ |
| TCP モード (-T) | ✔︎ | ✘ |
| IPv6 対応 | ✔︎ | ✘ |
トレーサウトで得られない情報
- 非対称経路 – ICMP 応答が別ルートを通ることがあります。
- MPLS トンネル – 1 ホップにまとめて表示される、または全く見えなくなる場合があります。
- ロードバランサの分岐 – 同一 TTL のプローブが異なるルータへ到達する可能性があります。
- ICMP レートリミッティング – 応答がドロップされ、
が連続して表示されることがあります。* * *
参考文献
- Tailscale exit node の投稿(インスピレーション元)
- WireGuard ホワイトペーパー
- GitHub 上の Traceroute‑Rust ソースコード
- Van Jacobson のオリジナルトレーサウト論文
- RFC 792: ICMP
- socket2 クレートドキュメント
- pnet クレートドキュメント(よりイディオム的なパケット解析用)
楽しいトレースライフを!