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
125 .sort_by_key(|program| std::cmp::Reverse(program.priority));
126 self.update_runner();
127 }
128
129 pub fn detach(&mut self, name: &str) -> Option<LoadedProgram> {
131 let pos = self.programs.iter().position(|p| p.name == name)?;
132 let prog = self.programs.remove(pos);
133 self.update_runner();
134 Some(prog)
135 }
136
137 pub fn has_programs(&self) -> bool {
139 !std::ptr::eq(self.runner as *const (), hook_noop as *const ())
140 }
141}
142
143pub fn create_hook_slots() -> [HookSlot; HookPoint::COUNT] {
146 std::array::from_fn(|_| HookSlot {
147 programs: Vec::new(),
148 runner: hook_noop,
149 })
150}
151
152#[macro_export]
161macro_rules! run_hook {
162 ($driver:expr, $point:expr, $ctx:expr) => {{
163 ($driver.hook_slots[$point as usize].runner)(&$driver.hook_slots[$point as usize], &$ctx)
164 }};
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use crate::result::Verdict;
171
172 #[test]
173 fn test_hook_point_count() {
174 assert_eq!(HookPoint::COUNT, 21);
175 assert_eq!(HookPoint::Tick as usize, 20);
177 }
178
179 #[test]
180 fn test_hook_noop_returns_none() {
181 let slot = HookSlot {
182 programs: Vec::new(),
183 runner: hook_noop,
184 };
185 let ctx = HookContext::Tick;
186 assert!(hook_noop(&slot, &ctx).is_none());
187 }
188
189 #[test]
190 fn test_create_hook_slots() {
191 let slots = create_hook_slots();
192 assert_eq!(slots.len(), HookPoint::COUNT);
193 for slot in &slots {
194 assert!(slot.programs.is_empty());
195 let ctx = HookContext::Tick;
197 assert!((slot.runner)(slot, &ctx).is_none());
198 }
199 }
200
201 #[test]
202 fn test_run_hook_macro() {
203 struct FakeDriver {
204 hook_slots: [HookSlot; HookPoint::COUNT],
205 }
206 let driver = FakeDriver {
207 hook_slots: create_hook_slots(),
208 };
209 let ctx = HookContext::Tick;
210 let result = run_hook!(driver, HookPoint::Tick, ctx);
211 assert!(result.is_none());
212
213 let ctx2 = HookContext::Interface { interface_id: 42 };
214 let result2 = run_hook!(driver, HookPoint::InterfaceUp, ctx2);
215 assert!(result2.is_none());
216 }
217
218 #[test]
219 fn test_verdict_values() {
220 assert_eq!(Verdict::Continue as u32, 0);
221 assert_eq!(Verdict::Drop as u32, 1);
222 assert_eq!(Verdict::Modify as u32, 2);
223 assert_eq!(Verdict::Halt as u32, 3);
224 }
225
226 #[test]
227 fn test_hook_result_helpers() {
228 let drop_r = HookResult::drop_result();
229 assert!(drop_r.is_drop());
230 assert_eq!(drop_r.verdict, Verdict::Drop as u32);
231
232 let cont_r = HookResult::continue_result();
233 assert!(!cont_r.is_drop());
234 assert_eq!(cont_r.verdict, Verdict::Continue as u32);
235 assert_eq!(cont_r.modified_data_len, 0);
236 assert_eq!(cont_r.inject_actions_count, 0);
237 assert_eq!(cont_r.log_len, 0);
238 }
239}