From 34c3a8c905f6877d1e900197627b026b93abdd5d Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Nov 2017 10:52:53 +0000 Subject: [PATCH] Rewrite the ARP cache to allow for flood protection and expiration. --- Cargo.toml | 7 +- README.md | 3 +- examples/client.rs | 8 +- examples/loopback.rs | 9 +- examples/ping.rs | 8 +- examples/server.rs | 8 +- src/iface/arp_cache.rs | 171 ------------------------------------- src/iface/ethernet.rs | 64 +++++++------- src/iface/mod.rs | 7 +- src/iface/neighbor.rs | 185 +++++++++++++++++++++++++++++++++++++++++ 10 files changed, 245 insertions(+), 225 deletions(-) delete mode 100644 src/iface/arp_cache.rs create mode 100644 src/iface/neighbor.rs diff --git a/Cargo.toml b/Cargo.toml index 9d9539e7..90f2a922 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,15 @@ license = "0BSD" [dependencies] byteorder = { version = "1.0", default-features = false } -managed = { version = "0.4.0", default-features = false } log = { version = "0.3", default-features = false, optional = true } libc = { version = "0.2.18", optional = true } +[dependencies.managed] +git = "https://github.com/m-labs/rust-managed.git" +rev = "629a6786a1cf1692015f464ed16c04eafa5cb8d1" +default-features = false +features = ["map"] + [dev-dependencies] log = "0.3" env_logger = "0.4" diff --git a/README.md b/README.md index 2200b43c..2e39b80f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ The only supported medium is Ethernet. * Regular Ethernet II frames are supported. * Unicast and broadcast packets are supported, multicast packets are **not** supported. * ARP packets (including gratuitous requests and replies) are supported. - * ARP rate limiting and cache expiration is **not** supported. + * ARP requests are sent at a rate not exceeding one per second. + * Cached ARP entries expire after one minute. * 802.3 frames and 802.1Q are **not** supported. * Jumbo frames are **not** supported. diff --git a/examples/client.rs b/examples/client.rs index 667c0899..11ffb2d0 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -7,11 +7,12 @@ extern crate smoltcp; mod utils; use std::str::{self, FromStr}; +use std::collections::BTreeMap; use std::time::Instant; use std::os::unix::io::AsRawFd; use smoltcp::phy::wait as phy_wait; use smoltcp::wire::{EthernetAddress, Ipv4Address, IpAddress, IpCidr}; -use smoltcp::iface::{ArpCache, SliceArpCache, EthernetInterface}; +use smoltcp::iface::{NeighborCache, EthernetInterface}; use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer}; fn main() { @@ -32,7 +33,7 @@ fn main() { let startup_time = Instant::now(); - let arp_cache = SliceArpCache::new(vec![Default::default(); 8]); + let neighbor_cache = NeighborCache::new(BTreeMap::new()); let tcp_rx_buffer = TcpSocketBuffer::new(vec![0; 64]); let tcp_tx_buffer = TcpSocketBuffer::new(vec![0; 128]); @@ -42,8 +43,7 @@ fn main() { let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 2), 24)]; let default_v4_gw = Ipv4Address::new(192, 168, 69, 100); let mut iface = EthernetInterface::new( - device, Box::new(arp_cache) as Box, - ethernet_addr, ip_addrs, Some(default_v4_gw)); + device, neighbor_cache, ethernet_addr, ip_addrs, Some(default_v4_gw)); let mut sockets = SocketSet::new(vec![]); let tcp_handle = sockets.add(tcp_socket); diff --git a/examples/loopback.rs b/examples/loopback.rs index c263c6fb..332c7fce 100644 --- a/examples/loopback.rs +++ b/examples/loopback.rs @@ -18,7 +18,7 @@ mod utils; use core::str; use smoltcp::phy::Loopback; use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr}; -use smoltcp::iface::{ArpCache, SliceArpCache, EthernetInterface}; +use smoltcp::iface::{NeighborCache, EthernetInterface}; use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer}; #[cfg(not(feature = "std"))] @@ -85,13 +85,12 @@ fn main() { device }; - let mut arp_cache_entries: [_; 8] = Default::default(); - let mut arp_cache = SliceArpCache::new(&mut arp_cache_entries[..]); + let mut neighbor_cache_entries = [None; 8]; + let mut neighbor_cache = NeighborCache::new(&mut neighbor_cache_entries[..]); let mut ip_addrs = [IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8)]; let mut iface = EthernetInterface::new( - device, &mut arp_cache as &mut ArpCache, - EthernetAddress::default(), &mut ip_addrs[..], None); + device, neighbor_cache, EthernetAddress::default(), &mut ip_addrs[..], None); let server_socket = { // It is not strictly necessary to use a `static mut` and unsafe code here, but diff --git a/examples/ping.rs b/examples/ping.rs index f19d341e..b5a9ea1d 100644 --- a/examples/ping.rs +++ b/examples/ping.rs @@ -8,13 +8,14 @@ extern crate byteorder; mod utils; use std::str::FromStr; +use std::collections::BTreeMap; use std::time::Instant; use std::os::unix::io::AsRawFd; use smoltcp::phy::Device; use smoltcp::phy::wait as phy_wait; use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Icmpv4Repr, Icmpv4Packet}; -use smoltcp::iface::{ArpCache, SliceArpCache, EthernetInterface}; +use smoltcp::iface::{NeighborCache, EthernetInterface}; use smoltcp::socket::{SocketSet, IcmpSocket, IcmpSocketBuffer, IcmpPacketBuffer, IcmpEndpoint}; use std::collections::HashMap; use byteorder::{ByteOrder, NetworkEndian}; @@ -45,7 +46,7 @@ fn main() { let startup_time = Instant::now(); - let arp_cache = SliceArpCache::new(vec![Default::default(); 8]); + let neighbor_cache = NeighborCache::new(BTreeMap::new()); let remote_addr = address; let local_addr = Ipv4Address::new(192, 168, 69, 1); @@ -58,8 +59,7 @@ fn main() { let ip_addr = IpCidr::new(IpAddress::from(local_addr), 24); let default_v4_gw = Ipv4Address::new(192, 168, 69, 100); let mut iface = EthernetInterface::new( - device, Box::new(arp_cache) as Box, - ethernet_addr, [ip_addr], Some(default_v4_gw)); + device, neighbor_cache, ethernet_addr, [ip_addr], Some(default_v4_gw)); let mut sockets = SocketSet::new(vec![]); let icmp_handle = sockets.add(icmp_socket); diff --git a/examples/server.rs b/examples/server.rs index 70bcab4e..30c131d6 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -7,12 +7,13 @@ extern crate smoltcp; mod utils; use std::str; +use std::collections::BTreeMap; use std::fmt::Write; use std::time::Instant; use std::os::unix::io::AsRawFd; use smoltcp::phy::wait as phy_wait; use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr}; -use smoltcp::iface::{ArpCache, SliceArpCache, EthernetInterface}; +use smoltcp::iface::{NeighborCache, EthernetInterface}; use smoltcp::socket::SocketSet; use smoltcp::socket::{UdpSocket, UdpSocketBuffer, UdpPacketBuffer}; use smoltcp::socket::{TcpSocket, TcpSocketBuffer}; @@ -31,7 +32,7 @@ fn main() { let startup_time = Instant::now(); - let arp_cache = SliceArpCache::new(vec![Default::default(); 8]); + let neighbor_cache = NeighborCache::new(BTreeMap::new()); let udp_rx_buffer = UdpSocketBuffer::new(vec![UdpPacketBuffer::new(vec![0; 64])]); let udp_tx_buffer = UdpSocketBuffer::new(vec![UdpPacketBuffer::new(vec![0; 128])]); @@ -56,8 +57,7 @@ fn main() { let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]); let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)]; let mut iface = EthernetInterface::new( - device, Box::new(arp_cache) as Box, - ethernet_addr, ip_addrs, None); + device, neighbor_cache, ethernet_addr, ip_addrs, None); let mut sockets = SocketSet::new(vec![]); let udp_handle = sockets.add(udp_socket); diff --git a/src/iface/arp_cache.rs b/src/iface/arp_cache.rs deleted file mode 100644 index 6aec8e63..00000000 --- a/src/iface/arp_cache.rs +++ /dev/null @@ -1,171 +0,0 @@ -use managed::ManagedSlice; - -use wire::{EthernetAddress, IpAddress}; - -/// An Address Resolution Protocol cache. -/// -/// This interface maps protocol addresses to hardware addresses. -pub trait Cache { - /// Update the cache to map given protocol address to given hardware address. - fn fill(&mut self, protocol_addr: &IpAddress, hardware_addr: &EthernetAddress); - - /// Look up the hardware address corresponding for the given protocol address. - fn lookup(&mut self, protocol_addr: &IpAddress) -> Option; -} - -/// An Address Resolution Protocol cache backed by a slice. -/// -/// This cache uses a fixed-size storage, binary search, and a least recently used -/// eviction strategy. -/// -/// # Examples -/// -/// On systems with heap, this cache can be created with: -/// ```rust -/// use smoltcp::iface::SliceArpCache; -/// let mut arp_cache = SliceArpCache::new(vec![Default::default(); 8]); -/// ``` -/// -/// On systems without heap, use: -/// ```rust -/// use smoltcp::iface::SliceArpCache; -/// let mut arp_cache_storage = [Default::default(); 8]; -/// let mut arp_cache = SliceArpCache::new(&mut arp_cache_storage[..]); -/// ``` -pub struct SliceCache<'a> { - storage: ManagedSlice<'a, (IpAddress, EthernetAddress, usize)>, - counter: usize -} - -impl<'a> SliceCache<'a> { - /// Create a cache. The backing storage is cleared upon creation. - /// - /// # Panics - /// This function panics if `storage.len() == 0`. - pub fn new(storage: T) -> SliceCache<'a> - where T: Into> { - let mut storage = storage.into(); - if storage.len() == 0 { - panic!("ARP slice cache created with empty storage") - } - - for elem in storage.iter_mut() { - *elem = Default::default() - } - SliceCache { - storage: storage, - counter: 0 - } - } - - /// Find an entry for the given protocol address, if any. - fn find(&self, protocol_addr: &IpAddress) -> Option { - // The order of comparison is important: any valid IpAddress should - // sort before IpAddress::Invalid. - self.storage.binary_search_by_key(protocol_addr, |&(key, _, _)| key).ok() - } - - /// Sort entries in an order suitable for `find`. - fn sort(&mut self) { - #[cfg(feature = "std")] - fn sort(data: &mut [(IpAddress, EthernetAddress, usize)]) { - data.sort_by_key(|&(key, _, _)| key) - } - - #[cfg(not(feature = "std"))] - fn sort(data: &mut [(IpAddress, EthernetAddress, usize)]) { - // Use an insertion sort, which performs best on 10 elements and less. - for i in 1..data.len() { - let mut j = i; - while j > 0 && data[j-1].0 > data[j].0 { - data.swap(j, j - 1); - j = j - 1; - } - } - } - - sort(&mut self.storage) - } - - /// Find the least recently used entry. - fn lru(&self) -> usize { - self.storage.iter().enumerate().min_by_key(|&(_, &(_, _, counter))| counter).unwrap().0 - } -} - -impl<'a> Cache for SliceCache<'a> { - fn fill(&mut self, protocol_addr: &IpAddress, hardware_addr: &EthernetAddress) { - debug_assert!(protocol_addr.is_unicast()); - debug_assert!(hardware_addr.is_unicast()); - - if let None = self.find(protocol_addr) { - let lru_index = self.lru(); - - if net_log_enabled!(trace) { - let (old_protocol_addr, old_hardware_addr, _counter) = self.storage[lru_index]; - if !old_protocol_addr.is_unspecified() { - net_trace!("evicting {} => {}", old_protocol_addr, old_hardware_addr); - } - net_trace!("filling {} => {}", protocol_addr, hardware_addr); - } - - self.counter += 1; - self.storage[lru_index] = - (*protocol_addr, *hardware_addr, self.counter); - self.sort() - } - } - - fn lookup(&mut self, protocol_addr: &IpAddress) -> Option { - if let Some(index) = self.find(protocol_addr) { - let (_protocol_addr, hardware_addr, ref mut counter) = self.storage[index]; - self.counter += 1; - *counter = self.counter; - Some(hardware_addr) - } else { - None - } - } -} - -#[cfg(test)] -mod test { - use wire::Ipv4Address; - use super::*; - - 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]); - - const PADDR_A: IpAddress = IpAddress::Ipv4(Ipv4Address([1, 0, 0, 1])); - const PADDR_B: IpAddress = IpAddress::Ipv4(Ipv4Address([1, 0, 0, 2])); - const PADDR_C: IpAddress = IpAddress::Ipv4(Ipv4Address([1, 0, 0, 3])); - const PADDR_D: IpAddress = IpAddress::Ipv4(Ipv4Address([1, 0, 0, 4])); - - #[test] - fn test_slice_cache() { - let mut cache_storage = [Default::default(); 3]; - let mut cache = SliceCache::new(&mut cache_storage[..]); - - cache.fill(&PADDR_A, &HADDR_A); - assert_eq!(cache.lookup(&PADDR_A), Some(HADDR_A)); - assert_eq!(cache.lookup(&PADDR_B), None); - - cache.fill(&PADDR_B, &HADDR_B); - cache.fill(&PADDR_C, &HADDR_C); - assert_eq!(cache.lookup(&PADDR_A), Some(HADDR_A)); - assert_eq!(cache.lookup(&PADDR_B), Some(HADDR_B)); - assert_eq!(cache.lookup(&PADDR_C), Some(HADDR_C)); - - cache.lookup(&PADDR_B); - cache.lookup(&PADDR_A); - cache.lookup(&PADDR_C); - cache.fill(&PADDR_D, &HADDR_D); - assert_eq!(cache.lookup(&PADDR_A), Some(HADDR_A)); - assert_eq!(cache.lookup(&PADDR_B), None); - assert_eq!(cache.lookup(&PADDR_C), Some(HADDR_C)); - assert_eq!(cache.lookup(&PADDR_D), Some(HADDR_D)); - } -} - diff --git a/src/iface/ethernet.rs b/src/iface/ethernet.rs index 51ce7ec0..2aad951d 100644 --- a/src/iface/ethernet.rs +++ b/src/iface/ethernet.rs @@ -2,7 +2,7 @@ // of RFC 1122 that discuss Ethernet, ARP and IP. use core::cmp; -use managed::{Managed, ManagedSlice}; +use managed::ManagedSlice; use {Error, Result}; use phy::{Device, DeviceCapabilities, RxToken, TxToken}; @@ -26,7 +26,7 @@ use socket::IcmpSocket; use socket::UdpSocket; #[cfg(feature = "socket-tcp")] use socket::TcpSocket; -use super::ArpCache; +use super::{NeighborCache, NeighborAnswer}; /// An Ethernet network interface. /// @@ -46,7 +46,7 @@ pub struct Interface<'b, 'c, DeviceT: for<'d> Device<'d>> { /// methods on the `Interface` in this time (since its `device` field is borrowed /// exclusively). However, it is still possible to call methods on its `inner` field. struct InterfaceInner<'b, 'c> { - arp_cache: Managed<'b, ArpCache>, + neighbor_cache: NeighborCache<'b>, ethernet_addr: EthernetAddress, ip_addrs: ManagedSlice<'c, IpCidr>, ipv4_gateway: Option, @@ -73,23 +73,21 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT> /// # Panics /// See the restrictions on [set_hardware_addr](#method.set_hardware_addr) /// and [set_protocol_addrs](#method.set_protocol_addrs) functions. - pub fn new - (device: DeviceT, arp_cache: ArpCacheMT, + pub fn new + (device: DeviceT, + neighbor_cache: NeighborCache<'b>, ethernet_addr: EthernetAddress, ip_addrs: ProtocolAddrsMT, ipv4_gateway: Ipv4GatewayAddrT) -> Interface<'b, 'c, DeviceT> - where ArpCacheMT: Into>, - ProtocolAddrsMT: Into>, + where ProtocolAddrsMT: Into>, Ipv4GatewayAddrT: Into>, { let ip_addrs = ip_addrs.into(); InterfaceInner::check_ethernet_addr(ðernet_addr); InterfaceInner::check_ip_addrs(&ip_addrs); let inner = InterfaceInner { - ethernet_addr, - ip_addrs, - arp_cache: arp_cache.into(), + ethernet_addr, ip_addrs, neighbor_cache, ipv4_gateway: ipv4_gateway.into(), device_capabilities: device.capabilities(), }; @@ -301,7 +299,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { match eth_frame.ethertype() { EthernetProtocol::Arp => - self.process_arp(ð_frame), + self.process_arp(timestamp, ð_frame), EthernetProtocol::Ipv4 => self.process_ipv4(sockets, timestamp, ð_frame), // Drop all other traffic. @@ -310,7 +308,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { } fn process_arp<'frame, T: AsRef<[u8]>> - (&mut self, eth_frame: &EthernetFrame<&'frame T>) -> + (&mut self, timestamp: u64, eth_frame: &EthernetFrame<&'frame T>) -> Result> { let arp_packet = ArpPacket::new_checked(eth_frame.payload())?; @@ -324,8 +322,9 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { operation, source_hardware_addr, source_protocol_addr, target_protocol_addr, .. } => { if source_protocol_addr.is_unicast() && source_hardware_addr.is_unicast() { - self.arp_cache.fill(&source_protocol_addr.into(), - &source_hardware_addr); + self.neighbor_cache.fill(source_protocol_addr.into(), + source_hardware_addr, + timestamp); } else { // Discard packets with non-unicast source addresses. net_debug!("non-unicast source address"); @@ -350,7 +349,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { } fn process_ipv4<'frame, T: AsRef<[u8]>> - (&mut self, sockets: &mut SocketSet, _timestamp: u64, + (&mut self, sockets: &mut SocketSet, timestamp: u64, eth_frame: &EthernetFrame<&'frame T>) -> Result> { @@ -366,8 +365,9 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { if eth_frame.src_addr().is_unicast() { // Fill the ARP cache from IP header of unicast frames. - self.arp_cache.fill(&IpAddress::Ipv4(ipv4_repr.src_addr), - ð_frame.src_addr()); + self.neighbor_cache.fill(IpAddress::Ipv4(ipv4_repr.src_addr), + eth_frame.src_addr(), + timestamp); } let ip_repr = IpRepr::Ipv4(ipv4_repr); @@ -406,7 +406,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { #[cfg(feature = "socket-tcp")] IpProtocol::Tcp => - self.process_tcp(sockets, _timestamp, ip_repr, ip_payload), + self.process_tcp(sockets, timestamp, ip_repr, ip_payload), #[cfg(feature = "socket-raw")] _ if handled_by_raw_socket => @@ -678,12 +678,12 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { { let dst_addr = self.route(dst_addr)?; - if let Some(hardware_addr) = self.arp_cache.lookup(&dst_addr) { - return Ok((hardware_addr,tx_token)) - } - - if dst_addr.is_broadcast() { - return Ok((EthernetAddress::BROADCAST, tx_token)) + match self.neighbor_cache.lookup(&dst_addr, timestamp) { + NeighborAnswer::Found(hardware_addr) => + return Ok((hardware_addr, tx_token)), + NeighborAnswer::Hushed => + return Err(Error::Unaddressable), + NeighborAnswer::NotFound => (), } match (src_addr, dst_addr) { @@ -740,10 +740,10 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { #[cfg(test)] mod test { - use std::boxed::Box; + use std::collections::BTreeMap; use {Result, Error}; - use iface::{ArpCache, SliceArpCache, EthernetInterface}; + use iface::{NeighborCache, EthernetInterface}; use phy::{self, Loopback, ChecksumCapabilities}; use socket::SocketSet; use wire::{ArpOperation, ArpPacket, ArpRepr}; @@ -755,17 +755,17 @@ mod test { use super::Packet; - fn create_loopback<'a, 'b>() -> - (EthernetInterface<'static, 'b, Loopback>, SocketSet<'static, 'a, 'b>) { + fn create_loopback<'a, 'b>() -> (EthernetInterface<'static, 'b, Loopback>, + SocketSet<'static, 'a, 'b>) { // Create a basic device let device = Loopback::new(); - let arp_cache = SliceArpCache::new(vec![Default::default(); 8]); + let neighbor_cache = NeighborCache::new(BTreeMap::new()); let ip_addr = IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8); - (EthernetInterface::new( - device, Box::new(arp_cache) as Box, - EthernetAddress::default(), [ip_addr], None), SocketSet::new(vec![])) + (EthernetInterface::new(device, neighbor_cache, + EthernetAddress::default(), [ip_addr], None), + SocketSet::new(vec![])) } #[derive(Debug, PartialEq)] diff --git a/src/iface/mod.rs b/src/iface/mod.rs index e8f0e50b..bd68c73b 100644 --- a/src/iface/mod.rs +++ b/src/iface/mod.rs @@ -3,9 +3,10 @@ //! The `iface` module deals with the *network interfaces*. It filters incoming frames, //! provides lookup and caching of hardware addresses, and handles management packets. -mod arp_cache; +mod neighbor; mod ethernet; -pub use self::arp_cache::Cache as ArpCache; -pub use self::arp_cache::SliceCache as SliceArpCache; +pub use self::neighbor::Neighbor as Neighbor; +pub(crate) use self::neighbor::Answer as NeighborAnswer; +pub use self::neighbor::Cache as NeighborCache; pub use self::ethernet::Interface as EthernetInterface; diff --git a/src/iface/neighbor.rs b/src/iface/neighbor.rs new file mode 100644 index 00000000..954d5068 --- /dev/null +++ b/src/iface/neighbor.rs @@ -0,0 +1,185 @@ +// 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}; + +/// 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: u64, +} + +/// 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. + Hushed +} + +/// 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>, + hushed_until: u64, +} + +impl<'a> Cache<'a> { + /// Flood protection delay, in milliseconds. + const FLOOD_TIMER: u64 = 1_000; + + /// Neighbor entry lifetime, in milliseconds. + const ENTRY_LIFETIME: u64 = 60_000; + + /// Create a cache. The backing storage is cleared upon creation. + /// + /// # Panics + /// This function panics if `storage.len() == 0`. + pub fn new(storage: T) -> Cache<'a> + where T: Into> { + let mut storage = storage.into(); + storage.clear(); + + Cache { storage, hushed_until: 0 } + } + + pub(crate) fn fill(&mut self, protocol_addr: IpAddress, hardware_addr: EthernetAddress, + timestamp: u64) { + debug_assert!(protocol_addr.is_unicast()); + debug_assert!(hardware_addr.is_unicast()); + + 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 {} => {}", protocol_addr, hardware_addr); + } + Err(_) => unreachable!() + } + } + + pub(crate) fn lookup_pure(&self, protocol_addr: &IpAddress, timestamp: u64) -> + Option { + 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: u64) -> Answer { + match self.lookup_pure(protocol_addr, timestamp) { + Some(hardware_addr) => + Answer::Found(hardware_addr), + None if timestamp < self.hushed_until => + Answer::Hushed, + None => { + self.hushed_until = timestamp + Self::FLOOD_TIMER; + Answer::NotFound + } + } + } +} + +#[cfg(test)] +mod test { + use wire::Ipv4Address; + use super::*; + + const HADDR_A: EthernetAddress = EthernetAddress([0, 0, 0, 0, 0, 1]); + const HADDR_B: EthernetAddress = EthernetAddress([0, 0, 0, 0, 0, 2]); + + const PADDR_A: IpAddress = IpAddress::Ipv4(Ipv4Address([1, 0, 0, 1])); + const PADDR_B: IpAddress = IpAddress::Ipv4(Ipv4Address([1, 0, 0, 2])); + + #[test] + fn test_fill() { + let mut cache_storage = [Default::default(); 3]; + let mut cache = Cache::new(&mut cache_storage[..]); + + assert_eq!(cache.lookup_pure(&PADDR_A, 0), None); + assert_eq!(cache.lookup_pure(&PADDR_B, 0), None); + + cache.fill(PADDR_A, HADDR_A, 0); + assert_eq!(cache.lookup_pure(&PADDR_A, 0), Some(HADDR_A)); + assert_eq!(cache.lookup_pure(&PADDR_B, 0), None); + assert_eq!(cache.lookup_pure(&PADDR_A, 2 * Cache::ENTRY_LIFETIME), None); + + cache.fill(PADDR_A, HADDR_A, 0); + assert_eq!(cache.lookup_pure(&PADDR_B, 0), None); + } + + #[test] + fn test_expire() { + let mut cache_storage = [Default::default(); 3]; + let mut cache = Cache::new(&mut cache_storage[..]); + + cache.fill(PADDR_A, HADDR_A, 0); + assert_eq!(cache.lookup_pure(&PADDR_A, 0), Some(HADDR_A)); + assert_eq!(cache.lookup_pure(&PADDR_A, 2 * Cache::ENTRY_LIFETIME), None); + } + + #[test] + fn test_replace() { + let mut cache_storage = [Default::default(); 3]; + let mut cache = Cache::new(&mut cache_storage[..]); + + cache.fill(PADDR_A, HADDR_A, 0); + assert_eq!(cache.lookup_pure(&PADDR_A, 0), Some(HADDR_A)); + cache.fill(PADDR_A, HADDR_B, 0); + assert_eq!(cache.lookup_pure(&PADDR_A, 0), Some(HADDR_B)); + } + + #[test] + fn test_hush() { + let mut cache_storage = [Default::default(); 3]; + let mut cache = Cache::new(&mut cache_storage[..]); + + assert_eq!(cache.lookup(&PADDR_A, 0), Answer::NotFound); + assert_eq!(cache.lookup(&PADDR_A, 100), Answer::Hushed); + assert_eq!(cache.lookup(&PADDR_A, 2000), Answer::NotFound); + } +} +