123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- // Heads up! Before working on this file you should read, at least,
- // the parts of RFC 1122 that discuss ARP.
- use managed::ManagedMap;
- use crate::time::{Duration, Instant};
- use crate::wire::{EthernetAddress, IpAddress};
- /// A cached neighbor.
- ///
- /// A neighbor mapping translates from a protocol address to a hardware address,
- /// and contains the timestamp past which the mapping should be discarded.
- #[derive(Debug, Clone, Copy)]
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
- pub struct Neighbor {
- hardware_addr: EthernetAddress,
- expires_at: Instant,
- }
- /// An answer to a neighbor cache lookup.
- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
- pub(crate) enum Answer {
- /// The neighbor address is in the cache and not expired.
- Found(EthernetAddress),
- /// The neighbor address is not in the cache, or has expired.
- NotFound,
- /// The neighbor address is not in the cache, or has expired,
- /// and a lookup has been made recently.
- RateLimited,
- }
- impl Answer {
- /// Returns whether a valid address was found.
- pub(crate) fn found(&self) -> bool {
- match self {
- Answer::Found(_) => true,
- _ => false,
- }
- }
- }
- /// A neighbor cache backed by a map.
- ///
- /// # Examples
- ///
- /// On systems with heap, this cache can be created with:
- ///
- /// ```rust
- /// use std::collections::BTreeMap;
- /// use smoltcp::iface::NeighborCache;
- /// let mut neighbor_cache = NeighborCache::new(BTreeMap::new());
- /// ```
- ///
- /// On systems without heap, use:
- ///
- /// ```rust
- /// use smoltcp::iface::NeighborCache;
- /// let mut neighbor_cache_storage = [None; 8];
- /// let mut neighbor_cache = NeighborCache::new(&mut neighbor_cache_storage[..]);
- /// ```
- #[derive(Debug)]
- pub struct Cache<'a> {
- storage: ManagedMap<'a, IpAddress, Neighbor>,
- silent_until: Instant,
- gc_threshold: usize,
- }
- impl<'a> Cache<'a> {
- /// Minimum delay between discovery requests, in milliseconds.
- pub(crate) const SILENT_TIME: Duration = Duration { millis: 1_000 };
- /// Neighbor entry lifetime, in milliseconds.
- pub(crate) const ENTRY_LIFETIME: Duration = Duration { millis: 60_000 };
- /// Default number of entries in the cache before GC kicks in
- pub(crate) const GC_THRESHOLD: usize = 1024;
- /// Create a cache. The backing storage is cleared upon creation.
- ///
- /// # Panics
- /// This function panics if `storage.len() == 0`.
- pub fn new<T>(storage: T) -> Cache<'a>
- where
- T: Into<ManagedMap<'a, IpAddress, Neighbor>>,
- {
- Cache::new_with_limit(storage, Cache::GC_THRESHOLD)
- }
- pub fn new_with_limit<T>(storage: T, gc_threshold: usize) -> Cache<'a>
- where
- T: Into<ManagedMap<'a, IpAddress, Neighbor>>,
- {
- let mut storage = storage.into();
- storage.clear();
- Cache {
- storage,
- gc_threshold,
- silent_until: Instant::from_millis(0),
- }
- }
- pub fn fill(
- &mut self,
- protocol_addr: IpAddress,
- hardware_addr: EthernetAddress,
- timestamp: Instant,
- ) {
- debug_assert!(protocol_addr.is_unicast());
- debug_assert!(hardware_addr.is_unicast());
- #[cfg(any(feature = "std", feature = "alloc"))]
- let current_storage_size = self.storage.len();
- match self.storage {
- ManagedMap::Borrowed(_) => (),
- #[cfg(any(feature = "std", feature = "alloc"))]
- ManagedMap::Owned(ref mut map) => {
- if current_storage_size >= self.gc_threshold {
- let new_btree_map = map
- .iter_mut()
- .map(|(key, value)| (*key, *value))
- .filter(|(_, v)| timestamp < v.expires_at)
- .collect();
- *map = new_btree_map;
- }
- }
- };
- let neighbor = Neighbor {
- expires_at: timestamp + Self::ENTRY_LIFETIME,
- hardware_addr,
- };
- match self.storage.insert(protocol_addr, neighbor) {
- Ok(Some(old_neighbor)) => {
- if old_neighbor.hardware_addr != hardware_addr {
- net_trace!(
- "replaced {} => {} (was {})",
- protocol_addr,
- hardware_addr,
- old_neighbor.hardware_addr
- );
- }
- }
- Ok(None) => {
- net_trace!("filled {} => {} (was empty)", protocol_addr, hardware_addr);
- }
- Err((protocol_addr, neighbor)) => {
- // If we're going down this branch, it means that a fixed-size cache storage
- // is full, and we need to evict an entry.
- let old_protocol_addr = match self.storage {
- ManagedMap::Borrowed(ref mut pairs) => {
- pairs
- .iter()
- .min_by_key(|pair_opt| {
- let (_protocol_addr, neighbor) = pair_opt.unwrap();
- neighbor.expires_at
- })
- .expect("empty neighbor cache storage") // unwraps min_by_key
- .unwrap() // unwraps pair
- .0
- }
- // Owned maps can extend themselves.
- #[cfg(any(feature = "std", feature = "alloc"))]
- ManagedMap::Owned(_) => unreachable!(),
- };
- let _old_neighbor = self.storage.remove(&old_protocol_addr).unwrap();
- match self.storage.insert(protocol_addr, neighbor) {
- Ok(None) => {
- net_trace!(
- "filled {} => {} (evicted {} => {})",
- protocol_addr,
- hardware_addr,
- old_protocol_addr,
- _old_neighbor.hardware_addr
- );
- }
- // We've covered everything else above.
- _ => unreachable!(),
- }
- }
- }
- }
- pub(crate) fn lookup(&self, protocol_addr: &IpAddress, timestamp: Instant) -> Answer {
- if protocol_addr.is_broadcast() {
- return Answer::Found(EthernetAddress::BROADCAST);
- }
- if let Some(&Neighbor {
- expires_at,
- hardware_addr,
- }) = self.storage.get(protocol_addr)
- {
- if timestamp < expires_at {
- return Answer::Found(hardware_addr);
- }
- }
- if timestamp < self.silent_until {
- Answer::RateLimited
- } else {
- Answer::NotFound
- }
- }
- pub(crate) fn limit_rate(&mut self, timestamp: Instant) {
- self.silent_until = timestamp + Self::SILENT_TIME;
- }
- }
- #[cfg(test)]
- mod test {
- use super::*;
- use crate::wire::ip::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2, MOCK_IP_ADDR_3, MOCK_IP_ADDR_4};
- use std::collections::BTreeMap;
- const HADDR_A: EthernetAddress = EthernetAddress([0, 0, 0, 0, 0, 1]);
- const HADDR_B: EthernetAddress = EthernetAddress([0, 0, 0, 0, 0, 2]);
- const HADDR_C: EthernetAddress = EthernetAddress([0, 0, 0, 0, 0, 3]);
- const HADDR_D: EthernetAddress = EthernetAddress([0, 0, 0, 0, 0, 4]);
- #[test]
- fn test_fill() {
- let mut cache_storage = [Default::default(); 3];
- let mut cache = Cache::new(&mut cache_storage[..]);
- assert_eq!(
- cache
- .lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0))
- .found(),
- false
- );
- assert_eq!(
- cache
- .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
- .found(),
- false
- );
- cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
- assert_eq!(
- cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
- Answer::Found(HADDR_A)
- );
- assert_eq!(
- cache
- .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
- .found(),
- false
- );
- assert_eq!(
- cache
- .lookup(
- &MOCK_IP_ADDR_1,
- Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2
- )
- .found(),
- false
- );
- cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
- assert_eq!(
- cache
- .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(0))
- .found(),
- false
- );
- }
- #[test]
- fn test_expire() {
- let mut cache_storage = [Default::default(); 3];
- let mut cache = Cache::new(&mut cache_storage[..]);
- cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
- assert_eq!(
- cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
- Answer::Found(HADDR_A)
- );
- assert_eq!(
- cache
- .lookup(
- &MOCK_IP_ADDR_1,
- Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2
- )
- .found(),
- false
- );
- }
- #[test]
- fn test_replace() {
- let mut cache_storage = [Default::default(); 3];
- let mut cache = Cache::new(&mut cache_storage[..]);
- cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
- assert_eq!(
- cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
- Answer::Found(HADDR_A)
- );
- cache.fill(MOCK_IP_ADDR_1, HADDR_B, Instant::from_millis(0));
- assert_eq!(
- cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
- Answer::Found(HADDR_B)
- );
- }
- #[test]
- fn test_cache_gc() {
- let mut cache = Cache::new_with_limit(BTreeMap::new(), 2);
- cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(100));
- cache.fill(MOCK_IP_ADDR_2, HADDR_B, Instant::from_millis(50));
- // Adding third item after the expiration of the previous
- // two should garbage collect
- cache.fill(
- MOCK_IP_ADDR_3,
- HADDR_C,
- Instant::from_millis(50) + Cache::ENTRY_LIFETIME * 2,
- );
- assert_eq!(cache.storage.len(), 1);
- assert_eq!(
- cache.lookup(
- &MOCK_IP_ADDR_3,
- Instant::from_millis(50) + Cache::ENTRY_LIFETIME * 2
- ),
- Answer::Found(HADDR_C)
- );
- }
- #[test]
- fn test_evict() {
- let mut cache_storage = [Default::default(); 3];
- let mut cache = Cache::new(&mut cache_storage[..]);
- cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(100));
- cache.fill(MOCK_IP_ADDR_2, HADDR_B, Instant::from_millis(50));
- cache.fill(MOCK_IP_ADDR_3, HADDR_C, Instant::from_millis(200));
- assert_eq!(
- cache.lookup(&MOCK_IP_ADDR_2, Instant::from_millis(1000)),
- Answer::Found(HADDR_B)
- );
- assert_eq!(
- cache
- .lookup(&MOCK_IP_ADDR_4, Instant::from_millis(1000))
- .found(),
- false
- );
- cache.fill(MOCK_IP_ADDR_4, HADDR_D, Instant::from_millis(300));
- assert_eq!(
- cache
- .lookup(&MOCK_IP_ADDR_2, Instant::from_millis(1000))
- .found(),
- false
- );
- assert_eq!(
- cache.lookup(&MOCK_IP_ADDR_4, Instant::from_millis(1000)),
- Answer::Found(HADDR_D)
- );
- }
- #[test]
- fn test_hush() {
- let mut cache_storage = [Default::default(); 3];
- let mut cache = Cache::new(&mut cache_storage[..]);
- assert_eq!(
- cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(0)),
- Answer::NotFound
- );
- cache.limit_rate(Instant::from_millis(0));
- assert_eq!(
- cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(100)),
- Answer::RateLimited
- );
- assert_eq!(
- cache.lookup(&MOCK_IP_ADDR_1, Instant::from_millis(2000)),
- Answer::NotFound
- );
- }
- }
|