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

v0.7.x
whitequark 2017-11-21 10:52:53 +00:00
parent 2a52234d53
commit eaa50d0003
10 changed files with 245 additions and 225 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)]

View File

@ -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
src/iface/neighbor.rs Normal file
View File

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