Skip to main content

stackforge_core/layer/
neighbor.rs

1//! Neighbor resolution system for automatic MAC address lookup.
2//!
3//! which automatically resolves destination MAC addresses based on
4//! the network layer protocol being used.
5//!
6//! For example:
7//! - ARP requests should use broadcast MAC (ff:ff:ff:ff:ff:ff)
8//! - IP packets need ARP resolution to find the destination MAC
9//! - Multicast IPs map to specific multicast MACs
10
11use std::collections::HashMap;
12use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
13use std::sync::{Arc, RwLock};
14use std::time::{Duration, Instant};
15
16use crate::layer::LayerKind;
17use crate::layer::field::MacAddress;
18
19/// A resolver function type for determining destination MAC addresses.
20pub type ResolverFn = fn(&NeighborCache, &[u8], &[u8]) -> Option<MacAddress>;
21
22/// Neighbor cache entry with expiration.
23#[derive(Debug, Clone)]
24pub struct CacheEntry {
25    pub mac: MacAddress,
26    pub expires: Instant,
27}
28
29impl CacheEntry {
30    pub fn new(mac: MacAddress, ttl: Duration) -> Self {
31        Self {
32            mac,
33            expires: Instant::now() + ttl,
34        }
35    }
36
37    pub fn is_expired(&self) -> bool {
38        Instant::now() > self.expires
39    }
40}
41
42/// ARP cache for storing resolved MAC addresses.
43#[derive(Debug, Clone, Default)]
44pub struct ArpCache {
45    entries: HashMap<Ipv4Addr, CacheEntry>,
46    ttl: Duration,
47}
48
49impl ArpCache {
50    pub fn new() -> Self {
51        Self::with_ttl(Duration::from_secs(120)) // 2 minute default like Scapy
52    }
53
54    pub fn with_ttl(ttl: Duration) -> Self {
55        Self {
56            entries: HashMap::new(),
57            ttl,
58        }
59    }
60
61    /// Get a cached MAC address for an IP.
62    pub fn get(&self, ip: &Ipv4Addr) -> Option<MacAddress> {
63        self.entries.get(ip).and_then(|entry| {
64            if entry.is_expired() {
65                None
66            } else {
67                Some(entry.mac)
68            }
69        })
70    }
71
72    /// Store a MAC address for an IP.
73    pub fn put(&mut self, ip: Ipv4Addr, mac: MacAddress) {
74        self.entries.insert(ip, CacheEntry::new(mac, self.ttl));
75    }
76
77    /// Remove expired entries.
78    pub fn cleanup(&mut self) {
79        self.entries.retain(|_, entry| !entry.is_expired());
80    }
81
82    /// Clear all entries.
83    pub fn clear(&mut self) {
84        self.entries.clear();
85    }
86
87    /// Get number of entries.
88    pub fn len(&self) -> usize {
89        self.entries.len()
90    }
91
92    pub fn is_empty(&self) -> bool {
93        self.entries.is_empty()
94    }
95}
96
97/// IPv6 neighbor cache.
98#[derive(Debug, Clone, Default)]
99pub struct NdpCache {
100    entries: HashMap<Ipv6Addr, CacheEntry>,
101    ttl: Duration,
102}
103
104impl NdpCache {
105    pub fn new() -> Self {
106        Self::with_ttl(Duration::from_secs(120))
107    }
108
109    pub fn with_ttl(ttl: Duration) -> Self {
110        Self {
111            entries: HashMap::new(),
112            ttl,
113        }
114    }
115
116    pub fn get(&self, ip: &Ipv6Addr) -> Option<MacAddress> {
117        self.entries.get(ip).and_then(|entry| {
118            if entry.is_expired() {
119                None
120            } else {
121                Some(entry.mac)
122            }
123        })
124    }
125
126    pub fn put(&mut self, ip: Ipv6Addr, mac: MacAddress) {
127        self.entries.insert(ip, CacheEntry::new(mac, self.ttl));
128    }
129
130    pub fn cleanup(&mut self) {
131        self.entries.retain(|_, entry| !entry.is_expired());
132    }
133
134    pub fn clear(&mut self) {
135        self.entries.clear();
136    }
137}
138
139/// Trait for neighbor resolution.
140pub trait NeighborResolver {
141    /// Resolve the destination MAC for a given L2/L3 layer combination.
142    fn resolve(&self, l2_data: &[u8], l3_data: &[u8]) -> Option<MacAddress>;
143}
144
145/// Registry for neighbor resolvers.
146#[derive(Clone)]
147pub struct NeighborCache {
148    /// Registered resolvers for (L2, L3) pairs
149    resolvers: HashMap<(LayerKind, LayerKind), ResolverFn>,
150    /// ARP cache for IPv4
151    arp_cache: Arc<RwLock<ArpCache>>,
152    /// NDP cache for IPv6
153    ndp_cache: Arc<RwLock<NdpCache>>,
154}
155
156impl Default for NeighborCache {
157    fn default() -> Self {
158        Self::new()
159    }
160}
161
162impl NeighborCache {
163    pub fn new() -> Self {
164        let mut cache = Self {
165            resolvers: HashMap::new(),
166            arp_cache: Arc::new(RwLock::new(ArpCache::new())),
167            ndp_cache: Arc::new(RwLock::new(NdpCache::new())),
168        };
169        cache.register_defaults();
170        cache
171    }
172
173    /// Register default resolvers.
174    fn register_defaults(&mut self) {
175        self.register_l3(LayerKind::Ethernet, LayerKind::Arp, resolve_ether_arp);
176        self.register_l3(LayerKind::Ethernet, LayerKind::Ipv4, resolve_ether_ipv4);
177        self.register_l3(LayerKind::Ethernet, LayerKind::Ipv6, resolve_ether_ipv6);
178    }
179
180    /// Register a resolver for a L2/L3 combination.
181    pub fn register_l3(&mut self, l2: LayerKind, l3: LayerKind, resolver: ResolverFn) {
182        self.resolvers.insert((l2, l3), resolver);
183    }
184
185    /// Resolve destination MAC for the given layer data.
186    pub fn resolve(
187        &self,
188        l2: LayerKind,
189        l3: LayerKind,
190        l2_data: &[u8],
191        l3_data: &[u8],
192    ) -> Option<MacAddress> {
193        self.resolvers
194            .get(&(l2, l3))
195            .and_then(|resolver| resolver(self, l2_data, l3_data))
196    }
197
198    /// Get the ARP cache.
199    pub fn arp_cache(&self) -> &Arc<RwLock<ArpCache>> {
200        &self.arp_cache
201    }
202
203    /// Get the NDP cache.
204    pub fn ndp_cache(&self) -> &Arc<RwLock<NdpCache>> {
205        &self.ndp_cache
206    }
207
208    /// Cache an ARP entry.
209    pub fn cache_arp(&self, ip: Ipv4Addr, mac: MacAddress) {
210        if let Ok(mut cache) = self.arp_cache.write() {
211            cache.put(ip, mac);
212        }
213    }
214
215    /// Lookup an ARP entry.
216    pub fn lookup_arp(&self, ip: &Ipv4Addr) -> Option<MacAddress> {
217        self.arp_cache.read().ok()?.get(ip)
218    }
219
220    /// Cache an NDP entry.
221    pub fn cache_ndp(&self, ip: Ipv6Addr, mac: MacAddress) {
222        if let Ok(mut cache) = self.ndp_cache.write() {
223            cache.put(ip, mac);
224        }
225    }
226
227    /// Lookup an NDP entry.
228    pub fn lookup_ndp(&self, ip: &Ipv6Addr) -> Option<MacAddress> {
229        self.ndp_cache.read().ok()?.get(ip)
230    }
231}
232
233impl std::fmt::Debug for NeighborCache {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        f.debug_struct("NeighborCache")
236            .field("resolvers", &self.resolvers.keys().collect::<Vec<_>>())
237            .finish()
238    }
239}
240
241// ============================================================================
242// Default Resolver Implementations
243// ============================================================================
244
245/// Resolve MAC for Ethernet + ARP.
246fn resolve_ether_arp(
247    _cache: &NeighborCache,
248    _l2_data: &[u8],
249    l3_data: &[u8],
250) -> Option<MacAddress> {
251    if l3_data.len() < 8 {
252        return None;
253    }
254
255    // ARP Request (op=1) -> Broadcast
256    let op = u16::from_be_bytes([l3_data[6], l3_data[7]]);
257    if op == 1 {
258        Some(MacAddress::BROADCAST)
259    } else {
260        None
261    }
262}
263
264/// Resolve MAC for Ethernet + IPv4.
265fn resolve_ether_ipv4(
266    cache: &NeighborCache,
267    _l2_data: &[u8],
268    l3_data: &[u8],
269) -> Option<MacAddress> {
270    if l3_data.len() < 20 {
271        return None;
272    }
273
274    let dst_ip = Ipv4Addr::new(l3_data[16], l3_data[17], l3_data[18], l3_data[19]);
275
276    if dst_ip.is_multicast() {
277        return Some(MacAddress::from_ipv4_multicast(dst_ip));
278    }
279    if dst_ip.is_broadcast() || dst_ip == Ipv4Addr::new(255, 255, 255, 255) {
280        return Some(MacAddress::BROADCAST);
281    }
282
283    let interfaces = pnet_datalink::interfaces();
284    let mut next_hop = dst_ip;
285    let mut is_local = false;
286
287    for iface in &interfaces {
288        if !iface.is_up() {
289            continue;
290        }
291        for ip_net in &iface.ips {
292            if let IpAddr::V4(local_ip) = ip_net.ip() {
293                if local_ip == dst_ip {
294                    is_local = true;
295                    break;
296                }
297            }
298            if ip_net.contains(IpAddr::V4(dst_ip)) {
299                is_local = true;
300                break;
301            }
302        }
303    }
304
305    if !is_local {
306        if let Ok(gw) = default_net::get_default_gateway() {
307            if let IpAddr::V4(gw_ip) = gw.ip_addr {
308                next_hop = gw_ip;
309            }
310        }
311    }
312
313    cache.lookup_arp(&next_hop)
314}
315
316/// Resolve MAC for Ethernet + IPv6.
317fn resolve_ether_ipv6(
318    cache: &NeighborCache,
319    _l2_data: &[u8],
320    l3_data: &[u8],
321) -> Option<MacAddress> {
322    if l3_data.len() < 40 {
323        return None;
324    }
325
326    let mut dst_bytes = [0u8; 16];
327    dst_bytes.copy_from_slice(&l3_data[24..40]);
328    let dst_ip = Ipv6Addr::from(dst_bytes);
329
330    if dst_ip.segments()[0] >> 8 == 0xff {
331        return Some(MacAddress::from_ipv6_multicast(dst_ip));
332    }
333
334    let interfaces = pnet_datalink::interfaces();
335    let mut next_hop = dst_ip;
336    let mut is_local = false;
337
338    for iface in &interfaces {
339        if !iface.is_up() {
340            continue;
341        }
342        for ip_net in &iface.ips {
343            if ip_net.contains(IpAddr::V6(dst_ip)) {
344                is_local = true;
345                break;
346            }
347        }
348    }
349
350    if !is_local {
351        if let Ok(gw) = default_net::get_default_gateway() {
352            if let IpAddr::V6(gw_ip) = gw.ip_addr {
353                next_hop = gw_ip;
354            }
355        }
356    }
357
358    cache.lookup_ndp(&next_hop)
359}
360
361// ============================================================================
362// Helper Functions
363// ============================================================================
364
365/// Get multicast MAC for IPv4 multicast address.
366pub fn ipv4_multicast_mac(ip: Ipv4Addr) -> MacAddress {
367    MacAddress::from_ipv4_multicast(ip)
368}
369
370/// Get multicast MAC for IPv6 multicast address.
371pub fn ipv6_multicast_mac(ip: Ipv6Addr) -> MacAddress {
372    MacAddress::from_ipv6_multicast(ip)
373}
374
375/// Check if an IPv4 address is multicast.
376pub fn is_ipv4_multicast(ip: Ipv4Addr) -> bool {
377    ip.is_multicast()
378}
379
380/// Check if an IPv6 address is multicast.
381pub fn is_ipv6_multicast(ip: Ipv6Addr) -> bool {
382    ip.segments()[0] >> 8 == 0xff
383}
384
385/// Solicited-node multicast address for IPv6.
386pub fn solicited_node_multicast(ip: Ipv6Addr) -> Ipv6Addr {
387    let octets = ip.octets();
388    Ipv6Addr::new(
389        0xff02,
390        0,
391        0,
392        0,
393        0,
394        1,
395        0xff00 | (octets[13] as u16),
396        ((octets[14] as u16) << 8) | (octets[15] as u16),
397    )
398}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403
404    #[test]
405    fn test_arp_cache() {
406        let mut cache = ArpCache::new();
407        let ip = Ipv4Addr::new(192, 168, 1, 1);
408        let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
409
410        cache.put(ip, mac);
411        assert_eq!(cache.get(&ip), Some(mac));
412    }
413
414    #[test]
415    fn test_arp_cache_expiration() {
416        let mut cache = ArpCache::with_ttl(Duration::from_millis(1));
417        let ip = Ipv4Addr::new(192, 168, 1, 1);
418        let mac = MacAddress::new([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
419
420        cache.put(ip, mac);
421        std::thread::sleep(Duration::from_millis(10));
422        assert_eq!(cache.get(&ip), None);
423    }
424
425    #[test]
426    fn test_resolve_ether_arp() {
427        let cache = NeighborCache::new(); // Create dummy cache for signature
428
429        // ARP request (op=1)
430        let arp_request = vec![
431            0x00, 0x01, // hwtype
432            0x08, 0x00, // ptype
433            0x06, 0x04, // hwlen, plen
434            0x00, 0x01, // op = request
435        ];
436
437        // FIXED: Pass &cache as first argument
438        let result = resolve_ether_arp(&cache, &[], &arp_request);
439        assert_eq!(result, Some(MacAddress::BROADCAST));
440
441        // ARP reply (op=2)
442        let arp_reply = vec![
443            0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x02, // op = reply
444        ];
445
446        // FIXED: Pass &cache as first argument
447        let result = resolve_ether_arp(&cache, &[], &arp_reply);
448        assert_eq!(result, None);
449    }
450
451    #[test]
452    fn test_resolve_ether_ipv4_multicast() {
453        let cache = NeighborCache::new();
454
455        // IPv4 header with multicast destination (224.0.0.1)
456        let mut ipv4 = vec![0u8; 20];
457        ipv4[16] = 224;
458        ipv4[17] = 0;
459        ipv4[18] = 0;
460        ipv4[19] = 1;
461
462        // FIXED: Pass &cache as first argument
463        let result = resolve_ether_ipv4(&cache, &[], &ipv4);
464        assert!(result.is_some());
465        let mac = result.unwrap();
466        assert!(mac.is_ipv4_multicast());
467    }
468
469    #[test]
470    fn test_resolve_ether_ipv4_broadcast() {
471        let cache = NeighborCache::new();
472
473        let mut ipv4 = vec![0u8; 20];
474        ipv4[16] = 255;
475        ipv4[17] = 255;
476        ipv4[18] = 255;
477        ipv4[19] = 255;
478
479        // FIXED: Pass &cache as first argument
480        let result = resolve_ether_ipv4(&cache, &[], &ipv4);
481        assert_eq!(result, Some(MacAddress::BROADCAST));
482    }
483
484    #[test]
485    fn test_ipv4_multicast_mac() {
486        let ip = Ipv4Addr::new(224, 0, 0, 1);
487        let mac = ipv4_multicast_mac(ip);
488        assert_eq!(mac.0[0], 0x01);
489        assert_eq!(mac.0[1], 0x00);
490        assert_eq!(mac.0[2], 0x5e);
491    }
492
493    #[test]
494    fn test_ipv6_multicast_mac() {
495        let ip = Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 1);
496        let mac = ipv6_multicast_mac(ip);
497        assert_eq!(mac.0[0], 0x33);
498        assert_eq!(mac.0[1], 0x33);
499    }
500
501    #[test]
502    fn test_neighbor_cache() {
503        let cache = NeighborCache::new();
504
505        // Test ARP caching
506        let ip = Ipv4Addr::new(10, 0, 0, 1);
507        let mac = MacAddress::new([0xaa; 6]);
508        cache.cache_arp(ip, mac);
509        assert_eq!(cache.lookup_arp(&ip), Some(mac));
510    }
511
512    #[test]
513    fn test_solicited_node_multicast() {
514        let ip = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0x1234);
515        let snm = solicited_node_multicast(ip);
516
517        // Should be ff02::1:ff00:1234
518        assert_eq!(snm.segments()[0], 0xff02);
519        assert_eq!(snm.segments()[5], 1);
520    }
521}