1use memf_core::object_reader::ObjectReader;
8use memf_format::PhysicalMemoryProvider;
9
10use crate::{ArpEntryInfo, Error, NeighState, Result};
11
12pub fn walk_arp_cache<P: PhysicalMemoryProvider>(
18 reader: &ObjectReader<P>,
19) -> Result<Vec<ArpEntryInfo>> {
20 let arp_tbl_addr =
21 reader
22 .symbols()
23 .symbol_address("arp_tbl")
24 .ok_or_else(|| Error::MissingKernelSymbol {
25 name: "arp_tbl".into(),
26 })?;
27
28 let nht_ptr: u64 = reader.read_field(arp_tbl_addr, "neigh_table", "nht")?;
30 if nht_ptr == 0 {
31 return Ok(Vec::new());
32 }
33
34 let buckets_ptr: u64 = reader.read_field(nht_ptr, "neigh_hash_table", "hash_buckets")?;
36 let hash_shift: u32 = reader.read_field(nht_ptr, "neigh_hash_table", "hash_shift")?;
38 let bucket_count: u64 = 1u64 << hash_shift;
39
40 if buckets_ptr == 0 {
41 return Ok(Vec::new());
42 }
43
44 let mut entries = Vec::new();
45
46 for i in 0..bucket_count {
47 let bucket_addr = buckets_ptr + i * 8;
49 let neigh_ptr: u64 = match reader.read_bytes(bucket_addr, 8) {
50 Ok(bytes) => bytes[..8].try_into().map_or(0, u64::from_le_bytes),
51 Err(_) => continue,
52 };
53
54 let mut current = neigh_ptr;
55 let mut chain_len = 0;
56 while current != 0 && chain_len < 1000 {
57 match read_neighbour(reader, current) {
58 Ok(entry) => entries.push(entry),
59 Err(e @ (Error::MissingField { .. } | Error::MissingKernelSymbol { .. })) => {
60 return Err(e);
61 }
62 Err(_) => {}
63 }
64
65 current = match reader.read_field::<u64>(current, "neighbour", "next") {
67 Ok(v) => v,
68 Err(_) => break,
69 };
70 chain_len += 1;
71 }
72 }
73
74 Ok(entries)
75}
76
77fn read_neighbour<P: PhysicalMemoryProvider>(
78 reader: &ObjectReader<P>,
79 neigh_addr: u64,
80) -> Result<ArpEntryInfo> {
81 let ip_raw: u32 = reader.read_field(neigh_addr, "neighbour", "primary_key")?;
83 let ip_bytes = ip_raw.to_le_bytes();
84 let ip_addr = format!(
85 "{}.{}.{}.{}",
86 ip_bytes[0], ip_bytes[1], ip_bytes[2], ip_bytes[3]
87 );
88
89 let ha_offset = reader
91 .symbols()
92 .field_offset("neighbour", "ha")
93 .ok_or_else(|| Error::MissingField {
94 struct_name: "neighbour".into(),
95 field_name: "ha".into(),
96 })?;
97 let mac_bytes = reader.read_bytes(neigh_addr + ha_offset, 6)?;
98 let mac_addr = format!(
99 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
100 mac_bytes[0], mac_bytes[1], mac_bytes[2], mac_bytes[3], mac_bytes[4], mac_bytes[5]
101 );
102
103 let nud_state: u8 = reader.read_field(neigh_addr, "neighbour", "nud_state")?;
105
106 let dev_ptr: u64 = reader.read_field(neigh_addr, "neighbour", "dev")?;
108 let dev_name = if dev_ptr != 0 {
109 reader.read_field_string(dev_ptr, "net_device", "name", 16)?
110 } else {
111 String::from("?")
112 };
113
114 Ok(ArpEntryInfo {
115 ip_addr,
116 mac_addr,
117 dev_name,
118 state: NeighState::from_raw(nud_state),
119 })
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::NeighState;
126 use memf_core::object_reader::ObjectReader;
127 use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
128 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
129 use memf_symbols::isf::IsfResolver;
130 use memf_symbols::test_builders::IsfBuilder;
131
132 const NHT_PTR_OFF: usize = 0;
152 const HASH_BUCKETS_OFF: usize = 0;
154 const HASH_SHIFT_OFF: usize = 8;
155 const NEIGH_NEXT_OFF: usize = 0;
157 const NEIGH_KEY_OFF: usize = 8;
158 const NEIGH_HA_OFF: usize = 12;
159 const NEIGH_NUD_OFF: usize = 18;
160 const NEIGH_DEV_OFF: usize = 24;
161
162 fn build_arp_isf() -> serde_json::Value {
163 IsfBuilder::new()
164 .add_struct("neigh_table", 64)
165 .add_field("neigh_table", "nht", 0, "pointer")
166 .add_struct("neigh_hash_table", 16)
167 .add_field("neigh_hash_table", "hash_buckets", 0, "pointer")
168 .add_field("neigh_hash_table", "hash_shift", 8, "unsigned int")
169 .add_struct("neighbour", 64)
170 .add_field("neighbour", "next", 0, "pointer")
171 .add_field("neighbour", "primary_key", 8, "unsigned int")
172 .add_field("neighbour", "ha", 12, "char")
173 .add_field("neighbour", "nud_state", 18, "unsigned char")
174 .add_field("neighbour", "dev", 24, "pointer")
175 .add_struct("net_device", 256)
176 .add_field("net_device", "name", 0, "char")
177 .add_symbol("arp_tbl", 0xFFFF_8000_0010_0000)
178 .build_json()
179 }
180
181 fn make_reader(pages: &[(u64, u64, &[u8])]) -> ObjectReader<SyntheticPhysMem> {
182 let isf = build_arp_isf();
183 let resolver = IsfResolver::from_value(&isf).unwrap();
184
185 let mut builder = PageTableBuilder::new();
186 for &(vaddr, paddr, data) in pages {
187 builder = builder
188 .map_4k(vaddr, paddr, flags::WRITABLE)
189 .write_phys(paddr, data);
190 }
191 let (cr3, mem) = builder.build();
192 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
193 ObjectReader::new(vas, Box::new(resolver))
194 }
195
196 #[test]
198 fn walk_single_arp_entry() {
199 let arp_tbl_vaddr: u64 = 0xFFFF_8000_0010_0000;
200 let arp_tbl_paddr: u64 = 0x0080_0000;
201
202 let nht_vaddr: u64 = 0xFFFF_8000_0020_0000;
203 let nht_paddr: u64 = 0x0090_0000;
204
205 let neigh_vaddr: u64 = 0xFFFF_8000_0030_0000;
206 let neigh_paddr: u64 = 0x00A0_0000;
207
208 let dev_vaddr: u64 = 0xFFFF_8000_0040_0000;
209 let dev_paddr: u64 = 0x00B0_0000;
210
211 let bucket_array_vaddr: u64 = nht_vaddr + 0x100;
213
214 let mut arp_data = vec![0u8; 4096];
216 arp_data[NHT_PTR_OFF..NHT_PTR_OFF + 8].copy_from_slice(&nht_vaddr.to_le_bytes());
217
218 let mut nht_data = vec![0u8; 4096];
220 nht_data[HASH_BUCKETS_OFF..HASH_BUCKETS_OFF + 8]
221 .copy_from_slice(&bucket_array_vaddr.to_le_bytes());
222 nht_data[HASH_SHIFT_OFF..HASH_SHIFT_OFF + 4].copy_from_slice(&0u32.to_le_bytes());
224 nht_data[0x100..0x108].copy_from_slice(&neigh_vaddr.to_le_bytes());
226
227 let mut neigh_data = vec![0u8; 4096];
229 neigh_data[NEIGH_NEXT_OFF..NEIGH_NEXT_OFF + 8].copy_from_slice(&0u64.to_le_bytes()); let ip: u32 = u32::from_le_bytes([192, 168, 1, 1]);
231 neigh_data[NEIGH_KEY_OFF..NEIGH_KEY_OFF + 4].copy_from_slice(&ip.to_le_bytes());
232 neigh_data[NEIGH_HA_OFF..NEIGH_HA_OFF + 6]
233 .copy_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
234 neigh_data[NEIGH_NUD_OFF] = 0x02; neigh_data[NEIGH_DEV_OFF..NEIGH_DEV_OFF + 8].copy_from_slice(&dev_vaddr.to_le_bytes());
236
237 let mut dev_data = vec![0u8; 4096];
239 dev_data[..4].copy_from_slice(b"eth0");
240
241 let reader = make_reader(&[
242 (arp_tbl_vaddr, arp_tbl_paddr, &arp_data),
243 (nht_vaddr, nht_paddr, &nht_data),
244 (neigh_vaddr, neigh_paddr, &neigh_data),
245 (dev_vaddr, dev_paddr, &dev_data),
246 ]);
247
248 let entries = walk_arp_cache(&reader).unwrap();
249 assert_eq!(entries.len(), 1);
250 assert_eq!(entries[0].ip_addr, "192.168.1.1");
251 assert_eq!(entries[0].mac_addr, "aa:bb:cc:dd:ee:ff");
252 assert_eq!(entries[0].dev_name, "eth0");
253 assert_eq!(entries[0].state, NeighState::Reachable);
254 }
255
256 #[test]
258 fn walk_empty_arp_table() {
259 let arp_tbl_vaddr: u64 = 0xFFFF_8000_0010_0000;
260 let arp_tbl_paddr: u64 = 0x0080_0000;
261 let nht_vaddr: u64 = 0xFFFF_8000_0020_0000;
262 let nht_paddr: u64 = 0x0090_0000;
263 let bucket_array_vaddr: u64 = nht_vaddr + 0x100;
264
265 let mut arp_data = vec![0u8; 4096];
266 arp_data[NHT_PTR_OFF..NHT_PTR_OFF + 8].copy_from_slice(&nht_vaddr.to_le_bytes());
267
268 let mut nht_data = vec![0u8; 4096];
269 nht_data[HASH_BUCKETS_OFF..HASH_BUCKETS_OFF + 8]
270 .copy_from_slice(&bucket_array_vaddr.to_le_bytes());
271 nht_data[HASH_SHIFT_OFF..HASH_SHIFT_OFF + 4].copy_from_slice(&0u32.to_le_bytes());
272 nht_data[0x100..0x108].copy_from_slice(&0u64.to_le_bytes());
274
275 let reader = make_reader(&[
276 (arp_tbl_vaddr, arp_tbl_paddr, &arp_data),
277 (nht_vaddr, nht_paddr, &nht_data),
278 ]);
279
280 let entries = walk_arp_cache(&reader).unwrap();
281 assert!(entries.is_empty());
282 }
283
284 #[test]
286 fn walk_two_entries_in_chain() {
287 let arp_tbl_vaddr: u64 = 0xFFFF_8000_0010_0000;
288 let arp_tbl_paddr: u64 = 0x0080_0000;
289 let nht_vaddr: u64 = 0xFFFF_8000_0020_0000;
290 let nht_paddr: u64 = 0x0090_0000;
291 let neigh1_vaddr: u64 = 0xFFFF_8000_0030_0000;
292 let neigh1_paddr: u64 = 0x00A0_0000;
293 let neigh2_vaddr: u64 = 0xFFFF_8000_0050_0000;
294 let neigh2_paddr: u64 = 0x00C0_0000;
295 let dev_vaddr: u64 = 0xFFFF_8000_0040_0000;
296 let dev_paddr: u64 = 0x00B0_0000;
297 let bucket_array_vaddr: u64 = nht_vaddr + 0x100;
298
299 let mut arp_data = vec![0u8; 4096];
300 arp_data[NHT_PTR_OFF..NHT_PTR_OFF + 8].copy_from_slice(&nht_vaddr.to_le_bytes());
301
302 let mut nht_data = vec![0u8; 4096];
303 nht_data[HASH_BUCKETS_OFF..HASH_BUCKETS_OFF + 8]
304 .copy_from_slice(&bucket_array_vaddr.to_le_bytes());
305 nht_data[HASH_SHIFT_OFF..HASH_SHIFT_OFF + 4].copy_from_slice(&0u32.to_le_bytes());
306 nht_data[0x100..0x108].copy_from_slice(&neigh1_vaddr.to_le_bytes());
307
308 let mut neigh1_data = vec![0u8; 4096];
310 neigh1_data[NEIGH_NEXT_OFF..NEIGH_NEXT_OFF + 8]
311 .copy_from_slice(&neigh2_vaddr.to_le_bytes());
312 let ip1: u32 = u32::from_le_bytes([10, 0, 0, 1]);
313 neigh1_data[NEIGH_KEY_OFF..NEIGH_KEY_OFF + 4].copy_from_slice(&ip1.to_le_bytes());
314 neigh1_data[NEIGH_HA_OFF..NEIGH_HA_OFF + 6]
315 .copy_from_slice(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66]);
316 neigh1_data[NEIGH_NUD_OFF] = 0x04; neigh1_data[NEIGH_DEV_OFF..NEIGH_DEV_OFF + 8].copy_from_slice(&dev_vaddr.to_le_bytes());
318
319 let mut neigh2_data = vec![0u8; 4096];
321 neigh2_data[NEIGH_NEXT_OFF..NEIGH_NEXT_OFF + 8].copy_from_slice(&0u64.to_le_bytes());
322 let ip2: u32 = u32::from_le_bytes([10, 0, 0, 2]);
323 neigh2_data[NEIGH_KEY_OFF..NEIGH_KEY_OFF + 4].copy_from_slice(&ip2.to_le_bytes());
324 neigh2_data[NEIGH_HA_OFF..NEIGH_HA_OFF + 6]
325 .copy_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x00]);
326 neigh2_data[NEIGH_NUD_OFF] = 0x80; neigh2_data[NEIGH_DEV_OFF..NEIGH_DEV_OFF + 8].copy_from_slice(&dev_vaddr.to_le_bytes());
328
329 let mut dev_data = vec![0u8; 4096];
330 dev_data[..5].copy_from_slice(b"ens33");
331
332 let reader = make_reader(&[
333 (arp_tbl_vaddr, arp_tbl_paddr, &arp_data),
334 (nht_vaddr, nht_paddr, &nht_data),
335 (neigh1_vaddr, neigh1_paddr, &neigh1_data),
336 (neigh2_vaddr, neigh2_paddr, &neigh2_data),
337 (dev_vaddr, dev_paddr, &dev_data),
338 ]);
339
340 let entries = walk_arp_cache(&reader).unwrap();
341 assert_eq!(entries.len(), 2);
342 assert_eq!(entries[0].ip_addr, "10.0.0.1");
343 assert_eq!(entries[0].mac_addr, "11:22:33:44:55:66");
344 assert_eq!(entries[0].state, NeighState::Stale);
345 assert_eq!(entries[1].ip_addr, "10.0.0.2");
346 assert_eq!(entries[1].mac_addr, "aa:bb:cc:dd:ee:00");
347 assert_eq!(entries[1].state, NeighState::Permanent);
348 assert!(entries.iter().all(|e| e.dev_name == "ens33"));
349 }
350
351 #[test]
352 fn missing_arp_tbl_returns_missing_kernel_symbol() {
353 let isf = IsfBuilder::new().build_json();
354 let resolver = IsfResolver::from_value(&isf).unwrap();
355 let (cr3, mem) = PageTableBuilder::new().build();
356 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
357 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
358 let result = walk_arp_cache(&reader);
359 assert!(
360 matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "arp_tbl"),
361 "expected MissingKernelSymbol {{name: \"arp_tbl\"}}, got {result:?}"
362 );
363 }
364
365 #[test]
366 fn missing_neighbour_ha_field_returns_missing_field() {
367 let arp_tbl_vaddr: u64 = 0xFFFF_8000_0010_0000;
369 let arp_tbl_paddr: u64 = 0x0080_0000;
370 let nht_vaddr: u64 = 0xFFFF_8000_0020_0000;
371 let nht_paddr: u64 = 0x0090_0000;
372 let neigh_vaddr: u64 = 0xFFFF_8000_0030_0000;
373 let neigh_paddr: u64 = 0x00A0_0000;
374 let bucket_array_vaddr: u64 = nht_vaddr + 0x100;
375
376 let isf = IsfBuilder::new()
377 .add_symbol("arp_tbl", arp_tbl_vaddr)
378 .add_struct("neigh_table", 256)
379 .add_field("neigh_table", "nht", 0, "pointer")
380 .add_struct("neighbour", 128)
381 .add_field("neighbour", "next", 0, "pointer")
382 .add_field("neighbour", "primary_key", 8, "unsigned int")
383 .add_struct("neigh_hash_table", 64)
385 .add_field("neigh_hash_table", "hash_buckets", 0, "pointer")
386 .add_field("neigh_hash_table", "hash_shift", 8, "int")
387 .build_json();
388 let resolver = IsfResolver::from_value(&isf).unwrap();
389
390 let mut arp_data = vec![0u8; 4096];
391 arp_data[0..8].copy_from_slice(&nht_vaddr.to_le_bytes());
392
393 let mut nht_data = vec![0u8; 4096];
394 nht_data[0..8].copy_from_slice(&bucket_array_vaddr.to_le_bytes());
395 nht_data[8..12].copy_from_slice(&0u32.to_le_bytes()); nht_data[0x100..0x108].copy_from_slice(&neigh_vaddr.to_le_bytes());
397
398 let neigh_data = vec![0u8; 4096]; let (cr3, mem) = PageTableBuilder::new()
401 .map_4k(arp_tbl_vaddr, arp_tbl_paddr, flags::WRITABLE)
402 .write_phys(arp_tbl_paddr, &arp_data)
403 .map_4k(nht_vaddr, nht_paddr, flags::WRITABLE)
404 .write_phys(nht_paddr, &nht_data)
405 .map_4k(neigh_vaddr, neigh_paddr, flags::WRITABLE)
406 .write_phys(neigh_paddr, &neigh_data)
407 .build();
408 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
409 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
410 let result = walk_arp_cache(&reader);
411 assert!(
412 matches!(result, Err(crate::Error::MissingField { ref struct_name, ref field_name }) if struct_name == "neighbour" && field_name == "ha"),
413 "expected MissingField neighbour.ha, got {result:?}"
414 );
415 }
416}