#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BpfInsn {
pub code: u16,
pub jt: u8,
pub jf: u8,
pub k: u32,
}
impl From<BpfInsn> for libc::sock_filter {
fn from(insn: BpfInsn) -> Self {
libc::sock_filter {
code: insn.code,
jt: insn.jt,
jf: insn.jf,
k: insn.k,
}
}
}
impl From<libc::sock_filter> for BpfInsn {
fn from(sf: libc::sock_filter) -> Self {
Self {
code: sf.code,
jt: sf.jt,
jf: sf.jf,
k: sf.k,
}
}
}
#[derive(Debug, Clone)]
pub struct BpfFilter {
instructions: Vec<BpfInsn>,
}
impl BpfFilter {
pub const MAX_INSNS: usize = 4096;
pub fn new(instructions: Vec<BpfInsn>) -> Result<Self, BuildError> {
if instructions.len() > Self::MAX_INSNS {
return Err(BuildError::TooManyInstructions {
count: instructions.len(),
});
}
Ok(Self { instructions })
}
pub fn builder() -> super::bpf_builder::BpfFilterBuilder {
super::bpf_builder::BpfFilterBuilder::new()
}
pub fn instructions(&self) -> &[BpfInsn] {
&self.instructions
}
pub fn into_instructions(self) -> Vec<BpfInsn> {
self.instructions
}
pub fn len(&self) -> usize {
self.instructions.len()
}
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum BuildError {
#[error("conflicting fragments: {a} and {b} can't both match the same packet")]
ConflictingProtocols {
a: &'static str,
b: &'static str,
},
#[error(
"filter exceeds {} instructions (kernel BPF_MAXINSNS limit), got {count}",
BpfFilter::MAX_INSNS
)]
TooManyInstructions {
count: usize,
},
#[error("port out of range: {0}")]
PortOutOfRange(u32),
#[error("invalid IP prefix length: {0} (max 32 for IPv4, 128 for IPv6)")]
InvalidPrefix(u8),
#[error("ipv6 + extension headers not supported by the typed builder")]
Ipv6ExtHeader,
#[error("OR of zero branches")]
EmptyOr,
#[error("forward jump distance too large for cBPF (>255 instructions)")]
JumpTooFar,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bpf_insn_matches_sock_filter() {
assert_eq!(
std::mem::size_of::<BpfInsn>(),
std::mem::size_of::<libc::sock_filter>()
);
}
#[test]
fn bpf_insn_roundtrip() {
let insn = BpfInsn {
code: 0x28,
jt: 0,
jf: 0,
k: 12,
};
let sf: libc::sock_filter = insn.into();
let back: BpfInsn = sf.into();
assert_eq!(insn, back);
}
#[test]
fn bpf_filter_accessors() {
let insns = vec![
BpfInsn {
code: 0x28,
jt: 0,
jf: 0,
k: 12,
},
BpfInsn {
code: 0x06,
jt: 0,
jf: 0,
k: 0xFFFF,
},
];
let filter = BpfFilter::new(insns.clone()).unwrap();
assert_eq!(filter.len(), 2);
assert!(!filter.is_empty());
assert_eq!(filter.instructions(), &insns);
}
#[test]
fn bpf_filter_rejects_oversize() {
let oversize = vec![
BpfInsn {
code: 0x06,
jt: 0,
jf: 0,
k: 0xFFFF,
};
BpfFilter::MAX_INSNS + 1
];
let err = BpfFilter::new(oversize).unwrap_err();
assert!(
matches!(err, BuildError::TooManyInstructions { count } if count == BpfFilter::MAX_INSNS + 1)
);
}
}