Pārlūkot izejas kodu

Rewrite the ARP cache to allow for flood protection and expiration.

whitequark 7 gadi atpakaļ
vecāks
revīzija
34c3a8c905
10 mainītis faili ar 245 papildinājumiem un 225 dzēšanām
  1. 6 1
      Cargo.toml
  2. 2 1
      README.md
  3. 4 4
      examples/client.rs
  4. 4 5
      examples/loopback.rs
  5. 4 4
      examples/ping.rs
  6. 4 4
      examples/server.rs
  7. 0 171
      src/iface/arp_cache.rs
  8. 32 32
      src/iface/ethernet.rs
  9. 4 3
      src/iface/mod.rs
  10. 185 0
      src/iface/neighbor.rs

+ 6 - 1
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"

+ 2 - 1
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.
 

+ 4 - 4
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<ArpCache>,
-        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);

+ 4 - 5
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

+ 4 - 4
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<ArpCache>,
-        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);

+ 4 - 4
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<ArpCache>,
-        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);

+ 0 - 171
src/iface/arp_cache.rs

@@ -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<EthernetAddress>;
-}
-
-/// 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<T>(storage: T) -> SliceCache<'a>
-            where T: Into<ManagedSlice<'a, (IpAddress, EthernetAddress, usize)>> {
-        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<usize> {
-        // 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<EthernetAddress> {
-        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));
-    }
-}
-

+ 32 - 32
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<Ipv4Address>,
@@ -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<ArpCacheMT, ProtocolAddrsMT, Ipv4GatewayAddrT>
-              (device: DeviceT, arp_cache: ArpCacheMT,
+    pub fn new<ProtocolAddrsMT, Ipv4GatewayAddrT>
+              (device: DeviceT,
+               neighbor_cache: NeighborCache<'b>,
                ethernet_addr: EthernetAddress,
                ip_addrs: ProtocolAddrsMT,
                ipv4_gateway: Ipv4GatewayAddrT) ->
               Interface<'b, 'c, DeviceT>
-            where ArpCacheMT: Into<Managed<'b, ArpCache>>,
-                  ProtocolAddrsMT: Into<ManagedSlice<'c, IpCidr>>,
+            where ProtocolAddrsMT: Into<ManagedSlice<'c, IpCidr>>,
                   Ipv4GatewayAddrT: Into<Option<Ipv4Address>>, {
         let ip_addrs = ip_addrs.into();
         InterfaceInner::check_ethernet_addr(&ethernet_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(&eth_frame),
+                self.process_arp(timestamp, &eth_frame),
             EthernetProtocol::Ipv4 =>
                 self.process_ipv4(sockets, timestamp, &eth_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<Packet<'frame>>
     {
         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<Packet<'frame>>
     {
@@ -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),
-                                &eth_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<ArpCache>,
-            EthernetAddress::default(), [ip_addr], None), SocketSet::new(vec![]))
+        (EthernetInterface::new(device, neighbor_cache,
+            EthernetAddress::default(), [ip_addr], None),
+            SocketSet::new(vec![]))
     }
 
     #[derive(Debug, PartialEq)]

+ 4 - 3
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;

+ 185 - 0
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<T>(storage: T) -> Cache<'a>
+            where T: Into<ManagedMap<'a, IpAddress, Neighbor>> {
+        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<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: 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);
+    }
+}
+