Byte-frequency prefilter for read-heavy scanning pipelines, with optional eBPF offload.
ebpfsieve slides a fixed-size window over byte streams and reports candidate windows where all required byte-count thresholds are met. The idea is to cheaply reject data before handing it to a more expensive verifier.
- Userspace filtering (pure Rust, no dependencies on kernel features).
- Lazy iteration:
matching_windows_iteryields matches one at a time without allocating aVec. - Chunked readers, attach the filter to any
Readimpl and get per-chunk candidate ranges with automatic carry-over across chunk boundaries. - Optional eBPF, on Linux with the right features enabled, compile and load classic BPF socket filters or
aya-basedfentry/vfs_readprobes.
use ebpfsieve::{ByteFrequencyFilter, ByteThreshold};
let filter = ByteFrequencyFilter::new([
ByteThreshold::new(b'a', 3),
])?
.with_window_size(5)?;
let matches = filter.matching_windows(b"xyzaaaxyz");
assert_eq!(matches[0].offset, 1); // "yzaaa"
# Ok::<(), ebpfsieve::Error>(())A ByteFrequencyFilter is built from one or more ByteThreshold values. A window matches when every threshold is satisfied. Counts are maintained in a u16 histogram with saturating arithmetic, so very long windows are safe from overflow.
use ebpfsieve::{ByteFrequencyFilter, ByteThreshold};
let filter = ByteFrequencyFilter::new([ByteThreshold::new(b'x', 2)])?
.with_window_size(64)?
.with_chunk_size(4096)?;
// Any file works; here we write a small one to scan.
let path = std::env::temp_dir().join("ebpfsieve_readme_scan.bin");
std::fs::write(&path, b"aaaxyz xray xylophone".repeat(100))?;
let mut file = std::fs::File::open(&path)?;
let matches = filter.scan_file(&mut file, Some(1_000_000))?;
assert!(!matches.is_empty());
std::fs::remove_file(&path)?;
# Ok::<(), Box<dyn std::error::Error>>(())For internet-scale scanning, avoid collecting all matches into a Vec:
use ebpfsieve::{ByteFrequencyFilter, ByteThreshold};
let filter = ByteFrequencyFilter::new([ByteThreshold::new(b'a', 1)])?;
let mut iter = filter.matching_windows_iter(b"banana");
if let Some(first) = iter.next() {
println!("first match at offset {}", first.offset);
}
# Ok::<(), ebpfsieve::Error>(())serde(load filter rules from TOML withfrom_toml_str/from_toml_file).socket-bpf(compile and load classicBPF_PROG_TYPE_SOCKET_FILTERprograms viaebpfkit(Linux only)).kernel-bpf(loadaya-basedfentry/vfs_readprobes (Linux ≥ 5.8, BTF, root required)).
All fallible operations return ebpfsieve::Result<T>. Errors carry actionable messages, for example:
invalid filter configuration: window_size cannot be zero. Fix: provide a window_size of at least 1
MIT