use crate::program::LoadedProgram;
use crate::result::HookResult;
#[repr(usize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HookPoint {
PreIngress = 0,
PreDispatch = 1,
AnnounceReceived = 2,
PathUpdated = 3,
AnnounceRetransmit = 4,
LinkRequestReceived = 5,
LinkEstablished = 6,
LinkClosed = 7,
InterfaceUp = 8,
InterfaceDown = 9,
InterfaceConfigChanged = 10,
SendOnInterface = 11,
BroadcastOnAllInterfaces = 12,
DeliverLocal = 13,
TunnelSynthesize = 14,
Tick = 15,
}
impl HookPoint {
pub const COUNT: usize = 16;
}
pub enum HookContext<'a> {
Packet {
ctx: &'a crate::context::PacketContext,
raw: &'a [u8],
},
Interface {
interface_id: u64,
},
Tick,
Announce {
destination_hash: [u8; 16],
hops: u8,
interface_id: u64,
},
Link {
link_id: [u8; 16],
interface_id: u64,
},
}
pub type HookFn = fn(&HookSlot, &HookContext) -> Option<HookResult>;
pub fn hook_noop(_slot: &HookSlot, _ctx: &HookContext) -> Option<HookResult> {
None
}
fn hook_has_programs(_slot: &HookSlot, _ctx: &HookContext) -> Option<HookResult> {
None
}
pub struct HookSlot {
pub programs: Vec<LoadedProgram>,
pub runner: HookFn,
}
impl HookSlot {
pub fn update_runner(&mut self) {
if self.programs.is_empty() {
self.runner = hook_noop;
} else {
self.runner = hook_has_programs;
}
}
pub fn attach(&mut self, program: LoadedProgram) {
self.programs.push(program);
self.programs.sort_by(|a, b| b.priority.cmp(&a.priority));
self.update_runner();
}
pub fn detach(&mut self, name: &str) -> Option<LoadedProgram> {
let pos = self.programs.iter().position(|p| p.name == name)?;
let prog = self.programs.remove(pos);
self.update_runner();
Some(prog)
}
pub fn has_programs(&self) -> bool {
self.runner as *const () as usize != hook_noop as *const () as usize
}
}
pub fn create_hook_slots() -> [HookSlot; HookPoint::COUNT] {
std::array::from_fn(|_| HookSlot {
programs: Vec::new(),
runner: hook_noop,
})
}
#[macro_export]
macro_rules! run_hook {
($driver:expr, $point:expr, $ctx:expr) => {{
($driver.hook_slots[$point as usize].runner)(&$driver.hook_slots[$point as usize], &$ctx)
}};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::result::Verdict;
#[test]
fn test_hook_point_count() {
assert_eq!(HookPoint::COUNT, 16);
assert_eq!(HookPoint::Tick as usize, 15);
}
#[test]
fn test_hook_noop_returns_none() {
let slot = HookSlot {
programs: Vec::new(),
runner: hook_noop,
};
let ctx = HookContext::Tick;
assert!(hook_noop(&slot, &ctx).is_none());
}
#[test]
fn test_create_hook_slots() {
let slots = create_hook_slots();
assert_eq!(slots.len(), HookPoint::COUNT);
for slot in &slots {
assert!(slot.programs.is_empty());
let ctx = HookContext::Tick;
assert!((slot.runner)(slot, &ctx).is_none());
}
}
#[test]
fn test_run_hook_macro() {
struct FakeDriver {
hook_slots: [HookSlot; HookPoint::COUNT],
}
let driver = FakeDriver {
hook_slots: create_hook_slots(),
};
let ctx = HookContext::Tick;
let result = run_hook!(driver, HookPoint::Tick, ctx);
assert!(result.is_none());
let ctx2 = HookContext::Interface { interface_id: 42 };
let result2 = run_hook!(driver, HookPoint::InterfaceUp, ctx2);
assert!(result2.is_none());
}
#[test]
fn test_verdict_values() {
assert_eq!(Verdict::Continue as u32, 0);
assert_eq!(Verdict::Drop as u32, 1);
assert_eq!(Verdict::Modify as u32, 2);
assert_eq!(Verdict::Halt as u32, 3);
}
#[test]
fn test_hook_result_helpers() {
let drop_r = HookResult::drop_result();
assert!(drop_r.is_drop());
assert_eq!(drop_r.verdict, Verdict::Drop as u32);
let cont_r = HookResult::continue_result();
assert!(!cont_r.is_drop());
assert_eq!(cont_r.verdict, Verdict::Continue as u32);
assert_eq!(cont_r.modified_data_len, 0);
assert_eq!(cont_r.inject_actions_count, 0);
assert_eq!(cont_r.log_len, 0);
}
}