Rewrite the ARP cache to allow for flood protection and expiration.
parent
2a52234d53
commit
eaa50d0003
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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(ð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<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),
|
||||
ð_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)]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue