Implementing Paris Traceroute in Rust: Tracing Paths Through Load Balancers

rust dev.to

Standard traceroute is broken on modern networks.

If you've ever run traceroute to debug a connectivity issue and seen wildly inconsistent hops, you've hit this problem. The culprit: Equal-Cost Multi-Path (ECMP) routing.

The Problem

ECMP load balancers distribute traffic across multiple paths based on flow identifiers — typically a hash of source IP, destination IP, source port, and destination port. Traditional traceroute changes the destination port (or ICMP sequence number) for each probe, which means each probe can take a completely different path through the network.

The result? A traceroute that shows hops from multiple physical paths stitched together into one nonsensical output.

Paris Traceroute

In 2006, Augustin et al. published "Avoiding traceroute anomalies with Paris traceroute" at IMC. The key insight: keep flow identifiers constant across all probes so they all traverse the same path.

For UDP probes, this means:

  • Same source port
  • Same destination port
  • Vary the TTL (and checksum) only

For ICMP, it's trickier — ICMP echo requests don't have ports. Paris Traceroute manipulates the ICMP checksum field to maintain a constant value that ECMP routers hash on.

Implementing in Rust

I couldn't find a pure-Rust implementation, so I built one in multiprobe.

The core concept is a FlowId that stays constant:


rust
pub struct FlowId {
    pub src_port: u16,
    pub dst_port: u16,
    pub identifier: u16,
}

impl FlowId {
    pub fn udp(src: u16, dst: u16) -> Self {
        Self { src_port: src, dst_port: dst, identifier: 0 }
    }
}
When sending probes, we only vary the TTL:


let trace = Probe::paris("example.com")
    .flow_id(FlowId::udp(33434, 33434))
    .max_hops(30)
    .send().await?;

for hop in &trace.hops {
    println!("{:2}. {:15} {:.2}ms", 
        hop.ttl, 
        hop.addr.map(|ip| ip.to_string()).unwrap_or("*".into()),
        hop.rtt.as_secs_f64() * 1000.0);
}
Detecting Load Balancing
Once you can trace a single path consistently, you can also detect load balancing by sending probes with different flow IDs and comparing the paths:


let paths = discover_paths("example.com", 6, &Default::default()).await?;
println!("Found {} distinct paths", paths.len());
If you get multiple distinct paths, you've found an ECMP load balancer.

Results
With Paris Traceroute, you get:

Consistent paths through ECMP networks
Accurate hop-by-hop latency measurements
Load balancer detection (per-flow vs per-packet)
Try It

[dependencies]
multiprobe = "0.1"
crates.io
GitHub
docs.rs
References
Augustin, B., et al. "Avoiding traceroute anomalies with Paris traceroute." IMC 2006.
RFC 1191: Path MTU Discovery
Questions or feedback? Open an issue on GitHub or find me on [platform].



Enter fullscreen mode Exit fullscreen mode

Source: dev.to

arrow_back Back to Tutorials