123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- // Heads up! Before working on this file you should read, at least,
- // the parts of RFC 1122 that discuss ARP.
- use managed::ManagedMap;
- use wire::{EthernetAddress, IpAddress};
- use time::{Duration, Instant};
- #[cfg(any(feature = "std", feature = "alloc"))]
- use core::mem;
- /// 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)]
- pub struct Neighbor {
- hardware_addr: EthernetAddress,
- expires_at: Instant,
- }
- /// An answer to a neighbor cache lookup.
- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
- 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
- }
- /// 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.into_iter()
- .map(|(key, value)| (*key, *value))
- .filter(|(_, v)| timestamp < v.expires_at)
- .collect();
- mem::replace(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_pure(&self, protocol_addr: &IpAddress, timestamp: Instant) ->
- Option<EthernetAddress> {
- if protocol_addr.is_broadcast() {
- return Some(EthernetAddress::BROADCAST)
- }
- match self.storage.get(protocol_addr) {
- Some(&Neighbor { expires_at, hardware_addr }) => {
- if timestamp < expires_at {
- return Some(hardware_addr)
- }
- }
- None => ()
- }
- None
- }
- pub(crate) fn lookup(&mut self, protocol_addr: &IpAddress, timestamp: Instant) -> Answer {
- match self.lookup_pure(protocol_addr, timestamp) {
- Some(hardware_addr) =>
- Answer::Found(hardware_addr),
- None if timestamp < self.silent_until =>
- Answer::RateLimited,
- None => {
- self.silent_until = timestamp + Self::SILENT_TIME;
- Answer::NotFound
- }
- }
- }
- }
- #[cfg(test)]
- mod test {
- use super::*;
- use std::collections::BTreeMap;
- use wire::ip::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2, MOCK_IP_ADDR_3, MOCK_IP_ADDR_4};
- 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_pure(&MOCK_IP_ADDR_1, Instant::from_millis(0)), None);
- assert_eq!(cache.lookup_pure(&MOCK_IP_ADDR_2, Instant::from_millis(0)), None);
- cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
- assert_eq!(cache.lookup_pure(&MOCK_IP_ADDR_1, Instant::from_millis(0)), Some(HADDR_A));
- assert_eq!(cache.lookup_pure(&MOCK_IP_ADDR_2, Instant::from_millis(0)), None);
- assert_eq!(cache.lookup_pure(&MOCK_IP_ADDR_1, Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2),
- None);
- cache.fill(MOCK_IP_ADDR_1, HADDR_A, Instant::from_millis(0));
- assert_eq!(cache.lookup_pure(&MOCK_IP_ADDR_2, Instant::from_millis(0)), None);
- }
- #[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_pure(&MOCK_IP_ADDR_1, Instant::from_millis(0)), Some(HADDR_A));
- assert_eq!(cache.lookup_pure(&MOCK_IP_ADDR_1, Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2),
- None);
- }
- #[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_pure(&MOCK_IP_ADDR_1, Instant::from_millis(0)), Some(HADDR_A));
- cache.fill(MOCK_IP_ADDR_1, HADDR_B, Instant::from_millis(0));
- assert_eq!(cache.lookup_pure(&MOCK_IP_ADDR_1, Instant::from_millis(0)), Some(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_pure(&MOCK_IP_ADDR_3, Instant::from_millis(50) + Cache::ENTRY_LIFETIME * 2), Some(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_pure(&MOCK_IP_ADDR_2, Instant::from_millis(1000)), Some(HADDR_B));
- assert_eq!(cache.lookup_pure(&MOCK_IP_ADDR_4, Instant::from_millis(1000)), None);
- cache.fill(MOCK_IP_ADDR_4, HADDR_D, Instant::from_millis(300));
- assert_eq!(cache.lookup_pure(&MOCK_IP_ADDR_2, Instant::from_millis(1000)), None);
- assert_eq!(cache.lookup_pure(&MOCK_IP_ADDR_4, Instant::from_millis(1000)), Some(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);
- 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);
- }
- }
|