From d8625125824c040f16e0b666e59a75b5ae82e870 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 12 Dec 2016 02:39:46 +0000 Subject: [PATCH] Implement an ARP LRU cache. --- src/iface/arp_cache.rs | 128 +++++++++++++++++++++++++++++++++++++++++ src/iface/ethernet.rs | 39 +++++++++++++ src/iface/mod.rs | 31 ++++++++++ src/lib.rs | 3 +- src/phy/mod.rs | 6 +- src/wire/ethernet.rs | 14 ++++- src/wire/ipv4.rs | 2 +- 7 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 src/iface/arp_cache.rs create mode 100644 src/iface/ethernet.rs create mode 100644 src/iface/mod.rs diff --git a/src/iface/arp_cache.rs b/src/iface/arp_cache.rs new file mode 100644 index 0000000..8bcfb85 --- /dev/null +++ b/src/iface/arp_cache.rs @@ -0,0 +1,128 @@ +use wire::EthernetAddress; +use super::ProtocolAddress; + +/// An Address Resolution Protocol cache. +/// +/// This cache 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: ProtocolAddress, hardware_addr: EthernetAddress); + + /// Look up the hardware address corresponding for the given protocol address. + fn lookup(&mut self, protocol_addr: ProtocolAddress) -> 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 +/// This cache can be created as: +/// +/// ```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: &'a mut [(ProtocolAddress, 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: &'a mut [(ProtocolAddress, EthernetAddress, usize)]) -> SliceCache<'a> { + 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: ProtocolAddress) -> Option { + // The order of comparison is important: any valid ProtocolAddress should + // sort before ProtocolAddress::Invalid. + self.storage.binary_search_by_key(&protocol_addr, |&(key, _, _)| key).ok() + } + + /// Sort entries in an order suitable for `find`. + fn sort(&mut self) { + self.storage.sort_by_key(|&(key, _, _)| key) + } + + /// 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: ProtocolAddress, hardware_addr: EthernetAddress) { + if let None = self.find(protocol_addr) { + self.storage[self.lru()] = (protocol_addr, hardware_addr, self.counter); + self.sort() + } + } + + fn lookup(&mut self, protocol_addr: ProtocolAddress) -> 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 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: ProtocolAddress = ProtocolAddress::ipv4([0, 0, 0, 0]); + const PADDR_B: ProtocolAddress = ProtocolAddress::ipv4([0, 0, 0, 1]); + const PADDR_C: ProtocolAddress = ProtocolAddress::ipv4([0, 0, 0, 2]); + const PADDR_D: ProtocolAddress = ProtocolAddress::ipv4([0, 0, 0, 3]); + + #[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 new file mode 100644 index 0000000..c00a003 --- /dev/null +++ b/src/iface/ethernet.rs @@ -0,0 +1,39 @@ +use phy::Device; +use wire::EthernetAddress; +use super::{ProtocolAddress, ArpCache}; + +/// An Ethernet network interface. +#[derive(Debug)] +pub struct Interface { + device: DeviceT, + arp_cache: ArpCacheT, + hardware_addr: EthernetAddress, +} + +impl Interface { + /// Create a network interface using the provided network device. + /// + /// The newly created interface uses hardware address `00-00-00-00-00-00` and + /// has no assigned protocol addresses. + pub fn new(device: DeviceT, arp_cache: ArpCacheT) -> Interface { + Interface { + device: device, + arp_cache: arp_cache, + hardware_addr: EthernetAddress([0x00; 6]) + } + } + + /// Get the hardware address of the interface. + pub fn hardware_addr(&self) -> EthernetAddress { + self.hardware_addr + } + + /// Set the hardware address of the interface. + /// + /// # Panics + /// This function panics if `addr` is not unicast. + pub fn set_hardware_addr(&mut self, addr: EthernetAddress) { + if addr.is_multicast() { panic!("hardware address should be unicast") } + self.hardware_addr = addr + } +} diff --git a/src/iface/mod.rs b/src/iface/mod.rs new file mode 100644 index 0000000..4060db9 --- /dev/null +++ b/src/iface/mod.rs @@ -0,0 +1,31 @@ +//! Network interface logic. +//! +//! The `iface` module deals with the *network interfaces*. It filters incoming frames, +//! provides lookup and caching of hardware addresses, and handles management packets. +use wire; + +mod arp_cache; +mod ethernet; + +pub use self::arp_cache::Cache as ArpCache; +pub use self::arp_cache::SliceCache as SliceArpCache; +pub use self::ethernet::Interface as EthernetInterface; + +/// An internetworking protocol address. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub enum ProtocolAddress { + Invalid, + Ipv4(wire::Ipv4Address) +} + +impl ProtocolAddress { + pub const fn ipv4(bytes: [u8; 4]) -> ProtocolAddress { + ProtocolAddress::Ipv4(wire::Ipv4Address(bytes)) + } +} + +impl Default for ProtocolAddress { + fn default() -> ProtocolAddress { + ProtocolAddress::Invalid + } +} diff --git a/src/lib.rs b/src/lib.rs index c3b5fb4..1462f0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(range_contains, associated_consts)] +#![feature(range_contains, associated_consts, const_fn)] #![no_std] extern crate byteorder; @@ -11,3 +11,4 @@ extern crate libc; pub mod phy; pub mod wire; +pub mod iface; diff --git a/src/phy/mod.rs b/src/phy/mod.rs index f3ef449..e53c9fd 100644 --- a/src/phy/mod.rs +++ b/src/phy/mod.rs @@ -1,9 +1,9 @@ //! Access to networking hardware. //! -//! The `phy` module provides an interface for sending and receiving frames -//! through a physical (or perhaps virtualized) network device, [Device](trait.Device.html), +//! The `phy` module deals with the *network devices*. It provides an interface +//! for transmitting and receiving frames, [Device](trait.Device.html), //! as well as an implementations of that trait that uses the host OS, -//! [OsDevice](struct.OsDevice.html). +//! [RawSocket](struct.RawSocket.html) and [TapInterface](struct.TapInterface.html). #[cfg(feature = "std")] mod sys; diff --git a/src/wire/ethernet.rs b/src/wire/ethernet.rs index 9a9f7a4..07a57a7 100644 --- a/src/wire/ethernet.rs +++ b/src/wire/ethernet.rs @@ -22,10 +22,12 @@ impl fmt::Display for EtherType { } /// A six-octet Ethernet II address. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] pub struct Address(pub [u8; 6]); impl Address { + pub const BROADCAST: Address = Address([0xff; 6]); + /// Construct an Ethernet address from a sequence of octets, in big-endian. /// /// # Panics @@ -40,6 +42,16 @@ impl Address { pub fn as_bytes(&self) -> &[u8] { &self.0 } + + /// Query whether the "multicast" bit in the OUI is set. + pub fn is_multicast(&self) -> bool { + self.0[0] & 0x01 != 0 + } + + /// Query whether the "locally administered" bit in the OUI is set. + pub fn is_local(&self) -> bool { + self.0[0] & 0x02 != 0 + } } impl fmt::Display for Address { diff --git a/src/wire/ipv4.rs b/src/wire/ipv4.rs index 992662c..d5f94cc 100644 --- a/src/wire/ipv4.rs +++ b/src/wire/ipv4.rs @@ -1,7 +1,7 @@ use core::fmt; /// A four-octet IPv4 address. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] pub struct Address(pub [u8; 4]); impl Address {