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