blob: 039139ee6d280ad0de8c21918cc67bdee4db4890 [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::fmt::Debug;
use core::time::Duration;
use lock_order::{lock::UnlockedAccess, wrap::prelude::*};
use net_types::{
ip::{Ipv4, Ipv4Addr},
SpecifiedAddr, UnicastAddr, Witness as _,
};
use packet::{BufferMut, InnerPacketBuilder, Serializer};
use packet_formats::{
arp::{ArpOp, ArpPacket, ArpPacketBuilder, HType},
utils::NonZeroDuration,
};
use tracing::{debug, trace, warn};
use crate::{
context::{
CounterContext, EventContext, InstantBindingsTypes, SendFrameContext, TimerContext2,
TracingContext,
},
counters::Counter,
device::{
self,
link::{LinkDevice, LinkUnicastAddress},
DeviceIdContext, FrameDestination,
},
ip::device::nud::{
self, ConfirmationFlags, DynamicNeighborUpdateSource, LinkResolutionContext,
NudBindingsTypes, NudConfigContext, NudContext, NudHandler, NudSenderContext, NudState,
NudTimerId, NudUserConfig,
},
BindingsContext, CoreCtx, StackState,
};
// 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, SC: ArpContext<D, P, C>>()` 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 trait ArpDevice: LinkDevice<Address = Self::HType> {
type HType: HType + LinkUnicastAddress + core::fmt::Debug;
}
impl<L: LinkDevice> ArpDevice for L
where
L::Address: HType + LinkUnicastAddress,
{
type HType = L::Address;
}
/// The identifier for timer events in the ARP layer.
pub(crate) type ArpTimerId<L, D> = NudTimerId<Ipv4, L, D>;
/// The metadata associated with an ARP frame.
#[cfg_attr(test, derive(Debug, PartialEq, Clone))]
pub 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,
}
/// Counters for the ARP layer.
#[derive(Default)]
pub struct ArpCounters {
/// Count of ARP packets received from the link layer.
pub rx_packets: Counter,
/// Count of received ARP packets that were dropped due to being unparsable.
pub rx_malformed_packets: Counter,
/// Count of ARP request packets received.
pub rx_requests: Counter,
/// Count of ARP response packets received.
pub rx_responses: Counter,
/// Count of non-gratuitous ARP packets received and dropped because the
/// destination address is non-local.
pub rx_dropped_non_local_target: Counter,
/// Count of ARP request packets sent.
pub tx_requests: Counter,
/// Count of ARP request packets not sent because the source address was
/// unknown or unassigned.
pub tx_requests_dropped_no_local_addr: Counter,
/// Count of ARP response packets sent.
pub tx_responses: Counter,
}
impl<BC: BindingsContext> UnlockedAccess<crate::lock_ordering::ArpCounters> for StackState<BC> {
type Data = ArpCounters;
type Guard<'l> = &'l ArpCounters where Self: 'l;
fn access(&self) -> Self::Guard<'_> {
self.arp_counters()
}
}
impl<BC: BindingsContext, L> CounterContext<ArpCounters> for CoreCtx<'_, BC, L> {
fn with_counters<O, F: FnOnce(&ArpCounters) -> O>(&self, cb: F) -> O {
cb(self.unlocked_access::<crate::lock_ordering::ArpCounters>())
}
}
/// An execution context for the ARP protocol that allows sending IP packets to
/// specific neighbors.
pub trait ArpSenderContext<D: ArpDevice, BC: ArpBindingsContext<D, Self::DeviceId>>:
ArpConfigContext + DeviceIdContext<D>
{
/// Send an IP packet to the neighbor with address `dst_link_address`.
fn send_ip_packet_to_neighbor_link_addr<S>(
&mut self,
bindings_ctx: &mut BC,
dst_link_address: D::HType,
body: S,
) -> Result<(), S>
where
S: Serializer,
S::Buffer: BufferMut;
}
// 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).
/// The execution context for the ARP protocol provided by bindings.
pub trait ArpBindingsContext<D: ArpDevice, DeviceId>:
TimerContext2
+ TracingContext
+ LinkResolutionContext<D>
+ EventContext<nud::Event<D::Address, DeviceId, Ipv4, <Self as InstantBindingsTypes>::Instant>>
{
}
impl<
DeviceId,
D: ArpDevice,
BC: TimerContext2
+ TracingContext
+ LinkResolutionContext<D>
+ EventContext<
nud::Event<D::Address, DeviceId, Ipv4, <Self as InstantBindingsTypes>::Instant>,
>,
> ArpBindingsContext<D, DeviceId> for BC
{
}
/// An execution context for the ARP protocol.
pub trait ArpContext<D: ArpDevice, BC: ArpBindingsContext<D, Self::DeviceId>>:
DeviceIdContext<D> + SendFrameContext<BC, ArpFrameMetadata<D, Self::DeviceId>>
{
type ConfigCtx<'a>: ArpConfigContext;
type ArpSenderCtx<'a>: ArpSenderContext<D, BC, DeviceId = Self::DeviceId>;
/// Calls the function with a mutable reference to ARP state and the
/// core sender context.
fn with_arp_state_mut_and_sender_ctx<
O,
F: FnOnce(&mut ArpState<D, BC>, &mut Self::ArpSenderCtx<'_>) -> O,
>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
/// Get a protocol address of this interface.
///
/// If `device_id` does not have any addresses associated with it, return
/// `None`.
fn get_protocol_addr(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
) -> Option<Ipv4Addr>;
/// Get the hardware address of this interface.
fn get_hardware_addr(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
) -> UnicastAddr<D::HType>;
/// Calls the function with a mutable reference to ARP state and the ARP
/// configuration context.
fn with_arp_state_mut<O, F: FnOnce(&mut ArpState<D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
/// Calls the function with an immutable reference to ARP state.
fn with_arp_state<O, F: FnOnce(&ArpState<D, BC>) -> O>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O;
}
/// An execution context for the ARP protocol that allows accessing
/// configuration parameters.
pub trait ArpConfigContext {
fn retransmit_timeout(&mut self) -> NonZeroDuration {
NonZeroDuration::new(DEFAULT_ARP_REQUEST_PERIOD).unwrap()
}
/// Calls the callback with an immutable reference to NUD configurations.
fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O;
}
impl<
D: ArpDevice,
BC: ArpBindingsContext<D, CC::DeviceId>,
CC: ArpContext<D, BC> + CounterContext<ArpCounters>,
> NudContext<Ipv4, D, BC> for CC
{
type ConfigCtx<'a> = <CC as ArpContext<D, BC>>::ConfigCtx<'a>;
type SenderCtx<'a> = <CC as ArpContext<D, BC>>::ArpSenderCtx<'a>;
fn with_nud_state_mut_and_sender_ctx<
O,
F: FnOnce(&mut NudState<Ipv4, D, BC>, &mut Self::SenderCtx<'_>) -> O,
>(
&mut self,
device_id: &CC::DeviceId,
cb: F,
) -> O {
self.with_arp_state_mut_and_sender_ctx(device_id, |ArpState { nud }, core_ctx| {
cb(nud, core_ctx)
})
}
fn with_nud_state_mut<
O,
F: FnOnce(&mut NudState<Ipv4, D, BC>, &mut Self::ConfigCtx<'_>) -> O,
>(
&mut self,
device_id: &CC::DeviceId,
cb: F,
) -> O {
self.with_arp_state_mut(device_id, |ArpState { nud }, core_ctx| cb(nud, core_ctx))
}
fn with_nud_state<O, F: FnOnce(&NudState<Ipv4, D, BC>) -> O>(
&mut self,
device_id: &Self::DeviceId,
cb: F,
) -> O {
self.with_arp_state(device_id, |ArpState { nud }| cb(nud))
}
fn send_neighbor_solicitation(
&mut self,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
lookup_addr: SpecifiedAddr<Ipv4Addr>,
remote_link_addr: Option<D::Address>,
) {
send_arp_request(self, bindings_ctx, device_id, lookup_addr.get(), remote_link_addr)
}
}
impl<CC: ArpConfigContext> NudConfigContext<Ipv4> for CC {
fn retransmit_timeout(&mut self) -> NonZeroDuration {
self.retransmit_timeout()
}
fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
ArpConfigContext::with_nud_user_config(self, cb)
}
}
impl<D: ArpDevice, BC: ArpBindingsContext<D, CC::DeviceId>, CC: ArpSenderContext<D, BC>>
NudSenderContext<Ipv4, D, BC> for CC
{
fn send_ip_packet_to_neighbor_link_addr<S>(
&mut self,
bindings_ctx: &mut BC,
dst_mac: D::HType,
body: S,
) -> Result<(), S>
where
S: Serializer,
S::Buffer: BufferMut,
{
ArpSenderContext::send_ip_packet_to_neighbor_link_addr(self, bindings_ctx, dst_mac, body)
}
}
pub(crate) trait ArpPacketHandler<D: ArpDevice, BC>: DeviceIdContext<D> {
fn handle_packet<B: BufferMut + Debug>(
&mut self,
bindings_ctx: &mut BC,
device_id: Self::DeviceId,
frame_dst: FrameDestination,
buffer: B,
);
}
impl<
D: ArpDevice,
BC: ArpBindingsContext<D, CC::DeviceId>,
CC: ArpContext<D, BC>
+ SendFrameContext<BC, ArpFrameMetadata<D, Self::DeviceId>>
+ NudHandler<Ipv4, D, BC>
+ CounterContext<ArpCounters>,
> ArpPacketHandler<D, BC> for CC
{
/// Handles an inbound ARP packet.
fn handle_packet<B: BufferMut + Debug>(
&mut self,
bindings_ctx: &mut BC,
device_id: Self::DeviceId,
frame_dst: FrameDestination,
buffer: B,
) {
handle_packet(self, bindings_ctx, device_id, frame_dst, buffer)
}
}
fn handle_packet<
D: ArpDevice,
BC: ArpBindingsContext<D, CC::DeviceId>,
B: BufferMut + Debug,
CC: ArpContext<D, BC>
+ SendFrameContext<BC, ArpFrameMetadata<D, CC::DeviceId>>
+ NudHandler<Ipv4, D, BC>
+ CounterContext<ArpCounters>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: CC::DeviceId,
frame_dst: FrameDestination,
mut buffer: B,
) {
core_ctx.increment(|counters| &counters.rx_packets);
// TODO(wesleyac) Add support for probe.
let packet = match buffer.parse::<ArpPacket<_, D::HType, Ipv4Addr>>() {
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);
core_ctx.increment(|counters| &counters.rx_malformed_packets);
return;
}
};
enum ValidArpOp {
Request,
Response,
}
let op = match packet.operation() {
ArpOp::Request => {
core_ctx.increment(|counters| &counters.rx_requests);
ValidArpOp::Request
}
ArpOp::Response => {
core_ctx.increment(|counters| &counters.rx_responses);
ValidArpOp::Response
}
ArpOp::Other(o) => {
core_ctx.increment(|counters| &counters.rx_malformed_packets);
debug!("dropping arp packet with op = {:?}", o);
return;
}
};
enum PacketKind {
Gratuitous,
AddressedToMe,
}
// 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 |
// +----------+---------------+---------------+-----------------------------+
let sender_addr = packet.sender_protocol_address();
let target_addr = packet.target_protocol_address();
let (source, kind) = match (
sender_addr == target_addr,
Some(target_addr) == core_ctx.get_protocol_addr(bindings_ctx, &device_id),
) {
(true, false) => {
// Treat all GARP messages as neighbor probes as GARPs are not
// responses for previously sent requests, even if the packet
// operation is a response OP code.
//
// Per RFC 5944 section 4.6,
//
// A Gratuitous ARP [45] is an ARP packet sent by a node in order
// to spontaneously cause other nodes to update an entry in their
// ARP cache. A gratuitous ARP MAY use either an ARP Request or an
// ARP Reply packet. In either case, the ARP Sender Protocol
// Address and ARP Target Protocol Address are both set to the IP
// address of the cache entry to be updated, and the ARP Sender
// Hardware Address is set to the link-layer address to which this
// cache entry should be updated. When using an ARP Reply packet,
// the Target Hardware Address is also set to the link-layer
// address to which this cache entry should be updated (this field
// is not used in an ARP Request packet).
//
// In either case, for a gratuitous ARP, the ARP packet MUST be
// transmitted as a local broadcast packet on the local link. As
// specified in [16], any node receiving any ARP packet (Request
// or Reply) MUST update its local ARP cache with the Sender
// Protocol and Hardware Addresses in the ARP packet, if the
// receiving node has an entry for that IP address already in its
// ARP cache. This requirement in the ARP protocol applies even
// for ARP Request packets, and for ARP Reply packets that do not
// match any ARP Request transmitted by the receiving node [16].
(DynamicNeighborUpdateSource::Probe, PacketKind::Gratuitous)
}
(false, true) => {
// Consider ARP replies as solicited if they were unicast directly to us, and
// unsolicited otherwise.
let solicited = match frame_dst {
FrameDestination::Individual { local } => local,
FrameDestination::Broadcast | FrameDestination::Multicast => false,
};
let source = match op {
ValidArpOp::Request => DynamicNeighborUpdateSource::Probe,
ValidArpOp::Response => {
DynamicNeighborUpdateSource::Confirmation(ConfirmationFlags {
solicited_flag: solicited,
// ARP does not have the concept of an override flag in a neighbor
// confirmation; if the link address that's received does not match the one
// in the neighbor cache, the entry should always go to STALE.
override_flag: false,
})
}
};
(source, PacketKind::AddressedToMe)
}
(false, false) => {
core_ctx.increment(|counters| &counters.rx_dropped_non_local_target);
trace!(
"non-gratuitous ARP packet not targetting us; sender = {}, target={}",
sender_addr,
target_addr
);
return;
}
(true, true) => {
warn!(
"got gratuitous ARP packet with our address {target_addr} on device {device_id:?}, \
dropping...",
);
return;
}
};
let sender_hw_addr = packet.sender_hardware_address();
if let Some(addr) = SpecifiedAddr::new(sender_addr) {
NudHandler::<Ipv4, D, _>::handle_neighbor_update(
core_ctx,
bindings_ctx,
&device_id,
addr,
sender_hw_addr,
source,
)
};
match kind {
PacketKind::Gratuitous => return,
PacketKind::AddressedToMe => match source {
DynamicNeighborUpdateSource::Probe => {
let self_hw_addr = core_ctx.get_hardware_addr(bindings_ctx, &device_id);
core_ctx.increment(|counters| &counters.tx_responses);
debug!("sending ARP response for {target_addr} to {sender_addr}");
SendFrameContext::send_frame(
core_ctx,
bindings_ctx,
ArpFrameMetadata { device_id, dst_addr: sender_hw_addr },
ArpPacketBuilder::new(
ArpOp::Response,
self_hw_addr.get(),
target_addr,
sender_hw_addr,
sender_addr,
)
.into_serializer_with(buffer),
)
.unwrap_or_else(|serializer| {
warn!(
"failed to send ARP response for {target_addr} to {sender_addr}: \
{serializer:?}"
)
});
}
DynamicNeighborUpdateSource::Confirmation(_flags) => {}
},
}
}
// Use the same default retransmit timeout that is defined for NDP in
// [RFC 4861 section 10], to align behavior between IPv4 and IPv6 and simplify
// testing.
//
// TODO(https://fxbug.dev/42075782): allow this default to be overridden.
//
// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
const DEFAULT_ARP_REQUEST_PERIOD: Duration = crate::ip::device::state::RETRANS_TIMER_DEFAULT.get();
fn send_arp_request<
D: ArpDevice,
BC: ArpBindingsContext<D, CC::DeviceId>,
CC: ArpContext<D, BC> + CounterContext<ArpCounters>,
>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
lookup_addr: Ipv4Addr,
remote_link_addr: Option<D::Address>,
) {
if let Some(sender_protocol_addr) = core_ctx.get_protocol_addr(bindings_ctx, device_id) {
let self_hw_addr = core_ctx.get_hardware_addr(bindings_ctx, device_id);
let dst_addr = remote_link_addr.unwrap_or(D::HType::BROADCAST);
core_ctx.increment(|counters| &counters.tx_requests);
debug!("sending ARP request for {lookup_addr} to {dst_addr:?}");
SendFrameContext::send_frame(
core_ctx,
bindings_ctx,
ArpFrameMetadata { device_id: device_id.clone(), dst_addr },
ArpPacketBuilder::new(
ArpOp::Request,
self_hw_addr.get(),
sender_protocol_addr,
// This is meaningless, since RFC 826 does not specify the
// behaviour. However, `dst_addr` is sensible, as this is the
// actual address we are sending the packet to.
dst_addr,
lookup_addr,
)
.into_serializer(),
)
.unwrap_or_else(|serializer| {
warn!("failed to send ARP request for {lookup_addr} to {dst_addr:?}: {serializer:?}")
});
} 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?
core_ctx.increment(|counters| &counters.tx_requests_dropped_no_local_addr);
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 struct ArpState<D: ArpDevice, BT: NudBindingsTypes<D>> {
pub(crate) nud: NudState<Ipv4, D, BT>,
}
impl<D: ArpDevice, BC: NudBindingsTypes<D> + TimerContext2> ArpState<D, BC> {
pub fn new<DeviceId: device::WeakId, F: Fn(ArpTimerId<D, DeviceId>) -> BC::DispatchId>(
bindings_ctx: &mut BC,
device_id: DeviceId,
convert: F,
) -> Self {
ArpState { nud: NudState::new(bindings_ctx, device_id, convert) }
}
}
#[cfg(test)]
mod tests {
use alloc::{vec, vec::Vec};
use core::iter;
use net_types::ethernet::Mac;
use packet::{Buf, ParseBuffer};
use packet_formats::{
arp::{peek_arp_types, ArpHardwareType, ArpNetworkType},
ipv4::Ipv4FragmentType,
};
use test_case::test_case;
use super::*;
use crate::{
context::{
testutil::{
FakeBindingsCtx, FakeCoreCtx, FakeCtx, FakeInstant, FakeNetwork, FakeNetworkContext,
},
InstantContext as _, TimerHandler,
},
device::{
ethernet::EthernetLinkDevice,
link::testutil::FakeLinkDeviceId,
testutil::{FakeDeviceId, FakeWeakDeviceId},
},
ip::device::nud::{
testutil::{
assert_dynamic_neighbor_state, assert_dynamic_neighbor_with_addr,
assert_neighbor_unknown,
},
DynamicNeighborState, NudCounters, NudIcmpContext, Reachable, Stale,
},
socket::address::SocketIpAddr,
testutil::assert_empty,
};
const TEST_LOCAL_IPV4: Ipv4Addr = Ipv4Addr::new([1, 2, 3, 4]);
const TEST_REMOTE_IPV4: Ipv4Addr = Ipv4Addr::new([5, 6, 7, 8]);
const TEST_ANOTHER_REMOTE_IPV4: Ipv4Addr = Ipv4Addr::new([9, 10, 11, 12]);
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 fake `ArpContext` that stores frames, address resolution events, and
/// address resolution failure events.
struct FakeArpCtx {
proto_addr: Option<Ipv4Addr>,
hw_addr: UnicastAddr<Mac>,
arp_state: ArpState<EthernetLinkDevice, FakeBindingsCtxImpl>,
inner: FakeArpInnerCtx,
config: FakeArpConfigCtx,
counters: ArpCounters,
nud_counters: NudCounters<Ipv4>,
}
/// A fake `ArpSenderContext` that sends IP packets.
struct FakeArpInnerCtx;
/// A fake `ArpConfigContext`.
struct FakeArpConfigCtx;
impl FakeArpCtx {
fn new(bindings_ctx: &mut FakeBindingsCtxImpl) -> FakeArpCtx {
FakeArpCtx {
proto_addr: Some(TEST_LOCAL_IPV4),
hw_addr: UnicastAddr::new(TEST_LOCAL_MAC).unwrap(),
arp_state: ArpState::new(bindings_ctx, FakeWeakDeviceId(FakeLinkDeviceId), |t| t),
inner: FakeArpInnerCtx,
config: FakeArpConfigCtx,
counters: Default::default(),
nud_counters: Default::default(),
}
}
}
type FakeBindingsCtxImpl = FakeBindingsCtx<
ArpTimerId<EthernetLinkDevice, FakeWeakDeviceId<FakeLinkDeviceId>>,
nud::Event<Mac, FakeLinkDeviceId, Ipv4, FakeInstant>,
(),
(),
>;
type FakeCoreCtxImpl = FakeCoreCtx<
FakeArpCtx,
ArpFrameMetadata<EthernetLinkDevice, FakeLinkDeviceId>,
FakeDeviceId,
>;
fn new_context() -> crate::testutil::ContextPair<FakeCoreCtxImpl, FakeBindingsCtxImpl> {
FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
FakeCoreCtxImpl::with_state(FakeArpCtx::new(bindings_ctx))
})
}
impl FakeNetworkContext for crate::testutil::ContextPair<FakeCoreCtxImpl, FakeBindingsCtxImpl> {
type TimerId = ArpTimerId<EthernetLinkDevice, FakeWeakDeviceId<FakeLinkDeviceId>>;
type SendMeta = ArpFrameMetadata<EthernetLinkDevice, FakeLinkDeviceId>;
type RecvMeta = ArpFrameMetadata<EthernetLinkDevice, FakeLinkDeviceId>;
fn handle_frame(
&mut self,
ArpFrameMetadata { device_id, .. }: Self::RecvMeta,
data: Buf<Vec<u8>>,
) {
let Self { core_ctx, bindings_ctx } = self;
handle_packet(
core_ctx,
bindings_ctx,
device_id,
FrameDestination::Individual { local: true },
data,
)
}
fn handle_timer(&mut self, timer: Self::TimerId) {
let Self { core_ctx, bindings_ctx } = self;
TimerHandler::handle_timer(core_ctx, bindings_ctx, timer)
}
fn process_queues(&mut self) -> bool {
false
}
}
impl DeviceIdContext<EthernetLinkDevice> for FakeCoreCtxImpl {
type DeviceId = FakeLinkDeviceId;
type WeakDeviceId = FakeWeakDeviceId<FakeLinkDeviceId>;
}
impl DeviceIdContext<EthernetLinkDevice> for FakeArpInnerCtx {
type DeviceId = FakeLinkDeviceId;
type WeakDeviceId = FakeWeakDeviceId<FakeLinkDeviceId>;
}
impl ArpContext<EthernetLinkDevice, FakeBindingsCtxImpl> for FakeCoreCtxImpl {
type ConfigCtx<'a> = FakeArpConfigCtx;
type ArpSenderCtx<'a> = FakeArpInnerCtx;
fn with_arp_state_mut_and_sender_ctx<
O,
F: FnOnce(
&mut ArpState<EthernetLinkDevice, FakeBindingsCtxImpl>,
&mut Self::ArpSenderCtx<'_>,
) -> O,
>(
&mut self,
FakeLinkDeviceId: &FakeLinkDeviceId,
cb: F,
) -> O {
let state = self.get_mut();
cb(&mut state.arp_state, &mut state.inner)
}
fn with_arp_state<O, F: FnOnce(&ArpState<EthernetLinkDevice, FakeBindingsCtxImpl>) -> O>(
&mut self,
FakeLinkDeviceId: &FakeLinkDeviceId,
cb: F,
) -> O {
let state = self.get_ref();
cb(&state.arp_state)
}
fn get_protocol_addr(
&mut self,
_bindings_ctx: &mut FakeBindingsCtxImpl,
_device_id: &FakeLinkDeviceId,
) -> Option<Ipv4Addr> {
self.get_ref().proto_addr
}
fn get_hardware_addr(
&mut self,
_bindings_ctx: &mut FakeBindingsCtxImpl,
_device_id: &FakeLinkDeviceId,
) -> UnicastAddr<Mac> {
self.get_ref().hw_addr
}
fn with_arp_state_mut<
O,
F: FnOnce(
&mut ArpState<EthernetLinkDevice, FakeBindingsCtxImpl>,
&mut Self::ConfigCtx<'_>,
) -> O,
>(
&mut self,
FakeLinkDeviceId: &FakeLinkDeviceId,
cb: F,
) -> O {
let FakeArpCtx {
arp_state,
config,
proto_addr: _,
hw_addr: _,
inner: _,
counters: _,
nud_counters: _,
} = self.get_mut();
cb(arp_state, config)
}
}
impl NudIcmpContext<Ipv4, EthernetLinkDevice, FakeBindingsCtxImpl> for FakeCoreCtxImpl {
fn send_icmp_dest_unreachable(
&mut self,
_bindings_ctx: &mut FakeBindingsCtxImpl,
_frame: Buf<Vec<u8>>,
_device_id: Option<&Self::DeviceId>,
_original_src: SocketIpAddr<Ipv4Addr>,
_original_dst: SocketIpAddr<Ipv4Addr>,
_metadata: (usize, Ipv4FragmentType),
) {
panic!("send_icmp_dest_unreachable should not be called");
}
}
impl ArpConfigContext for FakeArpConfigCtx {
fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
cb(&NudUserConfig::default())
}
}
impl ArpConfigContext for FakeArpInnerCtx {
fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
cb(&NudUserConfig::default())
}
}
impl ArpSenderContext<EthernetLinkDevice, FakeBindingsCtxImpl> for FakeArpInnerCtx {
fn send_ip_packet_to_neighbor_link_addr<S>(
&mut self,
_bindings_ctx: &mut FakeBindingsCtxImpl,
_dst_link_address: Mac,
_body: S,
) -> Result<(), S> {
Ok(())
}
}
impl CounterContext<ArpCounters> for FakeCoreCtxImpl {
fn with_counters<O, F: FnOnce(&ArpCounters) -> O>(&self, cb: F) -> O {
cb(&self.get_ref().counters)
}
}
impl CounterContext<NudCounters<Ipv4>> for FakeCoreCtxImpl {
fn with_counters<O, F: FnOnce(&NudCounters<Ipv4>) -> O>(&self, cb: F) -> O {
cb(&self.get_ref().nud_counters)
}
}
fn send_arp_packet(
core_ctx: &mut FakeCoreCtxImpl,
bindings_ctx: &mut FakeBindingsCtxImpl,
op: ArpOp,
sender_ipv4: Ipv4Addr,
target_ipv4: Ipv4Addr,
sender_mac: Mac,
target_mac: Mac,
frame_dst: FrameDestination,
) {
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, ArpHardwareType::Ethernet);
assert_eq!(proto, ArpNetworkType::Ipv4);
handle_packet::<_, _, _, _>(core_ctx, bindings_ctx, FakeLinkDeviceId, frame_dst, 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(
core_ctx: &FakeCoreCtxImpl,
total_frames: usize,
dst: Mac,
op: ArpOp,
local_ipv4: Ipv4Addr,
remote_ipv4: Ipv4Addr,
local_mac: Mac,
remote_mac: Mac,
) {
assert_eq!(core_ctx.frames().len(), total_frames);
let (meta, frame) = &core_ctx.frames()[total_frames - 1];
assert_eq!(meta.dst_addr, dst);
validate_arp_packet(frame, op, local_ipv4, remote_ipv4, local_mac, remote_mac);
}
#[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 FakeCtx { mut core_ctx, mut bindings_ctx } = new_context();
send_arp_packet(
&mut core_ctx,
&mut bindings_ctx,
ArpOp::Request,
TEST_REMOTE_IPV4,
TEST_REMOTE_IPV4,
TEST_REMOTE_MAC,
TEST_INVALID_MAC,
FrameDestination::Individual { local: false },
);
// We should have cached the sender's address information.
assert_dynamic_neighbor_with_addr(
&mut core_ctx,
FakeLinkDeviceId,
SpecifiedAddr::new(TEST_REMOTE_IPV4).unwrap(),
TEST_REMOTE_MAC,
);
// Gratuitous ARPs should not prompt a response.
assert_empty(core_ctx.frames().iter());
}
#[test]
fn test_receive_gratuitous_arp_response() {
// Test that, when we receive a gratuitous ARP response, we cache the
// sender's address information, and we do not send a response.
let FakeCtx { mut core_ctx, mut bindings_ctx } = new_context();
send_arp_packet(
&mut core_ctx,
&mut bindings_ctx,
ArpOp::Response,
TEST_REMOTE_IPV4,
TEST_REMOTE_IPV4,
TEST_REMOTE_MAC,
TEST_REMOTE_MAC,
FrameDestination::Individual { local: false },
);
// We should have cached the sender's address information.
assert_dynamic_neighbor_with_addr(
&mut core_ctx,
FakeLinkDeviceId,
SpecifiedAddr::new(TEST_REMOTE_IPV4).unwrap(),
TEST_REMOTE_MAC,
);
// Gratuitous ARPs should not send a response.
assert_empty(core_ctx.frames().iter());
}
#[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 FakeCtx { mut core_ctx, mut bindings_ctx } = new_context();
// Trigger link resolution.
assert_neighbor_unknown(
&mut core_ctx,
FakeLinkDeviceId,
SpecifiedAddr::new(TEST_REMOTE_IPV4).unwrap(),
);
assert_eq!(
NudHandler::send_ip_packet_to_neighbor(
&mut core_ctx,
&mut bindings_ctx,
&FakeLinkDeviceId,
SpecifiedAddr::new(TEST_REMOTE_IPV4).unwrap(),
Buf::new([1], ..),
),
Ok(())
);
send_arp_packet(
&mut core_ctx,
&mut bindings_ctx,
ArpOp::Response,
TEST_REMOTE_IPV4,
TEST_REMOTE_IPV4,
TEST_REMOTE_MAC,
TEST_REMOTE_MAC,
FrameDestination::Individual { local: false },
);
// The response should now be in our cache.
assert_dynamic_neighbor_with_addr(
&mut core_ctx,
FakeLinkDeviceId,
SpecifiedAddr::new(TEST_REMOTE_IPV4).unwrap(),
TEST_REMOTE_MAC,
);
// Gratuitous ARPs should not send a response (the 1 frame is for the
// original request).
assert_eq!(core_ctx.frames().len(), 1);
}
#[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 FakeCtx { mut core_ctx, mut bindings_ctx } = new_context();
send_arp_packet(
&mut core_ctx,
&mut bindings_ctx,
ArpOp::Request,
TEST_REMOTE_IPV4,
TEST_LOCAL_IPV4,
TEST_REMOTE_MAC,
TEST_LOCAL_MAC,
FrameDestination::Individual { local: true },
);
// Make sure we cached the sender's address information.
assert_dynamic_neighbor_with_addr(
&mut core_ctx,
FakeLinkDeviceId,
SpecifiedAddr::new(TEST_REMOTE_IPV4).unwrap(),
TEST_REMOTE_MAC,
);
// We should have sent an ARP response.
validate_last_arp_packet(
&core_ctx,
1,
TEST_REMOTE_MAC,
ArpOp::Response,
TEST_LOCAL_IPV4,
TEST_REMOTE_IPV4,
TEST_LOCAL_MAC,
TEST_REMOTE_MAC,
);
}
struct ArpHostConfig<'a> {
name: &'a str,
proto_addr: Ipv4Addr,
hw_addr: Mac,
}
#[test_case(ArpHostConfig {
name: "remote",
proto_addr: TEST_REMOTE_IPV4,
hw_addr: TEST_REMOTE_MAC
},
vec![]
)]
#[test_case(ArpHostConfig {
name: "requested_remote",
proto_addr: TEST_REMOTE_IPV4,
hw_addr: TEST_REMOTE_MAC
},
vec![
ArpHostConfig {
name: "non_requested_remote",
proto_addr: TEST_ANOTHER_REMOTE_IPV4,
hw_addr: TEST_REMOTE_MAC
}
]
)]
fn test_address_resolution(
requested_remote_cfg: ArpHostConfig<'_>,
other_remote_cfgs: Vec<ArpHostConfig<'_>>,
) {
// Test a basic ARP resolution scenario.
// 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 requested remote receives the request, it populates its cache with
// the local's information, and sends an ARP reply.
// 3. Any non-requested remotes will neither populate their caches nor send ARP replies.
// 4. 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.
const LOCAL_HOST_CFG: ArpHostConfig<'_> =
ArpHostConfig { name: "local", proto_addr: TEST_LOCAL_IPV4, hw_addr: TEST_LOCAL_MAC };
let host_iter = other_remote_cfgs
.iter()
.chain(iter::once(&requested_remote_cfg))
.chain(iter::once(&LOCAL_HOST_CFG));
let mut network = FakeNetwork::new(
{
host_iter.clone().map(|cfg| {
let ArpHostConfig { name, proto_addr, hw_addr } = cfg;
let mut ctx = new_context();
let FakeCtx { core_ctx, bindings_ctx: _ } = &mut ctx;
core_ctx.get_mut().hw_addr = UnicastAddr::new(*hw_addr).unwrap();
core_ctx.get_mut().proto_addr = Some(*proto_addr);
(*name, ctx)
})
},
|ctx: &str, meta: ArpFrameMetadata<_, _>| {
host_iter
.clone()
.filter_map(|cfg| {
let ArpHostConfig { name, proto_addr: _, hw_addr: _ } = cfg;
if !ctx.eq(*name) {
Some((*name, meta.clone(), None))
} else {
None
}
})
.collect::<Vec<_>>()
},
);
let ArpHostConfig {
name: local_name,
proto_addr: local_proto_addr,
hw_addr: local_hw_addr,
} = LOCAL_HOST_CFG;
let ArpHostConfig {
name: requested_remote_name,
proto_addr: requested_remote_proto_addr,
hw_addr: requested_remote_hw_addr,
} = requested_remote_cfg;
// Trigger link resolution.
network.with_context(local_name, |FakeCtx { core_ctx, bindings_ctx }| {
assert_neighbor_unknown(
core_ctx,
FakeLinkDeviceId,
SpecifiedAddr::new(requested_remote_proto_addr).unwrap(),
);
assert_eq!(
NudHandler::send_ip_packet_to_neighbor(
core_ctx,
bindings_ctx,
&FakeLinkDeviceId,
SpecifiedAddr::new(requested_remote_proto_addr).unwrap(),
Buf::new([1], ..),
),
Ok(())
);
// We should have sent an ARP request.
validate_last_arp_packet(
core_ctx,
1,
Mac::BROADCAST,
ArpOp::Request,
local_proto_addr,
requested_remote_proto_addr,
local_hw_addr,
Mac::BROADCAST,
);
});
// Step once to deliver the ARP request to the remotes.
let res = network.step();
assert_eq!(res.timers_fired, 0);
// Our faked broadcast network should deliver frames to every host other
// than the sender itself. These should include all non-participating remotes
// and either the local or the participating remote, depending on who is
// sending the packet.
let expected_frames_sent_bcast = other_remote_cfgs.len() + 1;
assert_eq!(res.frames_sent, expected_frames_sent_bcast);
// The requested remote should have populated its ARP cache with the local's
// information.
network.with_context(requested_remote_name, |FakeCtx { core_ctx, bindings_ctx: _ }| {
assert_dynamic_neighbor_with_addr(
core_ctx,
FakeLinkDeviceId,
SpecifiedAddr::new(local_proto_addr).unwrap(),
LOCAL_HOST_CFG.hw_addr,
);
// The requested remote should have sent an ARP response.
validate_last_arp_packet(
core_ctx,
1,
local_hw_addr,
ArpOp::Response,
requested_remote_proto_addr,
local_proto_addr,
requested_remote_hw_addr,
local_hw_addr,
);
});
// Step once to deliver the ARP response to the local.
let res = network.step();
assert_eq!(res.timers_fired, 0);
assert_eq!(res.frames_sent, expected_frames_sent_bcast);
// The local should have populated its cache with the remote's
// information.
network.with_context(local_name, |FakeCtx { core_ctx, bindings_ctx: _ }| {
assert_dynamic_neighbor_with_addr(
core_ctx,
FakeLinkDeviceId,
SpecifiedAddr::new(requested_remote_proto_addr).unwrap(),
requested_remote_hw_addr,
);
});
other_remote_cfgs.iter().for_each(
|ArpHostConfig { name: unrequested_remote_name, proto_addr: _, hw_addr: _ }| {
// The non-requested_remote should not have populated its ARP cache.
network.with_context(
*unrequested_remote_name,
|FakeCtx { core_ctx, bindings_ctx: _ }| {
// The non-requested_remote should not have sent an ARP response.
assert_empty(core_ctx.frames().iter());
assert_neighbor_unknown(
core_ctx,
FakeLinkDeviceId,
SpecifiedAddr::new(local_proto_addr).unwrap(),
);
},
)
},
);
}
#[test_case(FrameDestination::Individual { local: true }, true; "unicast to us is solicited")]
#[test_case(
FrameDestination::Individual { local: false },
false;
"unicast to other addr is unsolicited"
)]
#[test_case(FrameDestination::Multicast, false; "multicast reply is unsolicited")]
#[test_case(FrameDestination::Broadcast, false; "broadcast reply is unsolicited")]
fn only_unicast_reply_treated_as_solicited(
frame_dst: FrameDestination,
expect_solicited: bool,
) {
let FakeCtx { mut core_ctx, mut bindings_ctx } = new_context();
// Trigger link resolution.
assert_neighbor_unknown(
&mut core_ctx,
FakeLinkDeviceId,
SpecifiedAddr::new(TEST_REMOTE_IPV4).unwrap(),
);
assert_eq!(
NudHandler::send_ip_packet_to_neighbor(
&mut core_ctx,
&mut bindings_ctx,
&FakeLinkDeviceId,
SpecifiedAddr::new(TEST_REMOTE_IPV4).unwrap(),
Buf::new([1], ..),
),
Ok(())
);
// Now send a confirmation with the specified frame destination.
send_arp_packet(
&mut core_ctx,
&mut bindings_ctx,
ArpOp::Response,
TEST_REMOTE_IPV4,
TEST_LOCAL_IPV4,
TEST_REMOTE_MAC,
TEST_LOCAL_MAC,
frame_dst,
);
// If the confirmation was interpreted as solicited, the entry should be
// marked as REACHABLE; otherwise, it should have transitioned to STALE.
let expected_state = if expect_solicited {
DynamicNeighborState::Reachable(Reachable {
link_address: TEST_REMOTE_MAC,
last_confirmed_at: bindings_ctx.now(),
})
} else {
DynamicNeighborState::Stale(Stale { link_address: TEST_REMOTE_MAC })
};
assert_dynamic_neighbor_state(
&mut core_ctx,
FakeLinkDeviceId,
SpecifiedAddr::new(TEST_REMOTE_IPV4).unwrap(),
expected_state,
);
}
}