Implement an ARP LRU cache.

This commit is contained in:
whitequark 2016-12-12 02:39:46 +00:00
parent 2e80d69384
commit d862512582
7 changed files with 217 additions and 6 deletions

128
src/iface/arp_cache.rs Normal file
View File

@ -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<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
/// 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<usize> {
// 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<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 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));
}
}

39
src/iface/ethernet.rs Normal file
View File

@ -0,0 +1,39 @@
use phy::Device;
use wire::EthernetAddress;
use super::{ProtocolAddress, ArpCache};
/// An Ethernet network interface.
#[derive(Debug)]
pub struct Interface<DeviceT: Device, ArpCacheT: ArpCache> {
device: DeviceT,
arp_cache: ArpCacheT,
hardware_addr: EthernetAddress,
}
impl<DeviceT: Device, ArpCacheT: ArpCache> Interface<DeviceT, ArpCacheT> {
/// 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<DeviceT, ArpCacheT> {
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
}
}

31
src/iface/mod.rs Normal file
View File

@ -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
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -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 {