use std::fmt;
#[derive(Debug)]
pub enum EbpfError {
Load(LoadError),
Attach(AttachError),
Map(MapError),
Bank(ManagerError),
KernelNotSupported(String),
}
impl fmt::Display for EbpfError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EbpfError::Load(e) => write!(f, "eBPF load error: {}", e),
EbpfError::Attach(e) => write!(f, "eBPF attach error: {}", e),
EbpfError::Map(e) => write!(f, "eBPF map error: {}", e),
EbpfError::Bank(e) => write!(f, "eBPF bank manager error: {}", e),
EbpfError::KernelNotSupported(msg) => {
write!(f, "Kernel not supported: {}", msg)
}
}
}
}
impl std::error::Error for EbpfError {}
impl From<LoadError> for EbpfError {
fn from(e: LoadError) -> Self {
EbpfError::Load(e)
}
}
impl From<AttachError> for EbpfError {
fn from(e: AttachError) -> Self {
EbpfError::Attach(e)
}
}
impl From<MapError> for EbpfError {
fn from(e: MapError) -> Self {
EbpfError::Map(e)
}
}
impl From<ManagerError> for EbpfError {
fn from(e: ManagerError) -> Self {
EbpfError::Bank(e)
}
}
#[derive(Debug)]
pub enum LoadError {
Libbpf(String),
NotFound(String),
EmptyBytecode,
VerificationFailed(String),
UnsupportedType(String),
}
impl fmt::Display for LoadError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LoadError::Libbpf(msg) => write!(f, "libbpf operation failed: {}", msg),
LoadError::NotFound(name) => write!(f, "Program not found: {}", name),
LoadError::EmptyBytecode => write!(f, "Bytecode is empty"),
LoadError::VerificationFailed(msg) => {
write!(f, "Verification failed: {}", msg)
}
LoadError::UnsupportedType(ty) => {
write!(f, "Unsupported program type: {}", ty)
}
}
}
}
impl std::error::Error for LoadError {}
#[derive(Debug)]
pub enum AttachError {
Libbpf(String),
InterfaceNotFound(String),
InvalidFlags(String),
InsufficientResources(String),
}
impl fmt::Display for AttachError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AttachError::Libbpf(msg) => write!(f, "libbpf attach failed: {}", msg),
AttachError::InterfaceNotFound(name) => {
write!(f, "Interface not found: {}", name)
}
AttachError::InvalidFlags(msg) => write!(f, "Invalid flags: {}", msg),
AttachError::InsufficientResources(msg) => {
write!(f, "Insufficient resources: {}", msg)
}
}
}
}
impl std::error::Error for AttachError {}
#[derive(Debug)]
pub enum MapError {
Libbpf(String),
NotFound(String),
SizeMismatch {
expected: usize,
actual: usize,
},
MapFull,
CounterOverflow,
UnsupportedOperation(String),
}
impl fmt::Display for MapError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MapError::Libbpf(msg) => write!(f, "libbpf map operation failed: {}", msg),
MapError::NotFound(name) => write!(f, "Map not found: {}", name),
MapError::SizeMismatch { expected, actual } => {
write!(
f,
"Size mismatch: expected {}, got {}",
expected, actual
)
}
MapError::MapFull => write!(f, "Map is full"),
MapError::CounterOverflow => write!(f, "Counter arithmetic overflow (fail-closed)"),
MapError::UnsupportedOperation(op) => {
write!(f, "Unsupported operation: {}", op)
}
}
}
}
impl std::error::Error for MapError {}
#[derive(Debug)]
pub enum ManagerError {
InvalidState,
Load(String),
SwitchFailed(String),
ProgramMismatch,
NotXdpMain(String),
LockError(String),
}
impl fmt::Display for ManagerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ManagerError::InvalidState => write!(f, "Invalid bank state"),
ManagerError::Load(msg) => write!(f, "Load error: {}", msg),
ManagerError::SwitchFailed(msg) => write!(f, "Switch failed: {}", msg),
ManagerError::ProgramMismatch => write!(f, "Program mismatch"),
ManagerError::NotXdpMain(msg) => {
write!(f, "Active program is not xdp_main (filter chain bypass): {}", msg)
}
ManagerError::LockError(msg) => write!(f, "Lock error: {}", msg),
}
}
}
impl std::error::Error for ManagerError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ebpf_error_display_load() {
let err = EbpfError::Load(LoadError::Libbpf("test error".to_string()));
assert!(format!("{}", err).contains("eBPF load error"));
assert!(format!("{}", err).contains("test error"));
}
#[test]
fn test_ebpf_error_display_attach() {
let err = EbpfError::Attach(AttachError::Libbpf("attach failed".to_string()));
assert!(format!("{}", err).contains("eBPF attach error"));
assert!(format!("{}", err).contains("attach failed"));
}
#[test]
fn test_ebpf_error_display_map() {
let err = EbpfError::Map(MapError::NotFound("test_map".to_string()));
assert!(format!("{}", err).contains("eBPF map error"));
assert!(format!("{}", err).contains("test_map"));
}
#[test]
fn test_ebpf_error_display_bank() {
let err = EbpfError::Bank(ManagerError::InvalidState);
assert!(format!("{}", err).contains("eBPF bank manager error"));
}
#[test]
fn test_ebpf_error_display_kernel() {
let err = EbpfError::KernelNotSupported("AF_XDP not available".to_string());
assert!(format!("{}", err).contains("Kernel not supported"));
assert!(format!("{}", err).contains("AF_XDP not available"));
}
#[test]
fn test_load_error_variants() {
let e1 = LoadError::Libbpf("libbpf fail".to_string());
assert!(format!("{}", e1).contains("libbpf operation failed"));
let e2 = LoadError::NotFound("my_prog".to_string());
assert!(format!("{}", e2).contains("Program not found"));
assert!(format!("{}", e2).contains("my_prog"));
let e3 = LoadError::EmptyBytecode;
assert!(format!("{}", e3).contains("Bytecode is empty"));
let e4 = LoadError::VerificationFailed("verifier error".to_string());
assert!(format!("{}", e4).contains("Verification failed"));
let e5 = LoadError::UnsupportedType("kprobe".to_string());
assert!(format!("{}", e5).contains("Unsupported program type"));
}
#[test]
fn test_attach_error_variants() {
let e1 = AttachError::Libbpf("libbpf attach fail".to_string());
assert!(format!("{}", e1).contains("libbpf attach failed"));
let e2 = AttachError::InterfaceNotFound("eth0".to_string());
assert!(format!("{}", e2).contains("Interface not found"));
assert!(format!("{}", e2).contains("eth0"));
let e3 = AttachError::InvalidFlags("bad flags".to_string());
assert!(format!("{}", e3).contains("Invalid flags"));
let e4 = AttachError::InsufficientResources("out of memory".to_string());
assert!(format!("{}", e4).contains("Insufficient resources"));
}
#[test]
fn test_map_error_variants() {
let e1 = MapError::Libbpf("map op fail".to_string());
assert!(format!("{}", e1).contains("libbpf map operation failed"));
let e2 = MapError::NotFound("xsk_map".to_string());
assert!(format!("{}", e2).contains("Map not found"));
assert!(format!("{}", e2).contains("xsk_map"));
let e3 = MapError::SizeMismatch { expected: 8, actual: 4 };
let msg = format!("{}", e3);
assert!(msg.contains("Size mismatch"));
assert!(msg.contains("8"));
assert!(msg.contains("4"));
let e4 = MapError::MapFull;
assert!(format!("{}", e4).contains("Map is full"));
let e5 = MapError::UnsupportedOperation("delete".to_string());
assert!(format!("{}", e5).contains("Unsupported operation"));
assert!(format!("{}", e5).contains("delete"));
}
#[test]
fn test_manager_error_variants() {
let e1 = ManagerError::InvalidState;
assert!(format!("{}", e1).contains("Invalid bank state"));
let e2 = ManagerError::Load("load fail".to_string());
assert!(format!("{}", e2).contains("Load error"));
let e3 = ManagerError::SwitchFailed("switch fail".to_string());
assert!(format!("{}", e3).contains("Switch failed"));
let e4 = ManagerError::ProgramMismatch;
assert!(format!("{}", e4).contains("Program mismatch"));
let e5 = ManagerError::NotXdpMain("Bank A is Redirect".to_string());
assert!(format!("{}", e5).contains("not xdp_main"));
let e6 = ManagerError::LockError("lock poisoned".to_string());
assert!(format!("{}", e6).contains("Lock error"));
}
#[test]
fn test_error_from_conversions() {
let load_err = LoadError::EmptyBytecode;
let ebpf_err: EbpfError = load_err.into();
assert!(matches!(ebpf_err, EbpfError::Load(_)));
let attach_err = AttachError::InterfaceNotFound("eth0".to_string());
let ebpf_err: EbpfError = attach_err.into();
assert!(matches!(ebpf_err, EbpfError::Attach(_)));
let map_err = MapError::MapFull;
let ebpf_err: EbpfError = map_err.into();
assert!(matches!(ebpf_err, EbpfError::Map(_)));
let mgr_err = ManagerError::InvalidState;
let ebpf_err: EbpfError = mgr_err.into();
assert!(matches!(ebpf_err, EbpfError::Bank(_)));
}
#[test]
fn test_errors_are_debug() {
let e = EbpfError::KernelNotSupported("test".to_string());
let debug_str = format!("{:?}", e);
assert!(!debug_str.is_empty());
let e = LoadError::EmptyBytecode;
let debug_str = format!("{:?}", e);
assert!(!debug_str.is_empty());
let e = AttachError::InvalidFlags("test".to_string());
let debug_str = format!("{:?}", e);
assert!(!debug_str.is_empty());
let e = MapError::SizeMismatch { expected: 4, actual: 8 };
let debug_str = format!("{:?}", e);
assert!(!debug_str.is_empty());
let e = ManagerError::LockError("test".to_string());
let debug_str = format!("{:?}", e);
assert!(!debug_str.is_empty());
}
#[test]
fn test_error_trait_implementations() {
fn assert_error<T: std::error::Error>() {}
assert_error::<EbpfError>();
assert_error::<LoadError>();
assert_error::<AttachError>();
assert_error::<MapError>();
assert_error::<ManagerError>();
}
#[test]
fn test_load_error_debug() {
let e = LoadError::NotFound("prog".to_string());
let debug_str = format!("{:?}", e);
assert!(!debug_str.is_empty());
}
#[test]
fn test_attach_error_debug() {
let e = AttachError::Libbpf("fail".to_string());
let debug_str = format!("{:?}", e);
assert!(!debug_str.is_empty());
}
#[test]
fn test_map_error_display_match() {
let e1 = MapError::MapFull;
let e2 = MapError::MapFull;
assert_eq!(format!("{}", e1), format!("{}", e2));
}
#[test]
fn test_manager_error_debug() {
let e = ManagerError::SwitchFailed("test".to_string());
let debug_str = format!("{:?}", e);
assert!(!debug_str.is_empty());
}
}