# How Traceroute Works: Implementation in Rust from Scratch
Traceroute is a network diagnostics tool that reveals the path packets take through the internet. It relies on two key mechanisms: TTL (Time to Live) in IP headers and ICMP error messages. By incrementing TTL at each step, routers send notifications when the packet's lifetime expires, building a route map. Implementing our own version in Rust in just 80 lines of code shows how low-level network operations become clear with a deep dive into protocols.
UDP Probe Architecture
The critical element of traceroute is sending targeted packets with limited TTL. UDP is used instead of TCP for three reasons:
- No handshake reduces overhead
- No delivery guarantee—packets are meant to be dropped
- High port numbers (starting from 33434) minimize conflicts with running services
The code initializes two sockets:
let send_sock = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
send_sock.set_ttl_v4(ttl)?;
let recv_sock = Socket::new(
Domain::IPV4,
Type::from(libc::SOCK_RAW),
Some(Protocol::ICMPV4),
)?;
The first socket sends doomed packets with the specified TTL, the second intercepts ICMP responses. Important: raw sockets require root privileges, as they operate at the network stack level.
Parsing ICMP Responses
When a router drops a packet at TTL=0, it sends an ICMP message of type 11 (Time Exceeded). The data structure contains:
- First 20 bytes—IP header of the response
- Bytes 12-15—router's IP address
- Byte 20—ICMP message type
The initial implementation only parsed the IP address:
if buf.len() >= 20 {
let ip = Ipv4Addr::new(buf[12], buf[13], buf[14], buf[15]);
Ok(Some(ip))
}
This led to errors in detecting the endpoint. Proper handling requires checking the ICMP type:
match buf[20] {
11 => Ok(ProbeResult::Hop(ip)),
3 if ip == target => Ok(ProbeResult::Reached(ip)),
3 => Ok(ProbeResult::Hop(ip)),
_ => Ok(ProbeResult::Timeout),
}
Type 3 (Destination Unreachable) indicates reaching the target only if the IP matches.
Traceroute Optimization
The original traceroute uses two techniques missing from the basic implementation:
- Incrementing port number—each subsequent packet is sent to port +1. This allows unambiguous matching of ICMP responses to requests via the Identification field in the UDP header.
- TCP mode support—when UDP is blocked by firewalls, a SYN packet with low TTL is used. The hop detection mechanism remains the same.
A critical error in early code versions—ignoring the IP address check when receiving Type 3. Without the if ip == target condition, tracing stopped at the first router returning Destination Unreachable.
What’s Important
- TTL as a control tool—gradually increasing the value allows sequential discovery of each hop
- ICMP messages—the basis of diagnostics—Time Exceeded (Type 11) and Destination Unreachable (Type 3) form the route map
- Raw sockets require privileges—low-level network operations are impossible without root rights
- UDP vs TCP—protocol choice affects firewall evasion, but not the basic algorithm
- Parsing binary data—direct access to packet bytes requires knowledge of IP/ICMP header structures
Implementing the Stop Condition
A key improvement—correctly detecting when the target is reached. The ProbeResult enum replaces the simple Option<Ipv4Addr>:
enum ProbeResult {
Hop(Ipv4Addr),
Reached(Ipv4Addr),
Timeout,
}
In the main loop, result handling looks like this:
match hop {
ProbeResult::Hop(ip) => println!("{:>2} {}", ttl, ip),
ProbeResult::Reached(ip) => {
println!("{:>2} {}", ttl, ip);
break;
}
ProbeResult::Timeout => println!("{:>2} *", ttl),
}
This ensures the trace stops upon reaching the target host, not on timeout. In real-world scenarios, also add:
- Three probes per TTL for latency statistics
- Handling of fragmented packets
- IPv6 support
- Distance-based timeouts
Summary and Limitations
The built implementation demonstrates traceroute's core, but has limitations:
- Doesn't account for asymmetric routes
- Doesn't handle ICMP Rate Limiting
- Ignores MPLS tags in modern networks
- Requires superuser privileges
For production solutions, use libraries like pnet_packet to avoid manual byte parsing. However, writing a raw version is the best way to understand network fundamentals. Every network engineer should implement traceroute at least once to grasp how data travels through the internet.
— Editorial Team
No comments yet.