Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
on: [pull_request]
name: benchmark pull requests
jobs:
runBenchmark:
name: run benchmark
runs-on: ubuntu-latest
steps:
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libpcap-dev
- uses: actions/checkout@v3
- uses: boa-dev/criterion-compare-action@v3
with:
features: "resolve"
branchName: ${{ github.base_ref }}
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 12 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pktstrings"
version = "1.4.1"
version = "1.5.0"
edition = "2021"
rust-version = "1.60"
authors = ["Pete Wicken <petewicken@gmail.com>"]
Expand All @@ -13,10 +13,10 @@ keywords = ["packet-analyzer", "pcap", "sniffing", "packet"]
categories = ["command-line-utilities", "network-programming"]

[dependencies]
clap = {version = "4", features = ["derive"]}
pcap = "1"
clap = { version = "4", features = ["derive"] }
pcap = "2"
colored = "2"
dns-lookup = {version = "2", optional = true}
dns-lookup = { version = "2", optional = true }
cfg-if = "1"
libc = "0"
regex = "1"
Expand Down Expand Up @@ -51,3 +51,11 @@ harness = false
[[bench]]
name = "strings"
harness = false

[[bench]]
name = "getfield_func_vs_macro"
harness = false

[[bench]]
name = "buffer_strings_vs_print_strings"
harness = false
160 changes: 160 additions & 0 deletions benches/buffer_strings_vs_print_strings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use colored::Colorize;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use pcap::{Activated, Capture};
use std::path::Path;

use pktstrings::net;

const PCAP: &str = "./benches/data/http.pcap";

pub fn print_strings<T: Activated>(
cap: &mut Capture<T>,
len: &usize,
resolver: &mut Option<Box<net::Resolver>>,
block_print: &bool,
) {
let mut pkt_count = 0;

while let Ok(pkt) = cap.next_packet() {
pkt_count += 1;

let mut printed = false;
let mut chars = 0;
let mut partial = String::new();
let mut pkt_str: Option<String> = None;
for byte in pkt.data {
let c = *byte as char;
// TODO: other encodings
if c.is_ascii() && !c.is_ascii_control() {
chars += 1;
if chars > *len {
print!("{}", c);
} else {
partial.push(c);
if chars == *len {
if pkt_str.is_none() {
if let Some(ref mut r) = resolver {
let mut pktsum = net::PacketSummary::from_packet(&pkt, Some(r));
pkt_str = Some(pktsum.formatted());
} else {
let mut pktsum = net::PacketSummary::from_packet(&pkt, None);
pkt_str = Some(pktsum.formatted());
}
}

let idx = pkt_count.to_string().blue();
if !printed || !*block_print {
if let Some(ref mut pkt_str) = pkt_str {
print!("[{idx}]{pkt_str}: ");
printed = true;
if *block_print {
println!();
}
}
}
print!("{partial}");
}
}
} else {
if chars >= *len {
println!();
}
chars = 0;
partial.clear();
}
}
if chars >= *len {
println!();
}
}
}

pub fn buffer_strings<T: Activated>(
cap: &mut Capture<T>,
len: &usize,
resolver: &mut Option<Box<net::Resolver>>,
block_print: &bool,
) {
let mut pkt_count = 0;

while let Ok(pkt) = cap.next_packet() {
pkt_count += 1;

let mut found = false;
let mut chars = 0;
let mut display_string = String::new();
let mut partial = String::new();
let mut pkt_str: Option<String> = None;
for byte in pkt.data {
let c = *byte as char;
// TODO: other encodings
if c.is_ascii() && !c.is_ascii_control() {
chars += 1;
if chars > *len {
display_string.push(c);
} else {
partial.push(c);
if chars == *len {
if pkt_str.is_none() {
if let Some(ref mut r) = resolver {
let mut pktsum = net::PacketSummary::from_packet(&pkt, Some(r));
pkt_str = Some(pktsum.formatted());
} else {
let mut pktsum = net::PacketSummary::from_packet(&pkt, None);
pkt_str = Some(pktsum.formatted());
}
}

let idx = pkt_count.to_string().blue();
if !found || !*block_print {
if let Some(ref mut pkt_str) = pkt_str {
display_string.push_str(format!("[{idx}]{pkt_str}: ").as_str());
found = true;
if *block_print {
display_string.push('\n');
}
}
}
display_string.push_str(partial.as_str());
partial.clear();
}
}
} else {
// print when we encounter non-ascii
if chars >= *len {
println!("{}", display_string);
} else {
partial.clear();
display_string.clear()
}
chars = 0;
}
}
// print if we hit end of packet but havent dumped buffer yet
if chars >= *len {
println!("{}", display_string);
}
}
}

fn dump_strings_benches(c: &mut Criterion) {
let mut pktstring_group = Criterion::benchmark_group(c, "String Dump Comparisons");
pktstring_group.bench_function(BenchmarkId::new("print_strings", "http_pcap"), |b| {
let filepath = Path::new(PCAP);
let mut cap = Capture::from_file(filepath).unwrap();
b.iter(|| {
print_strings(&mut cap, &7, &mut None, &false);
});
});
pktstring_group.bench_function(BenchmarkId::new("buffer_strings", "http_pcap"), |b| {
let filepath = Path::new(PCAP);
let mut cap = Capture::from_file(filepath).unwrap();
b.iter(|| {
buffer_strings(&mut cap, &7, &mut None, &false);
});
});
pktstring_group.finish();
}

