use flowscope::{
driver::{Driver, Event, SlotMessage},
extract::{FiveTuple, FiveTupleKey},
icmp::{IcmpMessage, IcmpParser},
pcap::PcapFlowSource,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args()
.nth(1)
.unwrap_or_else(|| "tests/data/mixed_short.pcap".to_string());
let mut builder = Driver::builder(FiveTuple::bidirectional());
let mut icmp_slot = builder.datagram_broadcast(IcmpParser::new());
let mut driver = builder.build();
let mut events: Vec<Event<FiveTupleKey>> = Vec::new();
let mut icmp_msgs: Vec<SlotMessage<IcmpMessage, FiveTupleKey>> = Vec::new();
let mut errors_observed = 0usize;
let mut errors_correlated = 0usize;
let mut errors_orphaned = 0usize;
for owned in PcapFlowSource::open(&path)?.views() {
let owned = owned?;
events.clear();
driver.track_into(&owned, &mut events);
icmp_msgs.clear();
icmp_slot.drain(&mut icmp_msgs);
for m in &icmp_msgs {
if !m.message.is_error() {
continue;
}
errors_observed += 1;
let kind_label: String =
match (m.message.dest_unreachable_kind(), m.message.mtu_signal()) {
(Some(du), _) => format!("DU/{}", du.as_str()),
(_, Some(mtu)) => {
let mtu_val = mtu
.next_hop_mtu()
.map(|m| format!(" mtu={m}"))
.unwrap_or_default();
format!("{}{mtu_val}", mtu.as_str())
}
_ => m.message.short_kind().to_string(),
};
let Some((_label, inner)) = m.message.error_inner() else {
errors_orphaned += 1;
continue;
};
match driver.tracker().stats_for_inner(inner) {
Some((flow_key, stats)) => {
errors_correlated += 1;
println!(
"{kind_label:<24} flow {flow_key:?} \
({} pkts, {} bytes in flight)",
stats.total_packets(),
stats.total_bytes(),
);
}
None => {
errors_orphaned += 1;
println!("{kind_label:<24} (no live flow — orphan)");
}
}
}
}
println!();
println!("=== summary ===");
println!("ICMP errors observed: {errors_observed}");
println!("Correlated to flows: {errors_correlated}");
println!("Orphan (no live flow): {errors_orphaned}");
Ok(())
}