use crate::spec::{BpfMapSpec, BpfProgramSpec};
#[derive(Debug, thiserror::Error)]
pub enum RuntimeError {
#[error("operation not supported by this runtime backend")]
Unsupported,
#[error("kernel rejected program at load: {0}")]
VerifierRejected(String),
#[error("attach failed: {0}")]
AttachFailed(String),
#[error("map operation failed: {0}")]
MapError(String),
#[error("io: {0}")]
Io(String),
}
pub struct LoadedProgram {
pub name: String,
pub token: u64,
}
pub trait BpfRuntime: Send + Sync {
fn load_program(&mut self, spec: &BpfProgramSpec) -> Result<LoadedProgram, RuntimeError>;
fn attach_program(&mut self, prog: &LoadedProgram) -> Result<(), RuntimeError>;
fn detach_program(&mut self, prog: LoadedProgram) -> Result<(), RuntimeError>;
fn create_map(&mut self, spec: &BpfMapSpec) -> Result<(), RuntimeError>;
}
#[derive(Debug, Default)]
pub struct SimulatedRuntime {
pub loaded_programs: Vec<BpfProgramSpec>,
pub attached_programs: Vec<String>,
pub created_maps: Vec<BpfMapSpec>,
next_token: u64,
}
impl SimulatedRuntime {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl BpfRuntime for SimulatedRuntime {
fn load_program(&mut self, spec: &BpfProgramSpec) -> Result<LoadedProgram, RuntimeError> {
self.loaded_programs.push(spec.clone());
self.next_token += 1;
Ok(LoadedProgram {
name: spec.name.clone(),
token: self.next_token,
})
}
fn attach_program(&mut self, prog: &LoadedProgram) -> Result<(), RuntimeError> {
self.attached_programs.push(prog.name.clone());
Ok(())
}
fn detach_program(&mut self, prog: LoadedProgram) -> Result<(), RuntimeError> {
self.attached_programs.retain(|n| n != &prog.name);
Ok(())
}
fn create_map(&mut self, spec: &BpfMapSpec) -> Result<(), RuntimeError> {
self.created_maps.push(spec.clone());
Ok(())
}
}
#[cfg(feature = "aya-runtime")]
mod aya_backend {
use super::*;
pub struct AyaRuntime;
impl BpfRuntime for AyaRuntime {
fn load_program(&mut self, _spec: &BpfProgramSpec) -> Result<LoadedProgram, RuntimeError> {
Err(RuntimeError::Unsupported)
}
fn attach_program(&mut self, _prog: &LoadedProgram) -> Result<(), RuntimeError> {
Err(RuntimeError::Unsupported)
}
fn detach_program(&mut self, _prog: LoadedProgram) -> Result<(), RuntimeError> {
Err(RuntimeError::Unsupported)
}
fn create_map(&mut self, _spec: &BpfMapSpec) -> Result<(), RuntimeError> {
Err(RuntimeError::Unsupported)
}
}
}
#[cfg(feature = "aya-runtime")]
pub use aya_backend::AyaRuntime;
#[cfg(test)]
mod tests {
use super::*;
use crate::spec::{BpfAttachPoint, BpfMapKind, BpfProgramKind};
fn sample_program() -> BpfProgramSpec {
BpfProgramSpec {
name: "drop_syn".into(),
kind: BpfProgramKind::Xdp,
attach: BpfAttachPoint {
target: "eth0".into(),
direction: None,
},
source: "bpf/drop_syn.rs".into(),
license: "GPL".into(),
pin_path: None,
uses_maps: vec![],
}
}
#[test]
fn simulated_runtime_records_lifecycle() {
let mut rt = SimulatedRuntime::new();
let map = BpfMapSpec {
name: "syn_counter".into(),
kind: BpfMapKind::PerCpuArray,
key_size: 4,
value_size: 8,
max_entries: 1,
pin_path: None,
};
rt.create_map(&map).unwrap();
assert_eq!(rt.created_maps.len(), 1);
let spec = sample_program();
let prog = rt.load_program(&spec).unwrap();
assert_eq!(prog.name, "drop_syn");
assert_eq!(rt.loaded_programs.len(), 1);
rt.attach_program(&prog).unwrap();
assert_eq!(rt.attached_programs, vec!["drop_syn"]);
rt.detach_program(prog).unwrap();
assert!(rt.attached_programs.is_empty());
}
}