criterion_group!(benches, dump_strings_benches);
criterion_main!(benches);
Binary file added benches/data/http.pcap
Binary file not shown.
107 changes: 107 additions & 0 deletions benches/getfield_func_vs_macro.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};

mod meta;

pub fn get_field(data: &[u8], offset: usize, bytelen: usize) -> Result<u128, &str> {
assert!(bytelen <= 16, "Length must be less than 16 bytes");
if (data.len() - offset) < bytelen {
return Err("Data after offset is shorter than bytelen");
}
let mut addr: u128 = 0;
for i in 0..(bytelen) {
addr |= (data[offset + i] as u128) << (((bytelen - 1) * 8) - (i * 8))
}
Ok(addr)
}

macro_rules! m_get_field {
($data:expr, $offset:expr, $bits:expr, $uint_type:ty) => {{
let uint_sz = std::mem::size_of::<$uint_type>();
let bytes = ($bits / 8) as $uint_type;
let bytes_round = if bytes > 1 { bytes } else { 1 };
assert!(
($bits / 8) <= uint_sz,
"Attempt to read more bits than possible in type"
);
if ($data.len() - $offset) < ($bits / 8) as usize {
Err("Data after offset is shorter than uint_type")
} else {
let mut val: $uint_type = 0;
for i in 0..(bytes_round) {
let idx = ($offset as $uint_type + i) as usize;
let data_byte = $data[idx] as $uint_type;
val |= data_byte << ((bytes_round - 1) * 8) - (i * 8);
}
val &= ((1u128 << $bits as u128) - 1) as $uint_type;
Ok(val)
}
}};
}

fn get_field_benches(c: &mut Criterion) {
let mut field_group = c.benchmark_group("Byte Field Conversion Comparisons");
for bytelen in 0..16 {
field_group.throughput(Throughput::Bytes(bytelen as u64));

field_group.bench_with_input(
BenchmarkId::new("func_field_select", bytelen),
&bytelen,
|b, bytelen| {
b.iter(|| get_field(meta::DATA, 0, *bytelen));
},
);

match bytelen {
0..=1 => {
field_group.bench_with_input(
BenchmarkId::new("macro_field_select", bytelen),
&bytelen,
|b, bytelen| {
b.iter(|| m_get_field!(meta::DATA, 0, *bytelen, u8));
},
);
}
2 => {
field_group.bench_with_input(
BenchmarkId::new("macro_field_select", bytelen),
&bytelen,
|b, bytelen| {
b.iter(|| m_get_field!(meta::DATA, 0, *bytelen, u16));
},
);
}
3..=4 => {
field_group.bench_with_input(
BenchmarkId::new("macro_field_select", bytelen),
&bytelen,
|b, bytelen| {
b.iter(|| m_get_field!(meta::DATA, 0, *bytelen, u32));
},
);
}
5..=8 => {
field_group.bench_with_input(
BenchmarkId::new("macro_field_select", bytelen),
&bytelen,
|b, bytelen| {
b.iter(|| m_get_field!(meta::DATA, 0, *bytelen, u64));
},
);
}
9..=16 => {
field_group.bench_with_input(
BenchmarkId::new("macro_field_select", bytelen),
&bytelen,
|b, bytelen| {
b.iter(|| m_get_field!(meta::DATA, 0, *bytelen, u128));
},
);
}
_ => {}
}
}
field_group.finish();
}

criterion_group!(benches, get_field_benches);
criterion_main!(benches);
2 changes: 1 addition & 1 deletion benches/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn net(c: &mut Criterion) {
BenchmarkId::new("get_field", bytelen),
&bytelen,
|b, bytelen| {
b.iter(|| net::get_field(meta::DATA, 0, *bytelen));
b.iter(|| net::get_field!(meta::DATA, 0, *bytelen, u128));
},
);
}
Expand Down
21 changes: 9 additions & 12 deletions src/bin/pktstrings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ struct Cli {
interface: Option<String>,
}

/// Applys the provided BPF filter to the capture file.
/// Applies the provided BPF filter to the capture file.
fn apply_filter<T: Activated>(cap: &mut Capture<T>, bpf: &Option<String>, cmd: &mut clap::Command) {
if let Some(bpf) = bpf {
match cap.filter(bpf, true) {
Expand All @@ -72,18 +72,15 @@ fn apply_filter<T: Activated>(cap: &mut Capture<T>, bpf: &Option<String>, cmd: &

/// Print to stdout the available network devices.
fn list_devices() {
Device::list()
.unwrap_or_default()
.iter()
.for_each(|dev| {
let mut dev = dev.name.to_string().normal();
if let Some(default_dev) = Device::lookup().ok().flatten() {
if *dev == *default_dev.name {
dev = dev.bold();
}
Device::list().unwrap_or_default().iter().for_each(|dev| {
let mut dev = dev.name.to_string().normal();
if let Some(default_dev) = Device::lookup().ok().flatten() {
if *dev == *default_dev.name {
dev = dev.bold();
}
print!("{dev}\t");
});
}
print!("{dev}\t");
});
println!();
}

Expand Down
Loading