1use crate::program::LoadedProgram;
2use crate::result::HookResult;
3
4#[repr(usize)]
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum HookPoint {
8 PreIngress = 0,
10 PreDispatch = 1,
11
12 AnnounceReceived = 2,
14 PathUpdated = 3,
15 AnnounceRetransmit = 4,
16
17 LinkRequestReceived = 5,
19 LinkEstablished = 6,
20 LinkClosed = 7,
21
22 InterfaceUp = 8,
24 InterfaceDown = 9,
25 InterfaceConfigChanged = 10,
26
27 BackbonePeerConnected = 11,
29 BackbonePeerDisconnected = 12,
30 BackbonePeerIdleTimeout = 13,
31 BackbonePeerWriteStall = 14,
32 BackbonePeerPenalty = 15,
33
34 SendOnInterface = 16,
36 BroadcastOnAllInterfaces = 17,
37 DeliverLocal = 18,
38 TunnelSynthesize = 19,
39
40 Tick = 20,
42}
43
44impl HookPoint {
45 pub const COUNT: usize = 21;
47}
48
49pub enum HookContext<'a> {
54 Packet {
55 ctx: &'a crate::context::PacketContext,
56 raw: &'a [u8],
57 },
58 Interface {
59 interface_id: u64,
60 },
61 Tick,
62 Announce {
63 destination_hash: [u8; 16],
64 hops: u8,
65 interface_id: u64,
66 },
67 Link {
68 link_id: [u8; 16],
69 interface_id: u64,
70 },
71 BackbonePeer {
72 server_interface_id: u64,
73 peer_interface_id: Option<u64>,
74 peer_ip: std::net::IpAddr,
75 peer_port: u16,
76 connected_for: std::time::Duration,
77 had_received_data: bool,
78 penalty_level: u8,
79 blacklist_for: std::time::Duration,
80 },
81}
82
83pub type HookFn = fn(&HookSlot, &HookContext) -> Option<HookResult>;
85
86pub fn hook_noop(_slot: &HookSlot, _ctx: &HookContext) -> Option<HookResult> {
90 None
91}
92
93fn hook_has_programs(_slot: &HookSlot, _ctx: &HookContext) -> Option<HookResult> {
98 None
101}
102
103pub struct HookSlot {
105 pub programs: Vec<LoadedProgram>,
106 pub runner: HookFn,
109}
110
111impl HookSlot {
112 pub fn update_runner(&mut self) {
114 if self.programs.is_empty() {
115 self.runner = hook_noop;
116 } else {
117 self.runner = hook_has_programs;
118 }
119 }
120
121 pub fn attach(&mut self, program: LoadedProgram) {
123 self.programs.push(program);
124 self.programs.sort_by(|a, b| b.priority.cmp(&a.priority));
125 self.update_runner();
126 }
127
128 pub fn detach(&mut self, name: &str) -> Option<LoadedProgram> {
130 let pos = self.programs.iter().position(|p| p.name == name)?;
131 let prog = self.programs.remove(pos);
132 self.update_runner();
133 Some(prog)
134 }
135
136 pub fn has_programs(&self) -> bool {
138 !std::ptr::eq(self.runner as *const (), hook_noop as *const ())
139 }
140}
141
142pub fn create_hook_slots() -> [HookSlot; HookPoint::COUNT] {
145 std::array::from_fn(|_| HookSlot {
146 programs: Vec::new(),
147 runner: hook_noop,
148 })
149}
150
151#[macro_export]
160macro_rules! run_hook {
161 ($driver:expr, $point:expr, $ctx:expr) => {{
162 ($driver.hook_slots[$point as usize].runner)(&$driver.hook_slots[$point as usize], &$ctx)
163 }};
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::result::Verdict;
170
171 #[test]
172 fn test_hook_point_count() {
173 assert_eq!(HookPoint::COUNT, 21);
174 assert_eq!(HookPoint::Tick as usize, 20);
176 }
177
178 #[test]
179 fn test_hook_noop_returns_none() {
180 let slot = HookSlot {
181 programs: Vec::new(),
182 runner: hook_noop,
183 };
184 let ctx = HookContext::Tick;
185 assert!(hook_noop(&slot, &ctx).is_none());
186 }
187
188 #[test]
189 fn test_create_hook_slots() {
190 let slots = create_hook_slots();
191 assert_eq!(slots.len(), HookPoint::COUNT);
192 for slot in &slots {
193 assert!(slot.programs.is_empty());
194 let ctx = HookContext::Tick;
196 assert!((slot.runner)(slot, &ctx).is_none());
197 }
198 }
199
200 #[test]
201 fn test_run_hook_macro() {
202 struct FakeDriver {
203 hook_slots: [HookSlot; HookPoint::COUNT],
204 }
205 let driver = FakeDriver {
206 hook_slots: create_hook_slots(),
207 };
208 let ctx = HookContext::Tick;
209 let result = run_hook!(driver, HookPoint::Tick, ctx);
210 assert!(result.is_none());
211
212 let ctx2 = HookContext::Interface { interface_id: 42 };
213 let result2 = run_hook!(driver, HookPoint::InterfaceUp, ctx2);
214 assert!(result2.is_none());
215 }
216
217 #[test]
218 fn test_verdict_values() {
219 assert_eq!(Verdict::Continue as u32, 0);
220 assert_eq!(Verdict::Drop as u32, 1);
221 assert_eq!(Verdict::Modify as u32, 2);
222 assert_eq!(Verdict::Halt as u32, 3);
223 }
224
225 #[test]
226 fn test_hook_result_helpers() {
227 let drop_r = HookResult::drop_result();
228 assert!(drop_r.is_drop());
229 assert_eq!(drop_r.verdict, Verdict::Drop as u32);
230
231 let cont_r = HookResult::continue_result();
232 assert!(!cont_r.is_drop());
233 assert_eq!(cont_r.verdict, Verdict::Continue as u32);
234 assert_eq!(cont_r.modified_data_len, 0);
235 assert_eq!(cont_r.inject_actions_count, 0);
236 assert_eq!(cont_r.log_len, 0);
237 }
238}