# multiprobe
[](https://crates.io/crates/multiprobe)
[](https://docs.rs/multiprobe)
[](LICENSE)
Enterprise-grade multi-protocol network probing library for Rust with Paris Traceroute, path analytics, MTU discovery, bufferbloat detection, and TLS analysis.
## Why multiprobe?
- **Paris Traceroute** - First pure-Rust implementation of ECMP-aware traceroute
- **Comprehensive Analytics** - Jitter, MTU, bufferbloat, reordering in one crate
- **Protocol Differential Score** - Novel metric for cross-protocol path analysis
- **Production Ready** - 100+ tests, zero unsafe in public API
## Features
### Protocol Probes
| TCP | Connect probes with timing breakdown | None |
| UDP | Packet probes with response detection | None |
| ICMP | Echo requests with RTT measurement | CAP_NET_RAW |
| TLS | Handshake timing, cipher, version analysis | None |
### Path Discovery
| Traceroute | Standard hop-by-hop path discovery |
| Paris Traceroute | ECMP-aware traceroute (maintains flow consistency) |
| Multi-path Discovery | Find all paths through load balancers |
| Load Balancing Detection | Identify per-flow vs per-packet ECMP |
### Path Analytics
| Latency Stats | min/max/mean/median/p95/p99/jitter |
| Path MTU | Binary search MTU discovery |
| Bufferbloat | Latency degradation under load (A-F grading) |
| Packet Reordering | Out-of-order delivery detection |
### Classification
| Path Fingerprint | Stable hashes for path comparison |
| Protocol Differential Score | Cross-protocol behavior analysis |
| Network Behavior | ICMP filtering, asymmetric routing detection |
## Installation
```toml
[dependencies]
multiprobe = "0.1"
```
## Quick Start
```rust
use multiprobe::Probe;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), multiprobe::Error> {
// TCP probe with timing
let tcp = Probe::tcp("example.com", 443)
.timeout(Duration::from_secs(5))
.send().await?;
println!("Success: {}", tcp.success);
println!("Latency: {:.2}ms", tcp.timing.total_ms());
Ok(())
}
```
## Multi-Protocol Analysis
```rust
use multiprobe::{Probe, Classifier};
let result = Probe::multi("example.com")
.tcp(80)
.tcp(443)
.udp(53)
.timeout(Duration::from_secs(5))
.send().await?;
// Per-protocol results
for probe in &result.results {
println!("{}: {} ({:.2}ms)",
probe.protocol,
if probe.success { "OK" } else { "FAIL" },
probe.timing.total_ms());
}
// Path classification
println!("Classification: {}", result.classify());
// Protocol Differential Score
let pds = Classifier::differential_score(&result.results);
println!("Consistency: {:.2}", pds.consistency);
println!("Behavior: {}", pds.interpret());
// Fingerprint
println!("Fingerprint: {}", Classifier::fingerprint(&result.results));
println!("Hash: {:016x}", Classifier::fingerprint_hash(&result.results));
```
## Paris Traceroute
Standard traceroute fails behind ECMP load balancers because it varies flow identifiers between probes. Paris Traceroute (Augustin et al., 2006) maintains constant flow IDs for consistent path discovery.
```rust
use multiprobe::{Probe, ParisMode, FlowId};
// Basic Paris Traceroute
let trace = Probe::paris("example.com")
.max_hops(30)
.timeout_per_hop(Duration::from_secs(2))
.send().await?;
println!("Target: {} ({})", trace.target, trace.target_ip);
println!("Hops: {}", trace.hops.len());
println!("Reached: {}", trace.reached_destination);
println!("Load Balancing: {}", trace.load_balancing);
for hop in &trace.hops {
let addr = hop.addr.map(|ip| ip.to_string())
.unwrap_or_else(|| "*".to_string());
println!("{:2}. {:15} {:.2}ms",
hop.ttl, addr, hop.rtt.as_secs_f64() * 1000.0);
}
// With custom flow ID
let trace = Probe::paris("example.com")
.flow_id(FlowId::udp(33434, 33434))
.mode(ParisMode::Udp)
.detect_load_balancing(true)
.send().await?;
// Discover multiple paths through load balancers
let paths = multiprobe::discover_paths("example.com", 6, &Default::default()).await?;
println!("Found {} distinct paths", paths.len());
```
## TLS Handshake Analysis
```rust
use multiprobe::Probe;
let tls = Probe::tls("example.com")
.port(443)
.timeout(Duration::from_secs(10))
.send().await?;
if tls.success {
println!("TLS Version: {}", tls.tls_version);
println!("Cipher: {:?}", tls.cipher_suite);
println!("HTTP/2: {}", tls.supports_http2());
println!("Modern TLS: {}", tls.is_modern_tls());
println!("\nTiming Breakdown:");
println!(" DNS: {:.2}ms", tls.timing.dns_ms());
println!(" TCP: {:.2}ms", tls.timing.tcp_ms());
println!(" TLS: {:.2}ms", tls.timing.tls_ms());
println!(" Total: {:.2}ms", tls.timing.total_ms());
} else {
println!("TLS failed: {:?}", tls.error);
}
```
## Latency Statistics
```rust
use multiprobe::Probe;
let stats = Probe::latency("example.com", 443)
.samples(100)
.interval(Duration::from_millis(100))
.send().await?;
println!("Samples: {}/{}", stats.success_count, stats.sample_count);
println!("Loss: {:.1}%", stats.loss_rate * 100.0);
println!();
println!("Min: {:.2}ms", stats.min_rtt.as_secs_f64() * 1000.0);
println!("Max: {:.2}ms", stats.max_rtt.as_secs_f64() * 1000.0);
println!("Mean: {:.2}ms", stats.mean_rtt.as_secs_f64() * 1000.0);
println!("Median: {:.2}ms", stats.median_rtt.as_secs_f64() * 1000.0);
println!("P95: {:.2}ms", stats.p95_rtt.as_secs_f64() * 1000.0);
println!("P99: {:.2}ms", stats.p99_rtt.as_secs_f64() * 1000.0);
println!("Jitter: {:.2}ms", stats.jitter.as_secs_f64() * 1000.0);
println!("StdDev: {:.2}ms", stats.std_dev.as_secs_f64() * 1000.0);
if stats.has_high_jitter() {
println!("WARNING: High jitter detected!");
}
if stats.has_packet_loss() {
println!("WARNING: Packet loss detected!");
}
```
## Path MTU Discovery
```rust
use multiprobe::Probe;
let mtu = Probe::mtu("example.com")
.min_mtu(68)
.max_mtu(1500)
.timeout(Duration::from_secs(2))
.send().await?;
println!("Path MTU: {} bytes", mtu.path_mtu);
println!("DF Honored: {}", mtu.df_honored);
println!("Frag Needed msgs: {}", mtu.frag_needed_count);
```
## Bufferbloat Detection
```rust
use multiprobe::Probe;
let bloat = Probe::bufferbloat("example.com")
.port(443)
.baseline_samples(10)
.loaded_samples(10)
.send().await?;
println!("Baseline: {:.2}ms", bloat.baseline_latency.as_secs_f64() * 1000.0);
println!("Under Load: {:.2}ms", bloat.loaded_latency.as_secs_f64() * 1000.0);
println!("Bloat Factor: {:.2}x", bloat.bloat_factor);
println!("Grade: {}", bloat.grade); // A-F rating
println!("Detected: {}", bloat.detected);
```
## Path Classification
| `Open` | All protocols succeed |
| `IcmpFiltered` | ICMP blocked, TCP/UDP pass |
| `TcpFiltered` | ICMP passes, TCP blocked |
| `SelectiveFirewall` | Some ports open, some closed |
| `Blocked` | All protocols fail |
| `NatDetected` | TTL variance indicates NAT |
## Protocol Differential Score
Quantifies behavioral differences across protocols:
```rust
use multiprobe::Classifier;
let pds = Classifier::differential_score(&results);
// Differentials (0.0 = identical, 1.0 = completely different)
println!("ICMP vs TCP: {:.2}", pds.icmp_tcp_diff);
println!("ICMP vs UDP: {:.2}", pds.icmp_udp_diff);
println!("TCP vs UDP: {:.2}", pds.tcp_udp_diff);
// Consistency (1.0 = all protocols identical)
println!("Consistency: {:.2}", pds.consistency);
// Latency variance
println!("Latency Variance: {:.2}ms", pds.latency_variance_ms);
// Interpreted behavior
use multiprobe::NetworkBehavior;
match pds.interpret() {
NetworkBehavior::Direct => println!("Direct path"),
NetworkBehavior::IcmpFiltering => println!("ICMP filtering"),
NetworkBehavior::ProtocolSpecificFiltering => println!("Protocol filtering"),
NetworkBehavior::AsymmetricRouting => println!("Asymmetric routing"),
NetworkBehavior::Unknown => println!("Unknown"),
}
```
## Load Balancing Detection
```rust
use multiprobe::LoadBalancingType;
let trace = Probe::paris("example.com")
.detect_load_balancing(true)
.send().await?;
match trace.load_balancing {
LoadBalancingType::None => println!("No load balancing"),
LoadBalancingType::PerFlow => println!("Per-flow ECMP"),
LoadBalancingType::PerPacket => println!("Per-packet ECMP"),
LoadBalancingType::Unknown => println!("Unknown"),
}
```
## Permissions
| TCP/UDP probes | None | None | None |
| TLS probes | None | None | None |
| Latency stats | None | None | None |
| ICMP ping | CAP_NET_RAW | root | Admin |
| Traceroute | CAP_NET_RAW | root | Admin |
| Paris Traceroute | CAP_NET_RAW | root | Admin |
| MTU Discovery | CAP_NET_RAW | root | Admin |
```bash
# Linux: Grant capability
sudo setcap cap_net_raw+ep ./your-binary
# macOS/Linux: Run with sudo
sudo ./your-binary
```
## API Reference
### Probe Builders
```rust
Probe::tcp(target, port) // TCP connect probe
Probe::udp(target, port) // UDP probe
Probe::icmp(target) // ICMP ping
Probe::multi(target) // Multi-protocol
Probe::traceroute(target) // Standard traceroute
Probe::paris(target) // Paris Traceroute
Probe::tls(target) // TLS handshake
Probe::latency(target, port) // Latency statistics
Probe::mtu(target) // Path MTU discovery
Probe::bufferbloat(target) // Bufferbloat detection
```
### Standalone Functions
```rust
multiprobe::paris_traceroute(target, &options) // Paris trace
multiprobe::discover_paths(target, flows, &options) // Multi-path
multiprobe::measure_latency(target, port, samples, interval)
multiprobe::discover_path_mtu(target, &options)
multiprobe::detect_bufferbloat(target, &options)
multiprobe::analyze_reordering(target, port, packets)
multiprobe::probe_tls(target, &options)
multiprobe::compare_tls(target1, target2, &options)
```
## References
- **Paris Traceroute**: Augustin et al., "Avoiding traceroute anomalies with Paris traceroute" (IMC 2006)
- **RFC 3550**: RTP jitter calculation
- **RFC 1191**: Path MTU Discovery
- **Bufferbloat**: Gettys & Nichols, "Bufferbloat: Dark Buffers in the Internet" (2012)
## License
MIT OR Apache-2.0