use std::env;
use std::time::{Duration, Instant};
use netmap_rs::prelude::*;
fn usage(prog: &str) -> ! {
eprintln!(
"usage: {prog} -i <port> -f <tx|rx> [-n <packets>] [-s <size>] [-b <batch>]\n\
\n\
{prog} - packet generator/drainer for VALE ports and netmap pipes.\n\
\n\
options:\n\
\x20 -i <port> port to use, e.g. vale0:p0 or netmap:pipe{{name\n\
\x20 -f <dir> 'tx' to generate packets, 'rx' to drain a port\n\
\x20 -n <packets> number of packets to send (default: unlimited)\n\
\x20 -s <size> packet payload size in bytes (default: 64, min 14 for VALE)\n\
\x20 -b <batch> packets sent per TX sync (default: 64)"
);
std::process::exit(1);
}
fn opt(args: &[String], key: &str) -> Option<String> {
args.iter()
.enumerate()
.find(|(_, a)| a.as_str() == key)
.and_then(|(i, _)| args.get(i + 1))
.cloned()
}
fn main() -> Result<(), Error> {
let args: Vec<String> = env::args().skip(1).collect();
if args.is_empty() {
usage(&env::args().next().unwrap_or_else(|| "pkt-gen".into()));
}
let ifname = opt(&args, "-i").unwrap_or_else(|| usage("pkt-gen"));
let netmap_name = if ifname.starts_with("netmap:") {
ifname.clone()
} else {
format!("netmap:{}", ifname)
};
let dir = opt(&args, "-f").unwrap_or_else(|| usage("pkt-gen"));
let num_packets: Option<usize> = opt(&args, "-n").and_then(|v| v.parse().ok());
let size: usize = opt(&args, "-s").and_then(|v| v.parse().ok()).unwrap_or(64);
let batch: usize = opt(&args, "-b").and_then(|v| v.parse().ok()).unwrap_or(64);
let nm = NetmapBuilder::new(&netmap_name).build()?;
println!(
"{}: opened {} ({} TX rings, {} RX rings, {} slots/ring)",
dir,
netmap_name,
nm.num_tx_rings(),
nm.num_rx_rings(),
nm.tx_ring(0).map(|r| r.num_slots()).unwrap_or(0)
);
match dir.as_str() {
"tx" => generate(nm, size, batch, num_packets),
"rx" => drain(nm),
other => {
eprintln!("unknown direction '{other}'; use 'tx' or 'rx'");
usage("pkt-gen");
}
}
}
fn generate(
nm: Netmap,
size: usize,
batch: usize,
num_packets: Option<usize>,
) -> Result<(), Error> {
let mut tx = nm.tx_ring(0)?;
if size > tx.max_payload_size() {
return Err(Error::PacketTooLarge(size));
}
if size < 14 {
println!("note: frames shorter than 14 bytes are dropped by VALE; padding to 14");
}
let fmt = |n: usize| {
let b = n * size;
if b >= 1024 * 1024 * 1024 {
format!("{:.1} GiB", b as f64 / 1024.0 / 1024.0 / 1024.0)
} else if b >= 1024 * 1024 {
format!("{:.1} MiB", b as f64 / 1024.0 / 1024.0)
} else if b >= 1024 {
format!("{:.1} KiB", b as f64 / 1024.0)
} else {
format!("{b} B")
}
};
println!(
"sending up to {} frames of {} bytes in batches of {}...",
num_packets
.map(|n| n.to_string())
.unwrap_or_else(|| "unlimited".into()),
size,
batch
);
let mut sent: usize = 0;
let start = Instant::now();
let mut last_report = Instant::now();
let mut last_sent = 0usize;
loop {
if tx.has_free_slots() {
let count = batch.min(tx.num_slots() - 1);
let mut reservation = tx.reserve_batch(count)?;
for i in 0..count {
let seq = sent + i;
let mut payload = vec![0u8; size.max(14)];
payload.write_seq(seq, size);
reservation
.packet(i, payload.len())?
.copy_from_slice(&payload);
sent += 1;
}
reservation.commit();
tx.sync();
}
if let Some(n) = num_packets {
if sent >= n {
break;
}
}
if last_report.elapsed() >= Duration::from_secs(1) {
let dt = last_report.elapsed().as_secs_f64();
let ds = sent - last_sent;
println!(
"sent {} frames ({:.0}/s, {})",
sent,
ds as f64 / dt,
fmt(ds)
);
last_report = Instant::now();
last_sent = sent;
}
}
let dt = start.elapsed().as_secs_f64();
println!(
"sent {sent} frames ({}/s, {}) in {:.2}s",
sent as f64 / dt,
fmt(sent),
dt
);
Ok(())
}
fn drain(nm: Netmap) -> Result<(), Error> {
let mut rx = nm.rx_ring(0)?;
println!("draining port (Ctrl-C to stop)...");
let mut received: usize = 0;
let mut last_report = Instant::now();
let mut last_received = 0usize;
let mut seq_ok = true;
let mut last_seq: Option<usize> = None;
loop {
rx.sync();
while let Some(frame) = rx.recv() {
received += 1;
let data = frame.payload();
if !seq_ok {
continue;
}
match data.read_seq(data.len()) {
Some(seq) => {
if let Some(prev) = last_seq {
if seq != prev + 1 {
seq_ok = false;
eprintln!(
"sequence gap at frame #{received}: expected {}, got {seq}",
prev + 1
);
}
}
last_seq = Some(seq);
}
None => seq_ok = false, }
}
if last_report.elapsed() >= Duration::from_secs(1) {
let dt = last_report.elapsed().as_secs_f64();
let dr = received - last_received;
println!(
"received {} frames ({:.0}/s), seq ok: {}",
received,
dr as f64 / dt,
seq_ok
);
last_report = Instant::now();
last_received = received;
}
std::thread::sleep(Duration::from_millis(5));
}
}
trait PayloadSeq {
fn write_seq(&mut self, seq: usize, size: usize);
fn read_seq(&self, len: usize) -> Option<usize>;
}
impl PayloadSeq for [u8] {
fn write_seq(&mut self, seq: usize, size: usize) {
for (i, b) in seq.to_le_bytes().iter().enumerate() {
self[i] = *b;
}
for b in self.iter_mut().skip(8).take(size.saturating_sub(8)) {
*b = 0x5a;
}
}
fn read_seq(&self, len: usize) -> Option<usize> {
if len < 8 {
return None;
}
Some(usize::from_le_bytes(self[..8].try_into().ok()?))
}
}