| // 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, HashMap}; |
| use core::{hash::Hash, marker::PhantomData, time::Duration}; |
| |
| use log::{debug, error}; |
| use net_types::{UnicastAddr, UnicastAddress, Witness as _}; |
| use packet::{BufferMut, EmptyBuf, InnerPacketBuilder}; |
| use packet_formats::arp::{ArpOp, ArpPacket, ArpPacketBuilder, HType, PType}; |
| |
| use crate::{ |
| context::{CounterContext, FrameContext, StateContext, TimerContext, TimerHandler}, |
| device::link::LinkDevice, |
| }; |
| |
| // 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(crate) trait ArpDevice { |
| type HType: HType + UnicastAddress; |
| } |
| |
| impl<L: LinkDevice> ArpDevice for L |
| where |
| L::Address: HType + UnicastAddress, |
| { |
| 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. |
| #[cfg_attr(test, derive(Debug, PartialEq))] |
| 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 `sync_ctx.deinitialize`. |
| /// |
| /// See [`ArpHandler::deinitialize`] for more details. |
| pub(super) fn deinitialize<D: ArpDevice, P: PType, C, H: ArpHandler<D, P, C>>( |
| sync_ctx: &mut H, |
| ctx: &mut C, |
| device_id: H::DeviceId, |
| ) { |
| sync_ctx.deinitialize(ctx, 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 |
| /// [`handle_packet`]), and allows ARP to reuse that buffer rather than needing |
| /// to always allocate a new one. |
| pub(crate) trait BufferArpContext< |
| D: ArpDevice, |
| P: PType, |
| C: ArpNonSyncCtx<D, P, Self::DeviceId>, |
| B: BufferMut, |
| >: |
| ArpContext<D, P, C> |
| + FrameContext<C, B, ArpFrameMetadata<D, <Self as ArpDeviceIdContext<D>>::DeviceId>> |
| { |
| } |
| |
| impl< |
| D: ArpDevice, |
| P: PType, |
| B: BufferMut, |
| C: ArpNonSyncCtx<D, P, SC::DeviceId>, |
| SC: ArpContext<D, P, C> |
| + FrameContext<C, B, ArpFrameMetadata<D, <Self as ArpDeviceIdContext<D>>::DeviceId>>, |
| > BufferArpContext<D, P, C, B> for SC |
| { |
| } |
| |
| // 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; |
| } |
| |
| /// The non-synchronized execution context for the ARP protocol. |
| pub(crate) trait ArpNonSyncCtx<D: ArpDevice, P: PType, DeviceId>: |
| TimerContext<ArpTimerId<D, P, DeviceId>> + CounterContext |
| { |
| } |
| impl< |
| DeviceId, |
| D: ArpDevice, |
| P: PType, |
| C: TimerContext<ArpTimerId<D, P, DeviceId>> + CounterContext, |
| > ArpNonSyncCtx<D, P, DeviceId> for C |
| { |
| } |
| |
| /// An execution context for the ARP protocol. |
| pub(crate) trait ArpContext<D: ArpDevice, P: PType, C: ArpNonSyncCtx<D, P, Self::DeviceId>>: |
| ArpDeviceIdContext<D> |
| + StateContext<C, ArpState<D, P>, <Self as ArpDeviceIdContext<D>>::DeviceId> |
| + FrameContext<C, EmptyBuf, ArpFrameMetadata<D, <Self as ArpDeviceIdContext<D>>::DeviceId>> |
| { |
| /// 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, ctx: &mut C, device_id: Self::DeviceId) -> Option<P>; |
| |
| /// Get the hardware address of this interface. |
| fn get_hardware_addr(&self, ctx: &mut C, device_id: Self::DeviceId) -> UnicastAddr<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, |
| ctx: &mut C, |
| 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, ctx: &mut C, 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, ctx: &mut C, device_id: Self::DeviceId, proto_addr: P); |
| } |
| |
| /// An ARP handler for ARP Events. |
| /// |
| /// `ArpHandler<D, P, C>` is implemented for any type which implements |
| /// [`ArpContext<D, P, C>`], and it can also be mocked for use in testing. |
| pub(super) trait ArpHandler<D: ArpDevice, P: PType, C>: |
| ArpDeviceIdContext<D> + TimerHandler<C, 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, ctx: &mut C, device_id: Self::DeviceId); |
| |
| /// Look up the hardware address for a network protocol address. |
| fn lookup( |
| &mut self, |
| ctx: &mut C, |
| 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, |
| ctx: &mut C, |
| device_id: Self::DeviceId, |
| addr: P, |
| hw: D::HType, |
| ); |
| } |
| |
| impl<D: ArpDevice, P: PType, C: ArpNonSyncCtx<D, P, SC::DeviceId>, SC: ArpContext<D, P, C>> |
| ArpHandler<D, P, C> for SC |
| { |
| fn deinitialize(&mut self, ctx: &mut C, device_id: Self::DeviceId) { |
| // Remove all timers associated with the device |
| ctx.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, |
| ctx: &mut C, |
| 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, ctx, device_id, lookup_addr); |
| } |
| |
| result |
| } |
| |
| #[cfg(test)] |
| fn insert_static_neighbor( |
| &mut self, |
| ctx: &mut C, |
| 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 = |
| ctx.cancel_timer(ArpTimerId::new_request_retry(device_id, addr)).is_some(); |
| let _: Option<C::Instant> = |
| ctx.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(ctx, device_id, addr, hw); |
| } |
| |
| self.get_state_mut_with(device_id).table.insert_static(addr, hw); |
| } |
| } |
| |
| /// Handle an ARP timer firing. |
| /// |
| /// Equivalent to calling `sync_ctx.handle_timer`. |
| pub(super) fn handle_timer<D: ArpDevice, P: PType, C, H: ArpHandler<D, P, C>>( |
| sync_ctx: &mut H, |
| ctx: &mut C, |
| id: ArpTimerId<D, P, H::DeviceId>, |
| ) { |
| sync_ctx.handle_timer(ctx, id) |
| } |
| |
| impl<D: ArpDevice, P: PType, C: ArpNonSyncCtx<D, P, SC::DeviceId>, SC: ArpContext<D, P, C>> |
| TimerHandler<C, ArpTimerId<D, P, SC::DeviceId>> for SC |
| { |
| fn handle_timer(&mut self, ctx: &mut C, id: ArpTimerId<D, P, SC::DeviceId>) { |
| match id.inner { |
| ArpTimerIdInner::RequestRetry { proto_addr } => { |
| send_arp_request(self, ctx, 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(ctx, 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(ctx, id.device_id) { |
| let self_hw_addr = self.get_hardware_addr(ctx, id.device_id); |
| // TODO(joshlf): Do something if send_frame returns an |
| // error? |
| let _ = self.send_frame( |
| ctx, |
| ArpFrameMetadata { device_id: id.device_id, dst_addr: D::HType::BROADCAST }, |
| ArpPacketBuilder::new( |
| ArpOp::Request, |
| self_hw_addr.get(), |
| 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 }, |
| } |
| |
| /// Handles an inbound ARP packet. |
| pub(crate) fn handle_packet< |
| D: ArpDevice, |
| P: PType, |
| C: ArpNonSyncCtx<D, P, SC::DeviceId>, |
| B: BufferMut, |
| SC: BufferArpContext<D, P, C, B>, |
| >( |
| sync_ctx: &mut SC, |
| ctx: &mut C, |
| device_id: SC::DeviceId, |
| 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()) == sync_ctx.get_protocol_addr(ctx, 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( |
| sync_ctx, |
| 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. |
| let _: Option<C::Instant> = ctx.cancel_timer(ArpTimerId::new_request_retry( |
| device_id, |
| packet.sender_protocol_address(), |
| )); |
| |
| ctx.increment_counter("arp::rx_gratuitous_resolve"); |
| // Notify device layer: |
| sync_ctx.address_resolved( |
| ctx, |
| 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 |
| || sync_ctx |
| .get_state_with(device_id) |
| .table |
| .lookup(packet.sender_protocol_address()) |
| .is_some() |
| { |
| insert_dynamic( |
| sync_ctx, |
| 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. |
| let _: Option<C::Instant> = ctx.cancel_timer(ArpTimerId::new_request_retry( |
| device_id, |
| packet.sender_protocol_address(), |
| )); |
| |
| ctx.increment_counter("arp::rx_resolve"); |
| // Notify device layer: |
| sync_ctx.address_resolved( |
| ctx, |
| device_id, |
| packet.sender_protocol_address(), |
| packet.sender_hardware_address(), |
| ); |
| } |
| if addressed_to_me && packet.operation() == ArpOp::Request { |
| let self_hw_addr = sync_ctx.get_hardware_addr(ctx, device_id); |
| ctx.increment_counter("arp::rx_request"); |
| // TODO(joshlf): Do something if send_frame returns an error? |
| let _ = sync_ctx.send_frame( |
| ctx, |
| ArpFrameMetadata { device_id, dst_addr: packet.sender_hardware_address() }, |
| ArpPacketBuilder::new( |
| ArpOp::Response, |
| self_hw_addr.get(), |
| 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, C, H: ArpHandler<D, P, C>>( |
| sync_ctx: &mut H, |
| ctx: &mut C, |
| device_id: H::DeviceId, |
| net: P, |
| hw: D::HType, |
| ) { |
| sync_ctx.insert_static_neighbor(ctx, 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: ArpNonSyncCtx<D, P, SC::DeviceId>, |
| SC: ArpContext<D, P, C>, |
| >( |
| sync_ctx: &mut SC, |
| ctx: &mut C, |
| device_id: SC::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 sync_ctx.get_state_mut_with(device_id).table.insert_dynamic(net, hw) { |
| let _: Option<C::Instant> = |
| 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, C, H: ArpHandler<D, P, C>>( |
| sync_ctx: &mut H, |
| ctx: &mut C, |
| device_id: H::DeviceId, |
| local_addr: D::HType, |
| lookup_addr: P, |
| ) -> Option<D::HType> { |
| sync_ctx.lookup(ctx, 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: ArpNonSyncCtx<D, P, SC::DeviceId>, |
| SC: ArpContext<D, P, C>, |
| >( |
| sync_ctx: &mut SC, |
| ctx: &mut C, |
| device_id: SC::DeviceId, |
| lookup_addr: P, |
| ) { |
| let tries_remaining = sync_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) = sync_ctx.get_protocol_addr(ctx, device_id) { |
| let self_hw_addr = sync_ctx.get_hardware_addr(ctx, device_id); |
| // TODO(joshlf): Do something if send_frame returns an error? |
| let _ = sync_ctx.send_frame( |
| ctx, |
| ArpFrameMetadata { device_id, dst_addr: D::HType::BROADCAST }, |
| ArpPacketBuilder::new( |
| ArpOp::Request, |
| self_hw_addr.get(), |
| 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. |
| let _: Option<C::Instant> = ctx.schedule_timer(DEFAULT_ARP_REQUEST_PERIOD, id); |
| sync_ctx |
| .get_state_mut_with(device_id) |
| .table |
| .set_waiting(lookup_addr, tries_remaining - 1); |
| } else { |
| let _: Option<C::Instant> = ctx.cancel_timer(id); |
| sync_ctx.get_state_mut_with(device_id).table.remove(lookup_addr); |
| sync_ctx.address_resolution_failed(ctx, 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. |
| let _: Option<_> = 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) => { |
| let _: &mut _ = entry.insert(new_val); |
| } |
| } |
| true |
| } |
| |
| fn remove(&mut self, net: P) { |
| let _: Option<_> = 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) { |
| // TODO(https://fxbug.dev/81368): Remove this type. |
| // |
| // This type (ArpTable) is type system-defeating. Instead of interacting with entries in |
| // the hash map, we do these point-access, which is both suboptimal and impossible to |
| // reason about because the type information is far away. This entire module needs a |
| // rewrite, so I am just sprinkling `let _` here for now. |
| let _: Option<_> = 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 alloc::{vec, vec::Vec}; |
| use core::iter; |
| |
| use net_types::{ethernet::Mac, ip::Ipv4Addr}; |
| use packet::{ParseBuffer, Serializer}; |
| use packet_formats::arp::{peek_arp_types, ArpHardwareType, ArpNetworkType, ArpPacketBuilder}; |
| use test_case::test_case; |
| |
| use super::*; |
| use crate::{ |
| context::{ |
| testutil::{ |
| DummyCtx, DummyInstant, DummyNetwork, DummyNonSyncCtx, DummySyncCtx, |
| DummyTimerCtxExt, |
| }, |
| InstantContext, |
| }, |
| device::ethernet::EthernetLinkDevice, |
| ip::DummyDeviceId, |
| 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]); |
| const TEST_ENTRY_EXPIRATION_TIMER_ID: ArpTimerId<EthernetLinkDevice, Ipv4Addr, ()> = |
| ArpTimerId { |
| device_id: (), |
| inner: ArpTimerIdInner::EntryExpiration { proto_addr: TEST_REMOTE_IPV4 }, |
| _marker: PhantomData, |
| }; |
| const TEST_REQUEST_RETRY_TIMER_ID: ArpTimerId<EthernetLinkDevice, Ipv4Addr, ()> = ArpTimerId { |
| device_id: (), |
| inner: ArpTimerIdInner::RequestRetry { proto_addr: TEST_REMOTE_IPV4 }, |
| _marker: PhantomData, |
| }; |
| |
| /// A dummy `ArpContext` that stores frames, address resolution events, and |
| /// address resolution failure events. |
| struct DummyArpCtx { |
| proto_addr: Option<Ipv4Addr>, |
| hw_addr: UnicastAddr<Mac>, |
| addr_resolved: Vec<(Ipv4Addr, Mac)>, |
| addr_resolution_failed: Vec<Ipv4Addr>, |
| addr_resolution_expired: Vec<Ipv4Addr>, |
| arp_state: ArpState<EthernetLinkDevice, Ipv4Addr>, |
| } |
| |
| impl Default for DummyArpCtx { |
| fn default() -> DummyArpCtx { |
| DummyArpCtx { |
| proto_addr: Some(TEST_LOCAL_IPV4), |
| hw_addr: UnicastAddr::new(TEST_LOCAL_MAC).unwrap(), |
| addr_resolved: Vec::new(), |
| addr_resolution_failed: Vec::new(), |
| addr_resolution_expired: Vec::new(), |
| arp_state: ArpState::default(), |
| } |
| } |
| } |
| |
| type MockNonSyncCtx = DummyNonSyncCtx<ArpTimerId<EthernetLinkDevice, Ipv4Addr, ()>, (), ()>; |
| |
| type MockCtx = |
| DummySyncCtx<DummyArpCtx, ArpFrameMetadata<EthernetLinkDevice, ()>, DummyDeviceId>; |
| |
| impl ArpDeviceIdContext<EthernetLinkDevice> for MockCtx { |
| type DeviceId = (); |
| } |
| |
| impl ArpContext<EthernetLinkDevice, Ipv4Addr, MockNonSyncCtx> for MockCtx { |
| fn get_protocol_addr(&self, _ctx: &mut MockNonSyncCtx, _device_id: ()) -> Option<Ipv4Addr> { |
| self.get_ref().proto_addr |
| } |
| |
| fn get_hardware_addr(&self, _ctx: &mut MockNonSyncCtx, _device_id: ()) -> UnicastAddr<Mac> { |
| self.get_ref().hw_addr |
| } |
| |
| fn address_resolved( |
| &mut self, |
| _ctx: &mut MockNonSyncCtx, |
| _device_id: (), |
| proto_addr: Ipv4Addr, |
| hw_addr: Mac, |
| ) { |
| self.get_mut().addr_resolved.push((proto_addr, hw_addr)); |
| } |
| |
| fn address_resolution_failed( |
| &mut self, |
| _ctx: &mut MockNonSyncCtx, |
| _device_id: (), |
| proto_addr: Ipv4Addr, |
| ) { |
| self.get_mut().addr_resolution_failed.push(proto_addr); |
| } |
| |
| fn address_resolution_expired( |
| &mut self, |
| _ctx: &mut MockNonSyncCtx, |
| _device_id: (), |
| proto_addr: Ipv4Addr, |
| ) { |
| self.get_mut().addr_resolution_expired.push(proto_addr); |
| } |
| } |
| |
| impl StateContext<MockNonSyncCtx, ArpState<EthernetLinkDevice, Ipv4Addr>> for MockCtx { |
| 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( |
| sync_ctx: &mut MockCtx, |
| ctx: &mut MockNonSyncCtx, |
| 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, ArpHardwareType::Ethernet); |
| assert_eq!(proto, ArpNetworkType::Ipv4); |
| |
| handle_packet::<_, Ipv4Addr, _, _, _>(sync_ctx, 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( |
| sync_ctx: &MockCtx, |
| total_frames: usize, |
| dst: Mac, |
| op: ArpOp, |
| local_ipv4: Ipv4Addr, |
| remote_ipv4: Ipv4Addr, |
| local_mac: Mac, |
| remote_mac: Mac, |
| ) { |
| assert_eq!(sync_ctx.frames().len(), total_frames); |
| let (meta, frame) = &sync_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 `sync_ctx` contains exactly one installed timer with the given |
| // instant and ID. |
| fn validate_single_timer( |
| non_sync_ctx: &MockNonSyncCtx, |
| instant: Duration, |
| id: ArpTimerId<EthernetLinkDevice, Ipv4Addr, ()>, |
| ) { |
| non_sync_ctx.timer_ctx().assert_timers_installed([(id, DummyInstant::from(instant))]); |
| } |
| |
| fn validate_single_retry_timer( |
| non_sync_ctx: &MockNonSyncCtx, |
| instant: Duration, |
| addr: Ipv4Addr, |
| ) { |
| validate_single_timer(non_sync_ctx, instant, ArpTimerId::new_request_retry((), addr)) |
| } |
| |
| fn validate_single_entry_timer( |
| non_sync_ctx: &MockNonSyncCtx, |
| instant: Duration, |
| addr: Ipv4Addr, |
| ) { |
| validate_single_timer(non_sync_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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::default()); |
| send_arp_packet( |
| &mut sync_ctx, |
| &mut non_sync_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!( |
| sync_ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4), |
| Some(&TEST_REMOTE_MAC) |
| ); |
| // Gratuitous ARPs should not prompt a response. |
| assert_empty(sync_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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::default()); |
| send_arp_packet( |
| &mut sync_ctx, |
| &mut non_sync_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!( |
| sync_ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4), |
| Some(&TEST_REMOTE_MAC) |
| ); |
| // Gratuitous ARPs should not send a response. |
| assert_empty(sync_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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::default()); |
| |
| assert_eq!( |
| lookup(&mut sync_ctx, &mut non_sync_ctx, (), TEST_LOCAL_MAC, TEST_REMOTE_IPV4), |
| None |
| ); |
| |
| // We should have installed a single retry timer. |
| validate_single_retry_timer(&non_sync_ctx, DEFAULT_ARP_REQUEST_PERIOD, TEST_REMOTE_IPV4); |
| |
| send_arp_packet( |
| &mut sync_ctx, |
| &mut non_sync_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!( |
| sync_ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4), |
| Some(&TEST_REMOTE_MAC) |
| ); |
| |
| // The retry timer should be canceled, and replaced by an entry |
| // expiration timer. |
| validate_single_entry_timer( |
| &non_sync_ctx, |
| DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD, |
| TEST_REMOTE_IPV4, |
| ); |
| |
| // We should have notified the device layer. |
| assert_eq!( |
| sync_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!(sync_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 MockNonSyncCtx2 = |
| DummyNonSyncCtx<ArpTimerId<EthernetLinkDevice, Ipv4Addr, usize>, (), ()>; |
| |
| type MockCtx2 = crate::context::testutil::DummySyncCtx< |
| DummyArpCtx, |
| ArpFrameMetadata<EthernetLinkDevice, usize>, |
| DummyDeviceId, |
| >; |
| |
| impl ArpDeviceIdContext<EthernetLinkDevice> for MockCtx2 { |
| type DeviceId = usize; |
| } |
| |
| impl ArpContext<EthernetLinkDevice, Ipv4Addr, MockNonSyncCtx2> for MockCtx2 { |
| fn get_protocol_addr( |
| &self, |
| _ctx: &mut MockNonSyncCtx2, |
| _device_id: usize, |
| ) -> Option<Ipv4Addr> { |
| self.get_ref().proto_addr |
| } |
| |
| fn get_hardware_addr( |
| &self, |
| _ctx: &mut MockNonSyncCtx2, |
| _device_id: usize, |
| ) -> UnicastAddr<Mac> { |
| self.get_ref().hw_addr |
| } |
| |
| fn address_resolved( |
| &mut self, |
| _ctx: &mut MockNonSyncCtx2, |
| _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, |
| _ctx: &mut MockNonSyncCtx2, |
| _device_id: usize, |
| proto_addr: Ipv4Addr, |
| ) { |
| self.get_mut().addr_resolution_failed.push(proto_addr); |
| } |
| |
| fn address_resolution_expired( |
| &mut self, |
| _ctx: &mut MockNonSyncCtx2, |
| _device_id: usize, |
| proto_addr: Ipv4Addr, |
| ) { |
| self.get_mut().addr_resolution_expired.push(proto_addr); |
| } |
| } |
| |
| impl StateContext<MockNonSyncCtx2, ArpState<EthernetLinkDevice, Ipv4Addr>, usize> for MockCtx2 { |
| 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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx2::default()); |
| |
| let device_id_0: usize = 0; |
| let device_id_1: usize = 1; |
| |
| assert_eq!( |
| lookup(&mut sync_ctx, &mut non_sync_ctx, device_id_0, TEST_LOCAL_MAC, TEST_REMOTE_IPV4), |
| None |
| ); |
| |
| // We should have installed a single retry timer. |
| let deadline = non_sync_ctx.now() + DEFAULT_ARP_REQUEST_PERIOD; |
| let timer = ArpTimerId::new_request_retry(device_id_0, TEST_REMOTE_IPV4); |
| non_sync_ctx.timer_ctx().assert_timers_installed([(timer, deadline)]); |
| |
| // Deinitializing a different ID should not impact the current timer. |
| deinitialize(&mut sync_ctx, &mut non_sync_ctx, device_id_1); |
| non_sync_ctx.timer_ctx().assert_timers_installed([(timer, deadline)]); |
| |
| // Deinitializing the correct ID should cancel the timer. |
| deinitialize(&mut sync_ctx, &mut non_sync_ctx, device_id_0); |
| non_sync_ctx.timer_ctx().assert_no_timers_installed(); |
| } |
| |
| #[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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::default()); |
| |
| // Perform the lookup. |
| assert_eq!( |
| lookup(&mut sync_ctx, &mut non_sync_ctx, (), TEST_LOCAL_MAC, TEST_REMOTE_IPV4), |
| None |
| ); |
| |
| // We should have sent a single ARP request. |
| validate_last_arp_packet( |
| &sync_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(&non_sync_ctx, DEFAULT_ARP_REQUEST_PERIOD, TEST_REMOTE_IPV4); |
| |
| // Test that, when we receive an ARP response, we cancel the timer. |
| send_arp_packet( |
| &mut sync_ctx, |
| &mut non_sync_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!( |
| sync_ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4), |
| Some(&TEST_REMOTE_MAC) |
| ); |
| |
| // We should have notified the device layer. |
| assert_eq!( |
| sync_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( |
| &non_sync_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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::default()); |
| |
| // Perform a gratuitous ARP to populate the cache. |
| send_arp_packet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| ArpOp::Response, |
| TEST_REMOTE_IPV4, |
| TEST_REMOTE_IPV4, |
| TEST_REMOTE_MAC, |
| TEST_REMOTE_MAC, |
| ); |
| |
| // Perform the lookup. |
| assert_eq!( |
| lookup(&mut sync_ctx, &mut non_sync_ctx, (), TEST_LOCAL_MAC, TEST_REMOTE_IPV4), |
| Some(TEST_REMOTE_MAC) |
| ); |
| |
| // We should not have sent any ARP request. |
| assert_empty(sync_ctx.frames().iter()); |
| // We should not have set a retry timer. |
| assert_eq!( |
| non_sync_ctx.cancel_timer(ArpTimerId::new_request_retry((), TEST_REMOTE_IPV4)), |
| 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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::default()); |
| |
| assert_eq!( |
| lookup(&mut sync_ctx, &mut non_sync_ctx, (), TEST_LOCAL_MAC, TEST_REMOTE_IPV4), |
| None |
| ); |
| |
| // `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( |
| &sync_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!( |
| sync_ctx.get_ref().arp_state.table.get_remaining_tries(TEST_REMOTE_IPV4), |
| Some(DEFAULT_ARP_REQUEST_MAX_TRIES - i) |
| ); |
| |
| // There should be a single ARP request retry timer installed. |
| validate_single_retry_timer( |
| &non_sync_ctx, |
| // Duration only implements Mul<u32> |
| DEFAULT_ARP_REQUEST_PERIOD * (i as u32), |
| TEST_REMOTE_IPV4, |
| ); |
| |
| // Trigger the ARP request retry timer. |
| assert_eq!( |
| non_sync_ctx.trigger_next_timer(&mut sync_ctx, TimerHandler::handle_timer), |
| Some(TEST_REQUEST_RETRY_TIMER_ID) |
| ); |
| } |
| |
| // 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( |
| &sync_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. |
| non_sync_ctx.timer_ctx().assert_no_timers_installed(); |
| |
| // The table entry should have been completely removed. |
| assert_eq!(sync_ctx.get_ref().arp_state.table.table.get(&TEST_REMOTE_IPV4), None); |
| |
| // We should have notified the device layer of the failure. |
| assert_eq!(sync_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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::default()); |
| |
| send_arp_packet( |
| &mut sync_ctx, |
| &mut non_sync_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 sync_ctx, &mut non_sync_ctx, (), TEST_LOCAL_MAC, TEST_REMOTE_IPV4), |
| Some(TEST_REMOTE_MAC) |
| ); |
| |
| // We should have sent an ARP response. |
| validate_last_arp_packet( |
| &sync_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); |
| let mac = Mac::new([1, 2, 3, 4, 5, 6]); |
| assert!(t.insert_dynamic(Ipv4Addr::new([10, 0, 0, 1]), mac)); |
| assert_eq!(t.lookup(Ipv4Addr::new([10, 0, 0, 1])), Some(&mac)); |
| assert_eq!(t.lookup(Ipv4Addr::new([10, 0, 0, 2])), None); |
| } |
| |
| 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 = DummyNetwork::new( |
| { |
| host_iter.clone().map(|cfg| { |
| let ArpHostConfig { name, proto_addr, hw_addr } = cfg; |
| let mut ctx = DummyCtx::with_sync_ctx(MockCtx::default()); |
| let DummyCtx { sync_ctx, non_sync_ctx: _ } = &mut ctx; |
| sync_ctx.get_mut().hw_addr = UnicastAddr::new(*hw_addr).unwrap(); |
| sync_ctx.get_mut().proto_addr = Some(*proto_addr); |
| (*name, ctx) |
| }) |
| }, |
| |ctx: &str, _meta| { |
| host_iter |
| .clone() |
| .filter_map(|cfg| { |
| let ArpHostConfig { name, proto_addr: _, hw_addr: _ } = cfg; |
| if !ctx.eq(*name) { |
| Some((*name, (), 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; |
| |
| // The lookup should fail. |
| network.with_context(local_name, |DummyCtx { sync_ctx, non_sync_ctx }| { |
| assert_eq!( |
| lookup(sync_ctx, non_sync_ctx, (), local_hw_addr, requested_remote_proto_addr), |
| None |
| ); |
| |
| // We should have sent an ARP request. |
| validate_last_arp_packet( |
| sync_ctx, |
| 1, |
| Mac::BROADCAST, |
| ArpOp::Request, |
| local_proto_addr, |
| requested_remote_proto_addr, |
| local_hw_addr, |
| Mac::BROADCAST, |
| ); |
| // We should have installed a retry timer. |
| validate_single_retry_timer( |
| non_sync_ctx, |
| DEFAULT_ARP_REQUEST_PERIOD, |
| requested_remote_proto_addr, |
| ); |
| }); |
| // Step once to deliver the ARP request to the remotes. |
| let res = network.step( |
| |DummyCtx { sync_ctx, non_sync_ctx }, device_id, buf| { |
| handle_packet(sync_ctx, non_sync_ctx, device_id, buf) |
| }, |
| |DummyCtx { sync_ctx, non_sync_ctx }, _ctx, id| { |
| TimerHandler::handle_timer(sync_ctx, non_sync_ctx, id) |
| }, |
| ); |
| 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. |
| assert_eq!( |
| network |
| .sync_ctx(requested_remote_name) |
| .get_ref() |
| .arp_state |
| .table |
| .lookup(local_proto_addr), |
| Some(&LOCAL_HOST_CFG.hw_addr) |
| ); |
| // The requested remote should have sent an ARP response. |
| validate_last_arp_packet( |
| network.sync_ctx(requested_remote_name), |
| 1, |
| local_hw_addr, |
| ArpOp::Response, |
| requested_remote_proto_addr, |
| local_proto_addr, |
| requested_remote_hw_addr, |
| local_hw_addr, |
| ); |
| |
| other_remote_cfgs.iter().for_each(|non_requested_remote| { |
| let ArpHostConfig { name: unrequested_remote_name, proto_addr: _, hw_addr: _ } = |
| non_requested_remote; |
| // The non-requested_remote should not have populated its ARP cache. |
| assert_eq!( |
| network |
| .sync_ctx(*unrequested_remote_name) |
| .get_ref() |
| .arp_state |
| .table |
| .lookup(local_proto_addr), |
| None |
| ); |
| |
| // The non-requested_remote should not have sent an ARP response. |
| assert_empty(network.sync_ctx(*unrequested_remote_name).frames().iter()); |
| }); |
| |
| // Step once to deliver the ARP response to the local. |
| let res = network.step( |
| |DummyCtx { sync_ctx, non_sync_ctx }, device_id, buf| { |
| handle_packet(sync_ctx, non_sync_ctx, device_id, buf) |
| }, |
| |DummyCtx { sync_ctx, non_sync_ctx }, _ctx, id| { |
| TimerHandler::handle_timer(sync_ctx, non_sync_ctx, id) |
| }, |
| ); |
| 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. |
| assert_eq!( |
| network |
| .sync_ctx(local_name) |
| .get_ref() |
| .arp_state |
| .table |
| .lookup(requested_remote_proto_addr), |
| Some(&requested_remote_hw_addr) |
| ); |
| // The retry timer should be canceled, and replaced by an entry |
| // expiration timer. |
| network.with_context(local_name, |DummyCtx { sync_ctx: _, non_sync_ctx }| { |
| validate_single_entry_timer( |
| non_sync_ctx, |
| DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD, |
| requested_remote_proto_addr, |
| ); |
| }); |
| // The device layer should have been notified. |
| assert_eq!( |
| network.sync_ctx(local_name).get_ref().addr_resolved.as_slice(), |
| [(requested_remote_proto_addr, requested_remote_hw_addr)] |
| ); |
| } |
| |
| #[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]); |
| assert!(table.insert_dynamic(ip, mac0)); |
| assert_eq!(table.lookup(ip), Some(&mac0)); |
| table.insert_static(ip, mac1); |
| assert_eq!(table.lookup(ip), Some(&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), Some(&mac0)); |
| table.insert_static(ip, mac1); |
| assert_eq!(table.lookup(ip), Some(&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), Some(&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); |
| assert!(table.insert_dynamic(ip, mac1)); |
| assert_eq!(table.lookup(ip), Some(&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]); |
| assert!(table.insert_dynamic(ip, mac0)); |
| assert_eq!(table.lookup(ip), Some(&mac0)); |
| assert!(table.insert_dynamic(ip, mac1)); |
| assert_eq!(table.lookup(ip), Some(&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), Some(&mac0)); |
| assert!(!table.insert_dynamic(ip, mac1)); |
| assert_eq!(table.lookup(ip), Some(&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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::default()); |
| |
| // Perform a lookup in order to kick off a request and install a retry |
| // timer. |
| assert_eq!( |
| lookup(&mut sync_ctx, &mut non_sync_ctx, (), TEST_LOCAL_MAC, TEST_REMOTE_IPV4), |
| None |
| ); |
| |
| // We should be in the Waiting state. |
| assert_eq!( |
| sync_ctx.get_ref().arp_state.table.get_remaining_tries(TEST_REMOTE_IPV4), |
| Some(DEFAULT_ARP_REQUEST_MAX_TRIES - 1) |
| ); |
| // We should have an ARP request retry timer set. |
| validate_single_retry_timer(&non_sync_ctx, DEFAULT_ARP_REQUEST_PERIOD, TEST_REMOTE_IPV4); |
| |
| // Now insert a static entry. |
| insert_static_neighbor( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| (), |
| TEST_REMOTE_IPV4, |
| TEST_REMOTE_MAC, |
| ); |
| |
| // The timer should have been canceled. |
| non_sync_ctx.timer_ctx().assert_no_timers_installed(); |
| |
| // We should have notified the device layer. |
| assert_eq!( |
| sync_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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::default()); |
| |
| insert_dynamic(&mut sync_ctx, &mut non_sync_ctx, (), TEST_REMOTE_IPV4, TEST_REMOTE_MAC); |
| |
| // We should have the address in cache. |
| assert_eq!( |
| sync_ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4), |
| Some(&TEST_REMOTE_MAC) |
| ); |
| // We should have an ARP entry expiration timer set. |
| validate_single_entry_timer( |
| &non_sync_ctx, |
| DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD, |
| TEST_REMOTE_IPV4, |
| ); |
| |
| // Now insert a static entry. |
| insert_static_neighbor( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| (), |
| TEST_REMOTE_IPV4, |
| TEST_REMOTE_MAC, |
| ); |
| |
| // The timer should have been canceled. |
| non_sync_ctx.timer_ctx().assert_no_timers_installed(); |
| } |
| |
| #[test] |
| fn test_arp_entry_expiration() { |
| // Test that, if a dynamic entry is installed, it is removed after the |
| // appropriate amount of time. |
| |
| let DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::default()); |
| |
| insert_dynamic(&mut sync_ctx, &mut non_sync_ctx, (), TEST_REMOTE_IPV4, TEST_REMOTE_MAC); |
| |
| // We should have the address in cache. |
| assert_eq!( |
| sync_ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4), |
| Some(&TEST_REMOTE_MAC) |
| ); |
| // We should have an ARP entry expiration timer set. |
| validate_single_entry_timer( |
| &non_sync_ctx, |
| DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD, |
| TEST_REMOTE_IPV4, |
| ); |
| |
| // Trigger the entry expiration timer. |
| assert_eq!( |
| non_sync_ctx.trigger_next_timer(&mut sync_ctx, TimerHandler::handle_timer), |
| Some(TEST_ENTRY_EXPIRATION_TIMER_ID) |
| ); |
| |
| // The right amount of time should have elapsed. |
| assert_eq!(non_sync_ctx.now(), DummyInstant::from(DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD)); |
| // The entry should have been removed. |
| assert_eq!(sync_ctx.get_ref().arp_state.table.table.get(&TEST_REMOTE_IPV4), None); |
| // The timer should have been canceled. |
| non_sync_ctx.timer_ctx().assert_no_timers_installed(); |
| // The device layer should have been notified. |
| assert_eq!(sync_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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::default()); |
| |
| insert_dynamic(&mut sync_ctx, &mut non_sync_ctx, (), TEST_REMOTE_IPV4, TEST_REMOTE_MAC); |
| |
| // Let 5 seconds elapse. |
| assert_empty(non_sync_ctx.trigger_timers_until_instant( |
| &mut sync_ctx, |
| DummyInstant::from(Duration::from_secs(5)), |
| TimerHandler::handle_timer, |
| )); |
| |
| // The entry should still be there. |
| assert_eq!( |
| sync_ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4), |
| Some(&TEST_REMOTE_MAC) |
| ); |
| |
| // Receive the gratuitous ARP response. |
| send_arp_packet( |
| &mut sync_ctx, |
| &mut non_sync_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_empty(non_sync_ctx.trigger_timers_until_instant( |
| &mut sync_ctx, |
| DummyInstant::from(DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD), |
| TimerHandler::handle_timer, |
| )); |
| // The entry should still be there. |
| assert_eq!( |
| sync_ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4), |
| Some(&TEST_REMOTE_MAC) |
| ); |
| |
| // Trigger the entry expiration timer. |
| assert_eq!( |
| non_sync_ctx.trigger_next_timer(&mut sync_ctx, TimerHandler::handle_timer), |
| Some(TEST_ENTRY_EXPIRATION_TIMER_ID) |
| ); |
| // The right amount of time should have elapsed. |
| assert_eq!( |
| non_sync_ctx.now(), |
| DummyInstant::from(Duration::from_secs(5) + DEFAULT_ARP_ENTRY_EXPIRATION_PERIOD) |
| ); |
| // The entry should be gone. |
| assert_eq!(sync_ctx.get_ref().arp_state.table.lookup(TEST_REMOTE_IPV4), None); |
| // The device layer should have been notified. |
| assert_eq!(sync_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 DummyCtx { mut sync_ctx, mut non_sync_ctx } = |
| DummyCtx::with_sync_ctx(MockCtx::default()); |
| |
| insert_static_neighbor( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| (), |
| TEST_REMOTE_IPV4, |
| TEST_REMOTE_MAC, |
| ); |
| non_sync_ctx.timer_ctx().assert_no_timers_installed(); |
| |
| insert_dynamic(&mut sync_ctx, &mut non_sync_ctx, (), TEST_REMOTE_IPV4, TEST_REMOTE_MAC); |
| non_sync_ctx.timer_ctx().assert_no_timers_installed(); |
| } |
| } |