use std::{
sync::{
Arc,
atomic::{AtomicU64, Ordering},
mpsc,
},
thread,
};
use flowscope::{
OwnedPacketView, PacketView, Timestamp,
driver::{Driver, Event, SlotMessage},
extract::{FiveTuple, FiveTupleKey},
extractor::FlowExtractor,
http::{HttpMessage, HttpParser},
pcap::PcapFlowSource,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args().skip(1);
let path = args
.next()
.unwrap_or_else(|| "tests/data/mixed_short.pcap".to_string());
let n_shards: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(4);
println!("[sharded_capture] {} shards, pcap = {}", n_shards, path);
let total_http_msgs = Arc::new(AtomicU64::new(0));
let total_packets = Arc::new(AtomicU64::new(0));
let mut shard_senders: Vec<mpsc::Sender<OwnedPacketView>> = Vec::with_capacity(n_shards);
let mut shard_threads = Vec::with_capacity(n_shards);
for shard_id in 0..n_shards {
let (tx, rx) = mpsc::channel::<OwnedPacketView>();
shard_senders.push(tx);
let http_total = Arc::clone(&total_http_msgs);
let pkt_total = Arc::clone(&total_packets);
shard_threads.push(thread::spawn(move || {
shard_worker(shard_id, rx, http_total, pkt_total)
}));
}
let source = PcapFlowSource::open(&path)?;
let mut dispatched = 0u64;
for view in source.views() {
let view = view?;
let shard_id = hash_to_shard(&view.frame, n_shards);
if shard_senders[shard_id].send(view).is_err() {
break;
}
dispatched += 1;
}
println!(
"[sharded_capture] dispatched {} packets to shards",
dispatched
);
drop(shard_senders);
for (i, t) in shard_threads.into_iter().enumerate() {
let r = t.join().unwrap_or_else(|_| panic!("shard {i} panicked"));
if let Err(e) = r {
return Err(format!("shard {i} error: {e}").into());
}
}
println!(
"[sharded_capture] total: {} packets, {} HTTP messages across {} shards",
total_packets.load(Ordering::Relaxed),
total_http_msgs.load(Ordering::Relaxed),
n_shards,
);
Ok(())
}
fn shard_worker(
shard_id: usize,
rx: mpsc::Receiver<OwnedPacketView>,
http_total: Arc<AtomicU64>,
pkt_total: Arc<AtomicU64>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut builder = Driver::builder(FiveTuple::bidirectional());
let mut http_slot = builder.session_on_ports(HttpParser::default(), [80, 8080]);
let mut driver = builder.build();
let mut events: Vec<Event<FiveTupleKey>> = Vec::with_capacity(64);
let mut messages: Vec<SlotMessage<HttpMessage, FiveTupleKey>> = Vec::with_capacity(64);
let mut local_pkt = 0u64;
let mut local_http = 0u64;
while let Ok(view) = rx.recv() {
events.clear();
driver.track_into(PacketView::from(&view), &mut events);
let drained = http_slot.drain_n(&mut messages, 64);
local_pkt += 1;
local_http += drained as u64;
if !messages.is_empty() {
messages.clear();
}
}
println!(
"[shard {}] processed {} packets, {} HTTP messages",
shard_id, local_pkt, local_http
);
pkt_total.fetch_add(local_pkt, Ordering::Relaxed);
http_total.fetch_add(local_http, Ordering::Relaxed);
Ok(())
}
fn hash_to_shard(frame: &[u8], n_shards: usize) -> usize {
FiveTuple::bidirectional()
.extract(PacketView::new(frame, Timestamp::default()))
.map(|e| e.key.shard_index(n_shards))
.unwrap_or(0)
}