blob: 03b7b992faeffd09fd29336716fde465dd28c71a [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! The Address Resolution Protocol (ARP).
use alloc::collections::hash_map::Entry;
use alloc::collections::HashMap;
use core::hash::Hash;
use core::marker::PhantomData;
use core::mem;
use core::time::Duration;
use log::{debug, error};
use net_types::{ethernet::Mac, ip::Ipv4Addr};
use never::Never;
use packet::{BufferMut, EmptyBuf, InnerPacketBuilder};
use zerocopy::{AsBytes, FromBytes, Unaligned};
use crate::context::{
CounterContext, FrameContext, FrameHandler, StateContext, TimerContext, TimerHandler,
};
use crate::device::ethernet::EtherType;
use crate::device::link::{BroadcastLinkAddress, LinkDevice};
use crate::wire::arp::{ArpPacket, ArpPacketBuilder};
/// The type of an ARP operation.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[allow(missing_docs)]
#[repr(u16)]
pub(crate) enum ArpOp {
Request = ArpOp::REQUEST,
Response = ArpOp::RESPONSE,
}
impl ArpOp {
const REQUEST: u16 = 0x0001;
const RESPONSE: u16 = 0x0002;
/// Construct an `ArpOp` from a `u16`.
///
/// `from_u16` returns the `ArpOp` with the numerical value `u`, or `None`
/// if the value is unrecognized.
pub(crate) fn from_u16(u: u16) -> Option<ArpOp> {
match u {
Self::REQUEST => Some(ArpOp::Request),
Self::RESPONSE => Some(ArpOp::Response),
_ => None,
}
}
}
/// A trait to represent an ARP hardware type.
pub(crate) trait HType: BroadcastLinkAddress + Hash + Eq {
/// The hardware type.
const HTYPE: ArpHardwareType;
/// The in-memory size of an instance of the type.
const HLEN: u8;
}
/// A trait to represent an ARP protocol type.
pub(crate) trait PType: FromBytes + AsBytes + Unaligned + Copy + Clone + Hash + Eq {
/// The protocol type.
const PTYPE: EtherType;
/// The in-memory size of an instance of the type.
const PLEN: u8;
}
impl HType for Mac {
const HTYPE: ArpHardwareType = ArpHardwareType::Ethernet;
const HLEN: u8 = mem::size_of::<Mac>() as u8;
}
impl PType for Ipv4Addr {
const PTYPE: EtherType = EtherType::Ipv4;
const PLEN: u8 = mem::size_of::<Ipv4Addr>() as u8;
}
/// An ARP hardware protocol.
#[derive(Debug, PartialEq)]
#[allow(missing_docs)]
#[repr(u16)]
pub(crate) enum ArpHardwareType {
Ethernet = ArpHardwareType::ETHERNET,
}
impl ArpHardwareType {
const ETHERNET: u16 = 0x0001;
/// Construct an `ArpHardwareType` from a `u16`.
///
/// `from_u16` returns the `ArpHardwareType` with the numerical value `u`,
/// or `None` if the value is unrecognized.
pub(crate) fn from_u16(u: u16) -> Option<ArpHardwareType> {
match u {
Self::ETHERNET => Some(ArpHardwareType::Ethernet),
_ => None,
}
}
}
// NOTE(joshlf): This may seem a bit odd. Why not just say that `ArpDevice` is a
// sub-trait of `L: LinkDevice` where `L::Address: HType`? Unfortunately, rustc
// is still pretty bad at reasoning about where clauses. In a (never published)
// earlier version of this code, I tried that approach. Even simple function
// signatures like `fn foo<D: ArpDevice, P: PType, C: ArpContext<D, P>>()` were
// too much for rustc to handle. Even without trying to actually use the
// associated `Address` type, that function signature alone would cause rustc to
// complain that it wasn't guaranteed that `D::Address: HType`.
//
// Doing it this way instead sidesteps the problem by taking the `where` clause
// out of the definition of `ArpDevice`. It's still present in the blanket impl,
// but rustc seems OK with that.
/// A link device whose addressing scheme is supported by ARP.
///
/// `ArpDevice` is implemented for all `L: LinkDevice where L::Address: HType`.
pub(crate) trait ArpDevice {
type HType: HType;
}
impl<L: LinkDevice> ArpDevice for L
where
L::Address: HType,
{
type HType = L::Address;
}
/// The identifier for timer events in the ARP layer.
///
/// This is used to retry sending ARP requests and to expire existing ARP table
/// entries. It is parametric on a device ID type, `D`, and a network protocol
/// type, `P`.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) struct ArpTimerId<D: ArpDevice, P: PType, DeviceId> {
device_id: DeviceId,
inner: ArpTimerIdInner<P>,
_marker: PhantomData<D>,
}
impl<D: ArpDevice, P: PType, DeviceId> ArpTimerId<D, P, DeviceId> {
fn new(device_id: DeviceId, inner: ArpTimerIdInner<P>) -> ArpTimerId<D, P, DeviceId> {
ArpTimerId { device_id, inner, _marker: PhantomData }
}
fn new_request_retry(device_id: DeviceId, proto_addr: P) -> ArpTimerId<D, P, DeviceId> {
ArpTimerId::new(device_id, ArpTimerIdInner::RequestRetry { proto_addr })
}
fn new_entry_expiration(device_id: DeviceId, proto_addr: P) -> ArpTimerId<D, P, DeviceId> {
ArpTimerId::new(device_id, ArpTimerIdInner::EntryExpiration { proto_addr })
}
pub(crate) fn get_device_id(&self) -> &DeviceId {
&self.device_id
}
}
/// The metadata associated with an ARP frame.
pub(crate) struct ArpFrameMetadata<D: ArpDevice, DeviceId> {
/// The ID of the ARP device.
pub(super) device_id: DeviceId,
/// The destination hardware address.
pub(super) dst_addr: D::HType,
}
/// Cleans up state associated with the device.
///
/// Equivalent to calling `ctx.deinitialize`.
///
/// See [`ArpHandler::deinitialize`] for more details.
pub(super) fn deinitialize<D: ArpDevice, P: PType, H: ArpHandler<D, P>>(
ctx: &mut H,
device_id: H::DeviceId,
) {
ctx.deinitialize(device_id)
}
/// An execution context for the ARP protocol when a buffer is provided.
///
/// `BufferArpContext` is like [`ArpContext`], except that it also requires that
/// the context be capable of receiving frames in buffers of type `B`. This is
/// used when a buffer of type `B` is provided to ARP (in particular, in
/// [`receive_arp_packet`]), and allows ARP to reuse that buffer rather than
/// needing to always allocate a new one.
pub(super) trait BufferArpContext<D: ArpDevice, P: PType, B: BufferMut>:
ArpContext<D, P> + FrameContext<B, ArpFrameMetadata<D, <Self as ArpDeviceIdContext<D>>::DeviceId>>
{
}
impl<
D: ArpDevice,
P: PType,
B: BufferMut,
C: ArpContext<D, P>
+ FrameContext<B, ArpFrameMetadata<D, <Self as ArpDeviceIdContext<D>>::DeviceId>>,
> BufferArpContext<D, P, B> for C
{
}
// NOTE(joshlf): The `ArpDevice` parameter may seem unnecessary. We only ever
// use the associated `HType` type, so why not just take that directly? By the
// same token, why have it as a parameter on `ArpState`, `ArpTimerId`, and
// `ArpFrameMetadata`? The answer is that, if we did, there would be no way to
// distinguish between different link device protocols that all happened to use
// the same hardware addressing scheme.
//
// Consider that the way that we implement context traits is via blanket impls.
// Even though each module's code _feels_ isolated from the rest of the system,
// in reality, all context impls end up on the same context type. In particular,
// all impls are of the form `impl<C: SomeContextTrait> SomeOtherContextTrait
// for C`. The `C` is the same throughout the whole stack.
//
// Thus, for two different link device protocols with the same `HType` and
// `PType`, if we used an `HType` parameter rather than an `ArpDevice`
// parameter, the `ArpContext` impls would conflict (in fact, the
// `StateContext`, `TimerContext`, and `FrameContext` impls would all conflict
// for similar reasons).
/// An execution context which provides a `DeviceId` type for ARP internals to share.
pub(crate) trait ArpDeviceIdContext<D: ArpDevice> {
/// An ID that identifies a particular device.
type DeviceId: Copy + PartialEq;
}
/// An execution context for the ARP protocol.
pub(crate) trait ArpContext<D: ArpDevice, P: PType>:
ArpDeviceIdContext<D>
+ StateContext<ArpState<D, P>, <Self as ArpDeviceIdContext<D>>::DeviceId>
+ TimerContext<ArpTimerId<D, P, <Self as ArpDeviceIdContext<D>>::DeviceId>>
+ FrameContext<EmptyBuf, ArpFrameMetadata<D, <Self as ArpDeviceIdContext<D>>::DeviceId>>
+ CounterContext
{
/// Get a protocol address of this interface.
///
/// If `device_id` does not have any addresses associated with it, return `None`.
fn get_protocol_addr(&self, device_id: Self::DeviceId) -> Option<P>;
/// Get the hardware address of this interface.
fn get_hardware_addr(&self, device_id: Self::DeviceId) -> D::HType;
/// Notifies the device layer that the hardware address `hw_addr` was
/// resolved for the given protocol address `proto_addr`.
fn address_resolved(&mut self, device_id: Self::DeviceId, proto_addr: P, hw_addr: D::HType);
/// Notifies the device layer that the hardware address resolution for the
/// given protocol address `proto_addr` failed.
fn address_resolution_failed(&mut self, device_id: Self::DeviceId, proto_addr: P);
/// Notifies the device layer that a previously-cached resolution entry has
/// expired, and is no longer valid.
fn address_resolution_expired(&mut self, device_id: Self::DeviceId, proto_addr: P);
}
/// An ARP packet handler.
///
/// The protocol and device types (`P` and `D` respectively) must be set
/// statically. Unless there is only one valid pair of protocol and hardware
/// types in a given context, it is the caller's responsibility to call
/// `peek_arp_types` in order to determine which types to use when referencing
/// a specific `ArpPacketHandler` implementation.
pub(super) trait ArpPacketHandler<D: ArpDevice, P: PType, B: BufferMut>:
ArpHandler<D, P>
{
/// Receive an ARP packet from a device.
fn receive_arp_packet(&mut self, device_id: Self::DeviceId, buffer: B);
}
impl<D: ArpDevice, P: PType, B: BufferMut, C: BufferArpContext<D, P, B>> ArpPacketHandler<D, P, B>
for C
{
fn receive_arp_packet(&mut self, device_id: Self::DeviceId, buffer: B) {
ArpTimerFrameHandler::handle_frame(self, device_id, (), buffer)
}
}
/// An ARP handler for ARP Events.
///
/// `ArpHandler<D, P>` is implemented for any type which implements [`ArpContext<D, P>`],
/// and it can also be mocked for use in testing.
pub(super) trait ArpHandler<D: ArpDevice, P: PType>:
ArpDeviceIdContext<D> + TimerHandler<ArpTimerId<D, P, <Self as ArpDeviceIdContext<D>>::DeviceId>>
{
/// Cleans up state associated with the device.
///
/// The contract is that after `deinitialize` is called, nothing else should be done
/// with the state.
fn deinitialize(&mut self, device_id: Self::DeviceId);
/// Look up the hardware address for a network protocol address.
fn lookup(
&mut self,
device_id: Self::DeviceId,
local_addr: D::HType,
lookup_addr: P,
) -> Option<D::HType>;
/// Insert a static entry into this device's ARP table.
///
/// This will cause any conflicting dynamic entry to be removed, and any future
/// conflicting gratuitous ARPs to be ignored.
// TODO(rheacock): remove `cfg(test)` when this is used.
#[cfg(test)]
fn insert_static_neighbor(&mut self, device_id: Self::DeviceId, addr: P, hw: D::HType);
}
impl<D: ArpDevice, P: PType, C: ArpContext<D, P>> ArpHandler<D, P> for C {
fn deinitialize(&mut self, device_id: Self::DeviceId) {
// Remove all timers associated with the device
self.cancel_timers_with(|timer_id| *timer_id.get_device_id() == device_id);
// TODO(rheacock): Send any immediate packets, and potentially flag the state as
// uninitialized?
}
fn lookup(
&mut self,
device_id: Self::DeviceId,
_local_addr: D::HType,
lookup_addr: P,
) -> Option<D::HType> {
let result = self.get_state_with(device_id).table.lookup(lookup_addr).cloned();
// Send an ARP Request if the address is not in our cache
if result.is_none() {
send_arp_request(self, device_id, lookup_addr);
}
result
}
#[cfg(test)]
fn insert_static_neighbor(&mut self, device_id: Self::DeviceId, addr: P, hw: D::HType) {
// Cancel any outstanding timers for this entry; if none exist, these will
// be no-ops.
let outstanding_request =
self.cancel_timer(ArpTimerId::new_request_retry(device_id, addr)).is_some();
self.cancel_timer(ArpTimerId::new_entry_expiration(device_id, addr));
// If there was an outstanding resolution request, notify the device layer
// that it's been resolved.
if outstanding_request {
self.address_resolved(device_id, addr, hw);
}
self.get_state_mut_with(device_id).table.insert_static(addr, hw);
}
}
/// Handle an ARP timer firing.
///
/// Equivalent to calling `ctx.handle_timer`.
pub(super) fn handle_timer<D: ArpDevice, P: PType, H: ArpHandler<D, P>>(
ctx: &mut H,
id: ArpTimerId<D, P, H::DeviceId>,
) {
ctx.handle_timer(id)
}
/// A handler for ARP events.
///
/// This type cannot be constructed, and is only meant to be used at the type
/// level. We implement `TimerHandler` and `FrameHandler` for `ArpTimerFrameHandler`
/// rather than just provide the top-level `handle_timer` and `receive_frame`
/// functions so that `ArpTimerFrameHandler` can be used in tests with the
/// `DummyTimerContextExt` trait and with the `DummyNetwork` type.
struct ArpTimerFrameHandler<D: ArpDevice, P> {
_marker: PhantomData<(D, P)>,
_never: Never,
}
impl<D: ArpDevice, P: PType, C: ArpContext<D, P>> TimerHandler<ArpTimerId<D, P, C::DeviceId>>
for C
{
fn handle_timer(&mut self, id: ArpTimerId<D, P, C::DeviceId>) {
match id.inner {
ArpTimerIdInner::RequestRetry { proto_addr } => {
send_arp_request(self, id.device_id, proto_addr)
}
ArpTimerIdInner::EntryExpiration { proto_addr } => {
self.get_state_mut_with(id.device_id).table.remove(proto_addr);
self.address_resolution_expired(id.device_id, proto_addr);
// There are several things to notice:
// - Unlike when we send an ARP request in response to a lookup,
// here we don't schedule a retry timer, so the request will
// be sent only once.
// - This is best-effort in the sense that the protocol is still
// correct if we don't manage to send an ARP request or
// receive an ARP response.
// - The point of doing this is just to make it more likely for
// our ARP cache to stay up to date; it's not actually a
// requirement of the protocol. Note that the RFC does say "It
// may be desirable to have table aging and/or timers".
if let Some(sender_protocol_addr) = self.get_protocol_addr(id.device_id) {
let self_hw_addr = self.get_hardware_addr(id.device_id);
// TODO(joshlf): Do something if send_frame returns an error?
let _ = self.send_frame(
ArpFrameMetadata { device_id: id.device_id, dst_addr: D::HType::BROADCAST },
ArpPacketBuilder::new(
ArpOp::Request,
self_hw_addr,
sender_protocol_addr,
// This is meaningless, since RFC 826 does not
// specify the behaviour. However, the broadcast
// address is sensible, as this is the actual
// address we are sending the packet to.
D::HType::BROADCAST,
proto_addr,
)
.into_serializer(),
);
}
}
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
enum ArpTimerIdInner<P: PType> {
RequestRetry { proto_addr: P },
EntryExpiration { proto_addr: P },
}
/// Receive an ARP packet from a device.
///
/// Equivalent to calling `ctx.receive_arp_packet`.
pub(super) fn receive_arp_packet<
D: ArpDevice,
P: PType,
B: BufferMut,
H: ArpPacketHandler<D, P, B>,
>(
ctx: &mut H,
device_id: H::DeviceId,
buffer: B,
) {
ctx.receive_arp_packet(device_id, buffer)
}
impl<D: ArpDevice, P: PType, B: BufferMut, C: BufferArpContext<D, P, B>>
FrameHandler<C, C::DeviceId, (), B> for ArpTimerFrameHandler<D, P>
{
fn handle_frame(ctx: &mut C, device_id: C::DeviceId, _meta: (), mut buffer: B) {
// TODO(wesleyac) Add support for probe.
let packet = match buffer.parse::<ArpPacket<_, D::HType, P>>() {
Ok(packet) => packet,
Err(err) => {
// If parse failed, it's because either the packet was malformed, or
// it was for an unexpected hardware or network protocol. In either
// case, we just drop the packet and move on. RFC 826's "Packet
// Reception" section says of packet processing algorithm, "Negative
// conditionals indicate an end of processing and a discarding of
// the packet."
debug!("discarding malformed ARP packet: {}", err);
return;
}
};
let addressed_to_me =
Some(packet.target_protocol_address()) == ctx.get_protocol_addr(device_id);
// The following logic is equivalent to the "ARP, Proxy ARP, and Gratuitous
// ARP" section of RFC 2002.
// Gratuitous ARPs, which have the same sender and target address, need to
// be handled separately since they do not send a response.
if packet.sender_protocol_address() == packet.target_protocol_address() {
insert_dynamic(
ctx,
device_id,
packet.sender_protocol_address(),
packet.sender_hardware_address(),
);
// If we have an outstanding retry timer for this host, we should cancel
// it since we now have the mapping in cache.
ctx.cancel_timer(ArpTimerId::new_request_retry(
device_id,
packet.sender_protocol_address(),
));
ctx.increment_counter("arp::rx_gratuitous_resolve");
// Notify device layer:
ctx.address_resolved(
device_id,
packet.sender_protocol_address(),
packet.sender_hardware_address(),
);
return;
}
// The following logic is equivalent to the "Packet Reception" section of
// RFC 826.
//
// We statically know that the hardware type and protocol type are correct,
// so we do not need to have additional code to check that. The remainder of
// the algorithm is:
//
// Merge_flag := false
// If the pair <protocol type, sender protocol address> is
// already in my translation table, update the sender
// hardware address field of the entry with the new
// information in the packet and set Merge_flag to true.
// ?Am I the target protocol address?
// Yes:
// If Merge_flag is false, add the triplet <protocol type,
// sender protocol address, sender hardware address> to
// the translation table.
// ?Is the opcode ares_op$REQUEST? (NOW look at the opcode!!)
// Yes:
// Swap hardware and protocol fields, putting the local
// hardware and protocol addresses in the sender fields.
// Set the ar$op field to ares_op$REPLY
// Send the packet to the (new) target hardware address on
// the same hardware on which the request was received.
//
// This can be summed up as follows:
//
// +----------+---------------+---------------+-----------------------------+
// | opcode | Am I the TPA? | SPA in table? | action |
// +----------+---------------+---------------+-----------------------------+
// | REQUEST | yes | yes | Update table, Send response |
// | REQUEST | yes | no | Update table, Send response |
// | REQUEST | no | yes | Update table |
// | REQUEST | no | no | NOP |
// | RESPONSE | yes | yes | Update table |
// | RESPONSE | yes | no | Update table |
// | RESPONSE | no | yes | Update table |
// | RESPONSE | no | no | NOP |
// +----------+---------------+---------------+-----------------------------+
//
// Given that the semantics of ArpTable is that inserting and updating an
// entry are the same, this can be implemented with two if statements (one
// to update the table, and one to send a response).
if addressed_to_me
|| ctx
.get_state_with(device_id)
.table
.lookup(packet.sender_protocol_address())
.is_some()
{
insert_dynamic(
ctx,
device_id,
packet.sender_protocol_address(),
packet.sender_hardware_address(),
);
// Since we just got the protocol -> hardware address mapping, we can
// cancel a timer to resend a request.
ctx.cancel_timer(ArpTimerId::new_request_retry(
device_id,
packet.sender_protocol_address(),
));
ctx.increment_counter("arp::rx_resolve");
// Notify device layer:
ctx.address_resolved(
device_id,
packet.sender_protocol_address(),
packet.sender_hardware_address(),
);
}
if addressed_to_me && packet.operation() == ArpOp::Request {
let self_hw_addr = ctx.get_hardware_addr(device_id);
ctx.increment_counter("arp::rx_request");
// TODO(joshlf): Do something if send_frame returns an error?
let _ = ctx.send_frame(
ArpFrameMetadata { device_id, dst_addr: packet.sender_hardware_address() },
ArpPacketBuilder::new(
ArpOp::Response,
self_hw_addr,
packet.target_protocol_address(),
packet.sender_hardware_address(),
packet.sender_protocol_address(),
)
.into_serializer_with(buffer),
);
}
}
}
/// Insert a static entry into this device's ARP table.
///
/// Equivalent to calling `ctx.insert_static_neighbor`.
///
/// See [`ArpHandler::insert_static_neighbor`] for more details.
// TODO(rheacock): remove `cfg(test)` when this is used.
#[cfg(test)]
pub(super) fn insert_static_neighbor<D: ArpDevice, P: PType, H: ArpHandler<D, P>>(
ctx: &mut H,
device_id: H::DeviceId,
net: P,
hw: D::HType,
) {
ctx.insert_static_neighbor(device_id, net, hw)
}
/// Insert a dynamic entry into this device's ARP table.
///
/// The entry will potentially be overwritten by any future static entry and the
/// entry will not be successfully added into the table if there currently is a
/// static entry.
fn insert_dynamic<D: ArpDevice, P: PType, C: ArpContext<D, P>>(
ctx: &mut C,
device_id: C::DeviceId,
net: P,
hw: D::HType,
) {
// Let's extend the expiration deadline by rescheduling the timer. It is
// assumed that `schedule_timer` will first cancel the timer that is already
// there.
let expiration = ArpTimerId::new_entry_expiration(device_id, net);
if ctx.get_state_mut_with(device_id).table.insert_dynamic(net, hw) {
ctx.schedule_timer(DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD, expiration);
}
}
/// Look up the hardware address for a network protocol address.
///
/// Equivalent to calling `ctx.lookup`.
pub(super) fn lookup<D: ArpDevice, P: PType, H: ArpHandler<D, P>>(
ctx: &mut H,
device_id: H::DeviceId,
local_addr: D::HType,
lookup_addr: P,
) -> Option<D::HType> {
ctx.lookup(device_id, local_addr, lookup_addr)
}
// Since BSD resends ARP requests every 20 seconds and sets the default time
// limit to establish a TCP connection as 75 seconds, 4 is used as the max
// number of tries, which is the initial remaining_tries.
const DEFAULT_ARP_REQUEST_MAX_TRIES: usize = 4;
// Currently at 20 seconds because that's what FreeBSD does.
const DEFAULT_ARP_REQUEST_PERIOD: Duration = Duration::from_secs(20);
// Based on standard implementations, 60 seconds is quite usual to expire an ARP
// entry.
const DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD: Duration = Duration::from_secs(60);
fn send_arp_request<D: ArpDevice, P: PType, C: ArpContext<D, P>>(
ctx: &mut C,
device_id: C::DeviceId,
lookup_addr: P,
) {
let tries_remaining = ctx
.get_state_mut_with(device_id)
.table
.get_remaining_tries(lookup_addr)
.unwrap_or(DEFAULT_ARP_REQUEST_MAX_TRIES);
if let Some(sender_protocol_addr) = ctx.get_protocol_addr(device_id) {
let self_hw_addr = ctx.get_hardware_addr(device_id);
// TODO(joshlf): Do something if send_frame returns an error?
let _ = ctx.send_frame(
ArpFrameMetadata { device_id, dst_addr: D::HType::BROADCAST },
ArpPacketBuilder::new(
ArpOp::Request,
self_hw_addr,
sender_protocol_addr,
// This is meaningless, since RFC 826 does not specify the
// behaviour. However, the broadcast address is sensible, as
// this is the actual address we are sending the packet to.
D::HType::BROADCAST,
lookup_addr,
)
.into_serializer(),
);
let id = ArpTimerId::new_request_retry(device_id, lookup_addr);
if tries_remaining > 1 {
// TODO(wesleyac): Configurable timer.
ctx.schedule_timer(DEFAULT_ARP_REQUEST_PERIOD, id);
ctx.get_state_mut_with(device_id).table.set_waiting(lookup_addr, tries_remaining - 1);
} else {
ctx.cancel_timer(id);
ctx.get_state_mut_with(device_id).table.remove(lookup_addr);
ctx.address_resolution_failed(device_id, lookup_addr);
}
} else {
// RFC 826 does not specify what to do if we don't have a local address,
// but there is no reasonable way to send an ARP request without one (as
// the receiver will cache our local address on receiving the packet.
// So, if this is the case, we do not send an ARP request.
// TODO(wesleyac): Should we cache these, and send packets once we have
// an address?
debug!("Not sending ARP request, since we don't know our local protocol address");
}
}
/// The state associated with an instance of the Address Resolution Protocol
/// (ARP).
///
/// Each device will contain an `ArpState` object for each of the network
/// protocols that it supports.
pub(crate) struct ArpState<D: ArpDevice, P: PType + Hash + Eq> {
table: ArpTable<P, D::HType>,
}
impl<D: ArpDevice, P: PType + Hash + Eq> Default for ArpState<D, P> {
fn default() -> Self {
ArpState { table: ArpTable::default() }
}
}
struct ArpTable<P: Hash + Eq, H> {
table: HashMap<P, ArpValue<H>>,
}
#[derive(Debug, Eq, PartialEq)] // for testing
enum ArpValue<H> {
// invariant: no timers exist for this entry
_Static { hardware_addr: H },
// invariant: a single entry expiration timer exists for this entry
Dynamic { hardware_addr: H },
// invariant: a single request retry timer exists for this entry
Waiting { remaining_tries: usize },
}
impl<P: Hash + Eq, H> ArpTable<P, H> {
// TODO(rheacock): remove `cfg(test)` when this is used
#[cfg(test)]
fn insert_static(&mut self, net: P, hw: H) {
// a static entry overrides everything, so just insert it.
self.table.insert(net, ArpValue::_Static { hardware_addr: hw });
}
/// This function tries to insert a dynamic entry into the ArpTable, the
/// bool returned from the function is used to indicate whether the
/// insertion is successful.
fn insert_dynamic(&mut self, net: P, hw: H) -> bool {
// a dynamic entry should not override a static one, if that happens,
// don't do it. if we want to handle this kind of situation in the
// future, we can make this function return a `Result`
let new_val = ArpValue::Dynamic { hardware_addr: hw };
match self.table.entry(net) {
Entry::Occupied(ref mut entry) => {
let old_val = entry.get_mut();
match old_val {
ArpValue::_Static { .. } => {
error!("Conflicting ARP entries: please check your manual configuration and hosts in your local network");
return false;
}
_ => *old_val = new_val,
}
}
Entry::Vacant(entry) => {
entry.insert(new_val);
}
}
true
}
fn remove(&mut self, net: P) {
self.table.remove(&net);
}
fn get_remaining_tries(&self, net: P) -> Option<usize> {
if let Some(ArpValue::Waiting { remaining_tries }) = self.table.get(&net) {
Some(*remaining_tries)
} else {
None
}
}
fn set_waiting(&mut self, net: P, remaining_tries: usize) {
self.table.insert(net, ArpValue::Waiting { remaining_tries });
}
fn lookup(&self, addr: P) -> Option<&H> {
match self.table.get(&addr) {
Some(ArpValue::_Static { hardware_addr })
| Some(ArpValue::Dynamic { hardware_addr }) => Some(hardware_addr),
_ => None,
}
}
}
impl<P: Hash + Eq, H> Default for ArpTable<P, H> {
fn default() -> Self {
ArpTable { table: HashMap::default() }
}
}
#[cfg(test)]
mod tests {
use net_types::ethernet::Mac;
use net_types::ip::Ipv4Addr;
use packet::{ParseBuffer, Serializer};
use super::*;
use crate::context::testutil::{DummyInstant, DummyNetwork, DummyTimerContextExt};
use crate::context::InstantContext;
use crate::device::ethernet::{EtherType, EthernetLinkDevice};
use crate::wire::arp::{peek_arp_types, ArpPacketBuilder};
const TEST_LOCAL_IPV4: Ipv4Addr = Ipv4Addr::new([1, 2, 3, 4]);
const TEST_REMOTE_IPV4: Ipv4Addr = Ipv4Addr::new([5, 6, 7, 8]);
const TEST_LOCAL_MAC: Mac = Mac::new([0, 1, 2, 3, 4, 5]);
const TEST_REMOTE_MAC: Mac = Mac::new([6, 7, 8, 9, 10, 11]);
const TEST_INVALID_MAC: Mac = Mac::new([0, 0, 0, 0, 0, 0]);
/// A dummy `ArpContext` that stores frames, address resolution events, and
/// address resolution failure events.
struct DummyArpContext {
proto_addr: Option<Ipv4Addr>,
hw_addr: Mac,
addr_resolved: Vec<(Ipv4Addr, Mac)>,
addr_resolution_failed: Vec<Ipv4Addr>,
addr_resolution_expired: Vec<Ipv4Addr>,
arp_state: ArpState<EthernetLinkDevice, Ipv4Addr>,
}
impl Default for DummyArpContext {
fn default() -> DummyArpContext {
DummyArpContext {
proto_addr: Some(TEST_LOCAL_IPV4),
hw_addr: TEST_LOCAL_MAC,
addr_resolved: Vec::new(),
addr_resolution_failed: Vec::new(),
addr_resolution_expired: Vec::new(),
arp_state: ArpState::default(),
}
}
}
type DummyContext = crate::context::testutil::DummyContext<
DummyArpContext,
ArpTimerId<EthernetLinkDevice, Ipv4Addr, ()>,
ArpFrameMetadata<EthernetLinkDevice, ()>,
>;
impl ArpDeviceIdContext<EthernetLinkDevice> for DummyContext {
type DeviceId = ();
}
impl ArpContext<EthernetLinkDevice, Ipv4Addr> for DummyContext {
fn get_protocol_addr(&self, _device_id: ()) -> Option<Ipv4Addr> {
self.get_ref().proto_addr
}
fn get_hardware_addr(&self, _device_id: ()) -> Mac {
self.get_ref().hw_addr
}
fn address_resolved(&mut self, _device_id: (), proto_addr: Ipv4Addr, hw_addr: Mac) {
self.get_mut().addr_resolved.push((proto_addr, hw_addr));
}
fn address_resolution_failed(&mut self, _device_id: (), proto_addr: Ipv4Addr) {
self.get_mut().addr_resolution_failed.push(proto_addr);
}
fn address_resolution_expired(&mut self, _device_id: (), proto_addr: Ipv4Addr) {
self.get_mut().addr_resolution_expired.push(proto_addr);
}
}
impl StateContext<ArpState<EthernetLinkDevice, Ipv4Addr>> for DummyContext {
fn get_state_with(&self, _id: ()) -> &ArpState<EthernetLinkDevice, Ipv4Addr> {
&self.get_ref().arp_state
}
fn get_state_mut_with(&mut self, _id: ()) -> &mut ArpState<EthernetLinkDevice, Ipv4Addr> {
&mut self.get_mut().arp_state
}
}
fn send_arp_packet(
ctx: &mut DummyContext,
op: ArpOp,
sender_ipv4: Ipv4Addr,
target_ipv4: Ipv4Addr,
sender_mac: Mac,
target_mac: Mac,
) {
let buf = ArpPacketBuilder::new(op, sender_mac, sender_ipv4, target_mac, target_ipv4)
.into_serializer()
.serialize_vec_outer()
.unwrap();
let (hw, proto) = peek_arp_types(buf.as_ref()).unwrap();
assert_eq!(hw, crate::device::arp::ArpHardwareType::Ethernet);
assert_eq!(proto, EtherType::Ipv4);
receive_arp_packet::<_, Ipv4Addr, _, _>(ctx, (), buf);
}
// Validate that buf is an ARP packet with the specific op, local_ipv4,
// remote_ipv4, local_mac and remote_mac.
fn validate_arp_packet(
mut buf: &[u8],
op: ArpOp,
local_ipv4: Ipv4Addr,
remote_ipv4: Ipv4Addr,
local_mac: Mac,
remote_mac: Mac,
) {
let packet = buf.parse::<ArpPacket<_, Mac, Ipv4Addr>>().unwrap();
assert_eq!(packet.sender_hardware_address(), local_mac);
assert_eq!(packet.target_hardware_address(), remote_mac);
assert_eq!(packet.sender_protocol_address(), local_ipv4);
assert_eq!(packet.target_protocol_address(), remote_ipv4);
assert_eq!(packet.operation(), op);
}
// Validate that we've sent `total_frames` frames in total, and that the
// most recent one was sent to `dst` with the given ARP packet contents.
fn validate_last_arp_packet(
ctx: &DummyContext,
total_frames: usize,
dst: Mac,
op: ArpOp,
local_ipv4: Ipv4Addr,
remote_ipv4: Ipv4Addr,
local_mac: Mac,
remote_mac: Mac,
) {
assert_eq!(ctx.frames().len(), total_frames);
let (meta, frame) = &ctx.frames()[total_frames - 1];
assert_eq!(meta.dst_addr, dst);
validate_arp_packet(frame, op, local_ipv4, remote_ipv4, local_mac, remote_mac);
}
// Validate that `ctx` contains exactly one installed timer with the given
// instant and ID.
fn validate_single_timer(
ctx: &DummyContext,
instant: Duration,
id: ArpTimerId<EthernetLinkDevice, Ipv4Addr, ()>,
) {
assert_eq!(ctx.timers().as_slice(), [(DummyInstant::from(instant), id)]);
}
fn validate_single_retry_timer(ctx: &DummyContext, instant: Duration, addr: Ipv4Addr) {
validate_single_timer(ctx, instant, ArpTimerId::new_request_retry((), addr))
}
fn validate_single_entry_timer(ctx: &DummyContext, instant: Duration, addr: Ipv4Addr) {
validate_single_timer(ctx, instant, ArpTimerId::new_entry_expiration((), addr))
}
#[test]
fn test_receive_gratuitous_arp_request() {
// Test that, when we receive a gratuitous ARP request, we cache the
// sender's address information, and we do not send a response.
let mut ctx = DummyContext::default();
send_arp_packet(
&mut ctx,
ArpOp::Request,
TEST_REMOTE_IPV4,
TEST_REMOTE_IPV4,
TEST_REMOTE_MAC,
TEST_INVALID_MAC,
);
// We should have cached the sender's address information.
assert_eq!(
ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4).unwrap(),
&TEST_REMOTE_MAC
);
// Gratuitous ARPs should not prompt a response.
assert_eq!(ctx.frames().len(), 0);
}
#[test]
fn test_receive_gratuitous_arp_response() {
// Test that, when we receive a gratuitous ARP request, we cache the
// sender's address information, and we do not send a response.
let mut ctx = DummyContext::default();
send_arp_packet(
&mut ctx,
ArpOp::Response,
TEST_REMOTE_IPV4,
TEST_REMOTE_IPV4,
TEST_REMOTE_MAC,
TEST_REMOTE_MAC,
);
// We should have cached the sender's address information.
assert_eq!(
ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4).unwrap(),
&TEST_REMOTE_MAC
);
// Gratuitous ARPs should not send a response.
assert_eq!(ctx.frames().len(), 0);
}
#[test]
fn test_receive_gratuitous_arp_response_existing_request() {
// Test that, if we have an outstanding request retry timer and receive
// a gratuitous ARP for the same host, we cancel the timer and notify
// the device layer.
let mut ctx = DummyContext::default();
lookup(&mut ctx, (), TEST_LOCAL_MAC, TEST_REMOTE_IPV4);
// We should have installed a single retry timer.
validate_single_retry_timer(&ctx, DEFAULT_ARP_REQUEST_PERIOD, TEST_REMOTE_IPV4);
send_arp_packet(
&mut ctx,
ArpOp::Response,
TEST_REMOTE_IPV4,
TEST_REMOTE_IPV4,
TEST_REMOTE_MAC,
TEST_REMOTE_MAC,
);
// The response should now be in our cache.
assert_eq!(
ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4).unwrap(),
&TEST_REMOTE_MAC
);
// The retry timer should be canceled, and replaced by an entry
// expiration timer.
validate_single_entry_timer(&ctx, DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD, TEST_REMOTE_IPV4);
// We should have notified the device layer.
assert_eq!(ctx.get_ref().addr_resolved.as_slice(), [(TEST_REMOTE_IPV4, TEST_REMOTE_MAC)]);
// Gratuitous ARPs should not send a response (the 1 frame is for the
// original request).
assert_eq!(ctx.frames().len(), 1);
}
#[test]
fn test_cancel_timers_on_deinitialize() {
// Test that associated timers are cancelled when the arp device
// is deinitialized.
// Cancelling timers matches on the DeviceId, so setup a context that
// uses IDs. The test doesn't use the context functions, so it's okay
// that they return the same info.
type DummyContext2 = crate::context::testutil::DummyContext<
DummyArpContext,
ArpTimerId<EthernetLinkDevice, Ipv4Addr, usize>,
ArpFrameMetadata<EthernetLinkDevice, usize>,
>;
impl ArpDeviceIdContext<EthernetLinkDevice> for DummyContext2 {
type DeviceId = usize;
}
impl ArpContext<EthernetLinkDevice, Ipv4Addr> for DummyContext2 {
fn get_protocol_addr(&self, _device_id: usize) -> Option<Ipv4Addr> {
self.get_ref().proto_addr
}
fn get_hardware_addr(&self, _device_id: usize) -> Mac {
self.get_ref().hw_addr
}
fn address_resolved(&mut self, _device_id: usize, proto_addr: Ipv4Addr, hw_addr: Mac) {
self.get_mut().addr_resolved.push((proto_addr, hw_addr));
}
fn address_resolution_failed(&mut self, _device_id: usize, proto_addr: Ipv4Addr) {
self.get_mut().addr_resolution_failed.push(proto_addr);
}
fn address_resolution_expired(&mut self, _device_id: usize, proto_addr: Ipv4Addr) {
self.get_mut().addr_resolution_expired.push(proto_addr);
}
}
impl StateContext<ArpState<EthernetLinkDevice, Ipv4Addr>, usize> for DummyContext2 {
fn get_state_with(&self, _id: usize) -> &ArpState<EthernetLinkDevice, Ipv4Addr> {
&self.get_ref().arp_state
}
fn get_state_mut_with(
&mut self,
_id: usize,
) -> &mut ArpState<EthernetLinkDevice, Ipv4Addr> {
&mut self.get_mut().arp_state
}
}
// Setup up a dummy context and trigger a timer with a lookup
let mut ctx = DummyContext2::default();
let device_id_0: usize = 0;
let device_id_1: usize = 1;
lookup(&mut ctx, device_id_0, TEST_LOCAL_MAC, TEST_REMOTE_IPV4);
// We should have installed a single retry timer.
assert_eq!(ctx.timers().len(), 1);
// Deinitializing a different ID should not impact the current timer.
deinitialize(&mut ctx, device_id_1);
assert_eq!(ctx.timers().len(), 1);
// Deinitializing the correct ID should cancel the timer.
deinitialize(&mut ctx, device_id_0);
assert_eq!(ctx.timers().len(), 0);
}
#[test]
fn test_send_arp_request_on_cache_miss() {
// Test that, when we perform a lookup that fails, we send an ARP
// request and install a timer to retry.
let mut ctx = DummyContext::default();
// Perform the lookup.
lookup(&mut ctx, (), TEST_LOCAL_MAC, TEST_REMOTE_IPV4);
// We should have sent a single ARP request.
validate_last_arp_packet(
&ctx,
1,
Mac::BROADCAST,
ArpOp::Request,
TEST_LOCAL_IPV4,
TEST_REMOTE_IPV4,
TEST_LOCAL_MAC,
Mac::BROADCAST,
);
// We should have installed a single retry timer.
validate_single_retry_timer(&ctx, DEFAULT_ARP_REQUEST_PERIOD, TEST_REMOTE_IPV4);
// Test that, when we receive an ARP response, we cancel the timer.
send_arp_packet(
&mut ctx,
ArpOp::Response,
TEST_REMOTE_IPV4,
TEST_LOCAL_IPV4,
TEST_REMOTE_MAC,
TEST_LOCAL_MAC,
);
// The response should now be in our cache.
assert_eq!(
ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4).unwrap(),
&TEST_REMOTE_MAC
);
// We should have notified the device layer.
assert_eq!(ctx.get_ref().addr_resolved.as_slice(), [(TEST_REMOTE_IPV4, TEST_REMOTE_MAC)]);
// The retry timer should be canceled, and replaced by an entry
// expiration timer.
validate_single_entry_timer(&ctx, DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD, TEST_REMOTE_IPV4);
}
#[test]
fn test_no_arp_request_on_cache_hit() {
// Test that, when we perform a lookup that succeeds, we do not send an
// ARP request or install a retry timer.
let mut ctx = DummyContext::default();
// Perform a gratuitous ARP to populate the cache.
send_arp_packet(
&mut ctx,
ArpOp::Response,
TEST_REMOTE_IPV4,
TEST_REMOTE_IPV4,
TEST_REMOTE_MAC,
TEST_REMOTE_MAC,
);
// Perform the lookup.
lookup(&mut ctx, (), TEST_LOCAL_MAC, TEST_REMOTE_IPV4);
// We should not have sent any ARP request.
assert_eq!(ctx.frames().len(), 0);
// We should not have set a retry timer.
assert!(ctx.cancel_timer(ArpTimerId::new_request_retry((), TEST_REMOTE_IPV4)).is_none());
}
#[test]
fn test_exhaust_retries_arp_request() {
// Test that, after performing a certain number of ARP request retries,
// we give up and don't install another retry timer.
let mut ctx = DummyContext::default();
lookup(&mut ctx, (), TEST_LOCAL_MAC, TEST_REMOTE_IPV4);
// `i` represents the `i`th request, so we start it at 1 since we
// already sent one request during the call to `lookup`.
for i in 1..DEFAULT_ARP_REQUEST_MAX_TRIES {
// We should have sent i requests total. We have already validated
// all the rest, so only validate the most recent one.
validate_last_arp_packet(
&ctx,
i,
Mac::BROADCAST,
ArpOp::Request,
TEST_LOCAL_IPV4,
TEST_REMOTE_IPV4,
TEST_LOCAL_MAC,
Mac::BROADCAST,
);
// Check the number of remaining tries.
assert_eq!(
ctx.get_ref().arp_state.table.get_remaining_tries(TEST_REMOTE_IPV4).unwrap(),
DEFAULT_ARP_REQUEST_MAX_TRIES - i
);
// There should be a single ARP request retry timer installed.
validate_single_retry_timer(
&ctx,
// Duration only implements Mul<u32>
DEFAULT_ARP_REQUEST_PERIOD * (i as u32),
TEST_REMOTE_IPV4,
);
// Trigger the ARP request retry timer.
assert!(ctx.trigger_next_timer());
}
// We should have sent DEFAULT_ARP_REQUEST_MAX_TRIES requests total. We
// have already validated all the rest, so only validate the most recent
// one.
validate_last_arp_packet(
&ctx,
DEFAULT_ARP_REQUEST_MAX_TRIES,
Mac::BROADCAST,
ArpOp::Request,
TEST_LOCAL_IPV4,
TEST_REMOTE_IPV4,
TEST_LOCAL_MAC,
Mac::BROADCAST,
);
// There shouldn't be any timers installed.
assert_eq!(ctx.timers().len(), 0);
// The table entry should have been completely removed.
assert!(ctx.get_ref().arp_state.table.table.get(&TEST_REMOTE_IPV4).is_none());
// We should have notified the device layer of the failure.
assert_eq!(ctx.get_ref().addr_resolution_failed.as_slice(), [TEST_REMOTE_IPV4]);
}
#[test]
fn test_handle_arp_request() {
// Test that, when we receive an ARP request, we cache the sender's
// address information and send an ARP response.
let mut ctx = DummyContext::default();
send_arp_packet(
&mut ctx,
ArpOp::Request,
TEST_REMOTE_IPV4,
TEST_LOCAL_IPV4,
TEST_REMOTE_MAC,
TEST_LOCAL_MAC,
);
// Make sure we cached the sender's address information.
assert_eq!(
lookup(&mut ctx, (), TEST_LOCAL_MAC, TEST_REMOTE_IPV4).unwrap(),
TEST_REMOTE_MAC
);
// We should have sent an ARP response.
validate_last_arp_packet(
&ctx,
1,
TEST_REMOTE_MAC,
ArpOp::Response,
TEST_LOCAL_IPV4,
TEST_REMOTE_IPV4,
TEST_LOCAL_MAC,
TEST_REMOTE_MAC,
);
}
#[test]
fn test_arp_table() {
let mut t: ArpTable<Ipv4Addr, Mac> = ArpTable::default();
assert_eq!(t.lookup(Ipv4Addr::new([10, 0, 0, 1])), None);
t.insert_dynamic(Ipv4Addr::new([10, 0, 0, 1]), Mac::new([1, 2, 3, 4, 5, 6]));
assert_eq!(*t.lookup(Ipv4Addr::new([10, 0, 0, 1])).unwrap(), Mac::new([1, 2, 3, 4, 5, 6]));
assert_eq!(t.lookup(Ipv4Addr::new([10, 0, 0, 2])), None);
}
#[test]
fn test_address_resolution() {
// Test a basic ARP resolution scenario with both sides participating.
// We expect the following steps:
// 1. When a lookup is performed and results in a cache miss, we send an
// ARP request and set a request retry timer.
// 2. When the remote receives the request, it populates its cache with
// the local's information, and sends an ARP reply.
// 3. When the reply is received, the timer is canceled, the table is
// updated, a new entry expiration timer is installed, and the device
// layer is notified of the resolution.
let local = DummyContext::default();
let mut remote = DummyContext::default();
remote.get_mut().hw_addr = TEST_REMOTE_MAC;
remote.get_mut().proto_addr = Some(TEST_REMOTE_IPV4);
let mut network = DummyNetwork::new(
vec![("local", local), ("remote", remote)],
|ctx, _state: &DummyArpContext, _meta| {
if ctx == "local" {
("remote", (), (), None)
} else {
("local", (), (), None)
}
},
);
// The lookup should fail.
assert!(lookup(network.context("local"), (), TEST_LOCAL_MAC, TEST_REMOTE_IPV4).is_none());
// We should have sent an ARP request.
validate_last_arp_packet(
network.context("local"),
1,
Mac::BROADCAST,
ArpOp::Request,
TEST_LOCAL_IPV4,
TEST_REMOTE_IPV4,
TEST_LOCAL_MAC,
Mac::BROADCAST,
);
// We should have installed a retry timer.
validate_single_retry_timer(
network.context("local"),
DEFAULT_ARP_REQUEST_PERIOD,
TEST_REMOTE_IPV4,
);
// Step once to deliver the ARP request to the remote.
let res = network.step::<ArpTimerFrameHandler<EthernetLinkDevice, Ipv4Addr>>();
assert_eq!(res.timers_fired(), 0);
assert_eq!(res.frames_sent(), 1);
// The remote should have populated its ARP cache with the local's
// information.
assert_eq!(
network.context("remote").get_ref().arp_state.table.lookup(TEST_LOCAL_IPV4).unwrap(),
&TEST_LOCAL_MAC
);
// The remote should have sent an ARP response.
validate_last_arp_packet(
network.context("remote"),
1,
TEST_LOCAL_MAC,
ArpOp::Response,
TEST_REMOTE_IPV4,
TEST_LOCAL_IPV4,
TEST_REMOTE_MAC,
TEST_LOCAL_MAC,
);
// Step once to deliver the ARP response to the local.
let res = network.step::<ArpTimerFrameHandler<EthernetLinkDevice, Ipv4Addr>>();
assert_eq!(res.timers_fired(), 0);
assert_eq!(res.frames_sent(), 1);
// The local should have populated its cache with the remote's
// information.
assert_eq!(
network.context("local").get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4).unwrap(),
&TEST_REMOTE_MAC
);
// The retry timer should be canceled, and replaced by an entry
// expiration timer.
validate_single_entry_timer(
network.context("local"),
DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD,
TEST_REMOTE_IPV4,
);
// The device layer should have been notified.
assert_eq!(
network.context("local").get_ref().addr_resolved.as_slice(),
[(TEST_REMOTE_IPV4, TEST_REMOTE_MAC)]
);
}
#[test]
fn test_arp_table_static_override_dynamic() {
let mut table: ArpTable<Ipv4Addr, Mac> = ArpTable::default();
let ip = TEST_REMOTE_IPV4;
let mac0 = Mac::new([1, 2, 3, 4, 5, 6]);
let mac1 = Mac::new([6, 5, 4, 3, 2, 1]);
table.insert_dynamic(ip, mac0);
assert_eq!(*table.lookup(ip).unwrap(), mac0);
table.insert_static(ip, mac1);
assert_eq!(*table.lookup(ip).unwrap(), mac1);
}
#[test]
fn test_arp_table_static_override_static() {
let mut table: ArpTable<Ipv4Addr, Mac> = ArpTable::default();
let ip = TEST_REMOTE_IPV4;
let mac0 = Mac::new([1, 2, 3, 4, 5, 6]);
let mac1 = Mac::new([6, 5, 4, 3, 2, 1]);
table.insert_static(ip, mac0);
assert_eq!(*table.lookup(ip).unwrap(), mac0);
table.insert_static(ip, mac1);
assert_eq!(*table.lookup(ip).unwrap(), mac1);
}
#[test]
fn test_arp_table_static_override_waiting() {
let mut table: ArpTable<Ipv4Addr, Mac> = ArpTable::default();
let ip = TEST_REMOTE_IPV4;
let mac1 = Mac::new([6, 5, 4, 3, 2, 1]);
table.set_waiting(ip, 4);
assert_eq!(table.lookup(ip), None);
table.insert_static(ip, mac1);
assert_eq!(*table.lookup(ip).unwrap(), mac1);
}
#[test]
fn test_arp_table_dynamic_override_waiting() {
let mut table: ArpTable<Ipv4Addr, Mac> = ArpTable::default();
let ip = TEST_REMOTE_IPV4;
let mac1 = Mac::new([6, 5, 4, 3, 2, 1]);
table.set_waiting(ip, 4);
assert_eq!(table.lookup(ip), None);
table.insert_dynamic(ip, mac1);
assert_eq!(*table.lookup(ip).unwrap(), mac1);
}
#[test]
fn test_arp_table_dynamic_override_dynamic() {
let mut table: ArpTable<Ipv4Addr, Mac> = ArpTable::default();
let ip = TEST_REMOTE_IPV4;
let mac0 = Mac::new([1, 2, 3, 4, 5, 6]);
let mac1 = Mac::new([6, 5, 4, 3, 2, 1]);
table.insert_dynamic(ip, mac0);
assert_eq!(*table.lookup(ip).unwrap(), mac0);
table.insert_dynamic(ip, mac1);
assert_eq!(*table.lookup(ip).unwrap(), mac1);
}
#[test]
fn test_arp_table_dynamic_should_not_override_static() {
let mut table: ArpTable<Ipv4Addr, Mac> = ArpTable::default();
let ip = TEST_REMOTE_IPV4;
let mac0 = Mac::new([1, 2, 3, 4, 5, 6]);
let mac1 = Mac::new([6, 5, 4, 3, 2, 1]);
table.insert_static(ip, mac0);
assert_eq!(*table.lookup(ip).unwrap(), mac0);
table.insert_dynamic(ip, mac1);
assert_eq!(*table.lookup(ip).unwrap(), mac0);
}
#[test]
fn test_arp_table_static_cancel_waiting_timer() {
// Test that, if we insert a static entry while a request retry timer is
// installed, that timer is canceled, and the device layer is notified.
let mut ctx = DummyContext::default();
// Perform a lookup in order to kick off a request and install a retry
// timer.
lookup(&mut ctx, (), TEST_LOCAL_MAC, TEST_REMOTE_IPV4);
// We should be in the Waiting state.
assert!(ctx.get_ref().arp_state.table.get_remaining_tries(TEST_REMOTE_IPV4).is_some());
// We should have an ARP request retry timer set.
validate_single_retry_timer(&ctx, DEFAULT_ARP_REQUEST_PERIOD, TEST_REMOTE_IPV4);
// Now insert a static entry.
insert_static_neighbor(&mut ctx, (), TEST_REMOTE_IPV4, TEST_REMOTE_MAC);
// The timer should have been canceled.
assert_eq!(ctx.timers().len(), 0);
// We should have notified the device layer.
assert_eq!(ctx.get_ref().addr_resolved.as_slice(), [(TEST_REMOTE_IPV4, TEST_REMOTE_MAC)]);
}
#[test]
fn test_arp_table_static_cancel_expiration_timer() {
// Test that, if we insert a static entry that overrides an existing
// dynamic entry, the dynamic entry's expiration timer is canceled.
let mut ctx = DummyContext::default();
insert_dynamic(&mut ctx, (), TEST_REMOTE_IPV4, TEST_REMOTE_MAC);
// We should have the address in cache.
assert_eq!(
ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4).unwrap(),
&TEST_REMOTE_MAC
);
// We should have an ARP entry expiration timer set.
validate_single_entry_timer(&ctx, DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD, TEST_REMOTE_IPV4);
// Now insert a static entry.
insert_static_neighbor(&mut ctx, (), TEST_REMOTE_IPV4, TEST_REMOTE_MAC);
// The timer should have been canceled.
assert_eq!(ctx.timers().len(), 0);
}
#[test]
fn test_arp_entry_expiration() {
// Test that, if a dynamic entry is installed, it is removed after the
// appropriate amount of time.
let mut ctx = DummyContext::default();
insert_dynamic(&mut ctx, (), TEST_REMOTE_IPV4, TEST_REMOTE_MAC);
// We should have the address in cache.
assert_eq!(
ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4).unwrap(),
&TEST_REMOTE_MAC
);
// We should have an ARP entry expiration timer set.
validate_single_entry_timer(&ctx, DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD, TEST_REMOTE_IPV4);
// Trigger the entry expiration timer.
assert!(ctx.trigger_next_timer());
// The right amount of time should have elapsed.
assert_eq!(ctx.now(), DummyInstant::from(DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD));
// The entry should have been removed.
assert!(ctx.get_ref().arp_state.table.table.get(&TEST_REMOTE_IPV4).is_none());
// The timer should have been canceled.
assert_eq!(ctx.timers().len(), 0);
// The device layer should have been notified.
assert_eq!(ctx.get_ref().addr_resolution_expired, [TEST_REMOTE_IPV4]);
}
#[test]
fn test_gratuitous_arp_resets_entry_timer() {
// Test that a gratuitous ARP resets the entry expiration timer by
// performing the following steps:
// 1. An arp entry is installed with default timer at instant t
// 2. A gratuitous arp message is sent after 5 seconds
// 3. Check at instant DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD whether the
// entry is there (it should be)
// 4. Check whether the entry disappears at instant
// (DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD + 5)
let mut ctx = DummyContext::default();
insert_dynamic(&mut ctx, (), TEST_REMOTE_IPV4, TEST_REMOTE_MAC);
// Let 5 seconds elapse.
assert_eq!(ctx.trigger_timers_until_instant(DummyInstant::from(Duration::from_secs(5))), 0);
// The entry should still be there.
assert_eq!(
ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4).unwrap(),
&TEST_REMOTE_MAC
);
// Receive the gratuitous ARP response.
send_arp_packet(
&mut ctx,
ArpOp::Response,
TEST_REMOTE_IPV4,
TEST_REMOTE_IPV4,
TEST_REMOTE_MAC,
TEST_REMOTE_MAC,
);
// Let the remaining time elapse to the first entry expiration timer.
assert_eq!(
ctx.trigger_timers_until_instant(DummyInstant::from(
DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD
)),
0
);
// The entry should still be there.
assert_eq!(
ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4).unwrap(),
&TEST_REMOTE_MAC
);
// Trigger the entry expiration timer.
assert!(ctx.trigger_next_timer());
// The right amount of time should have elapsed.
assert_eq!(
ctx.now(),
DummyInstant::from(Duration::from_secs(5) + DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD)
);
// The entry should be gone.
assert!(ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4).is_none());
// The device layer should have been notified.
assert_eq!(ctx.get_ref().addr_resolution_expired, [TEST_REMOTE_IPV4]);
}
#[test]
fn test_arp_table_dynamic_after_static_should_not_set_timer() {
// Test that, if a static entry exists, attempting to insert a dynamic
// entry for the same address will not cause a timer to be scheduled.
let mut ctx = DummyContext::default();
insert_static_neighbor(&mut ctx, (), TEST_REMOTE_IPV4, TEST_REMOTE_MAC);
assert_eq!(ctx.timers().len(), 0);
insert_dynamic(&mut ctx, (), TEST_REMOTE_IPV4, TEST_REMOTE_MAC);
assert_eq!(ctx.timers().len(), 0);
}
}