blob: 3766d375464cf8ad6acc4125eb5023989daa9ddf [file] [log] [blame]
// Copyright 2019 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 Neighbor Discovery Protocol (NDP).
//!
//! Neighbor Discovery for IPv6 as defined in [RFC 4861] defines mechanisms for
//! solving the following problems:
//! - Router Discovery
//! - Prefix Discovery
//! - Parameter Discovery
//! - Address Autoconfiguration
//! - Address resolution
//! - Next-hop determination
//! - Neighbor Unreachability Detection
//! - Duplicate Address Detection
//! - Redirect
//!
//! [RFC 4861]: https://tools.ietf.org/html/rfc4861
use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use std::num::{NonZeroU16, NonZeroU32, NonZeroU8};
use std::ops::RangeInclusive;
use std::time::Duration;
use log::{debug, error, trace};
use net_types::ip::{AddrSubnet, Ip, IpAddress, Ipv6, Ipv6Addr};
use net_types::{LinkLocalAddr, LinkLocalAddress, MulticastAddress};
use packet::{EmptyBuf, InnerPacketBuilder, Serializer};
use rand::{thread_rng, Rng};
use zerocopy::ByteSlice;
use crate::device::ethernet::EthernetNdpDevice;
use crate::device::{DeviceId, DeviceLayerTimerId, DeviceProtocol, Tentative};
use crate::ip::{IpDeviceIdContext, IpProto};
use crate::wire::icmp::ndp::options::{NdpOption, PrefixInformation};
use crate::wire::icmp::ndp::{
self, NeighborAdvertisement, NeighborSolicitation, Options, RouterAdvertisement,
RouterSolicitation,
};
use crate::wire::icmp::{IcmpMessage, IcmpPacketBuilder, IcmpUnusedCode, Icmpv6Packet};
use crate::wire::ipv6::Ipv6PacketBuilder;
use crate::{Context, EventDispatcher, StackState, TimerId, TimerIdInner};
//
// Default Router configurations
//
// See [`NdpRouterConfigurations`] for more details.
//
// Note that AdvSendAdvertisements MUST be FALSE by default so that a node will not accidentally
// start acting as a default router. Nodes must be explicitly configured by system management to
// send Router Advertisements.
const SHOULD_SEND_ADVERTISEMENTS_DEFAULT: bool = false;
const ROUTER_ADVERTISEMENTS_INTERVAL_DEFAULT: RangeInclusive<u16> = 200..=600;
const ADVERTISED_MANAGED_FLAG_DEFAULT: bool = false;
const ADVERTISED_OTHER_CONFIG_FLAG: bool = false;
const ADVERTISED_LINK_MTU: Option<NonZeroU32> = None;
const ADVERTISED_REACHABLE_TIME: u32 = 0;
const ADVERTISED_RETRANSMIT_TIMER: u32 = 0;
const ADVERTISED_CURRENT_HOP_LIMIT: u8 = 64;
// This call to `new_unchecked` is okay becase 1800 is non-zero, so we will not be violating
// `NonZeroU16`'s contract.
const ADVERTISED_DEFAULT_LIFETIME: Option<NonZeroU16> =
unsafe { Some(NonZeroU16::new_unchecked(1800)) };
/// The number of NS messages to be sent to perform DAD
/// [RFC 4862 section 5.1]
///
/// [RFC 4862 section 5.1]: https://tools.ietf.org/html/rfc4862#section-5.1
pub(crate) const DUP_ADDR_DETECT_TRANSMITS: u8 = 1;
//
// Node Constants
//
/// The default value for the default hop limit to be used when sending IP
/// packets.
// We know the call to `new_unchecked` is safe because 64 is non-zero.
pub(crate) const HOP_LIMIT_DEFAULT: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(64) };
/// The default value for *BaseReachableTime* as defined in
/// [RFC 4861 section 10].
///
/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
const REACHABLE_TIME_DEFAULT: Duration = Duration::from_secs(30);
/// The default value for *RetransTimer* as defined in
/// [RFC 4861 section 10].
///
/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
const RETRANS_TIMER_DEFAULT: Duration = Duration::from_secs(1);
/// The maximum number of multicast solicitations as defined in
/// [RFC 4861 section 10].
///
/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
const MAX_MULTICAST_SOLICIT: u8 = 3;
//
// Host Constants
//
/// Maximum number of Router Solicitation messages that may be sent
/// when attempting to discover routers. Each message sent must be
/// seperated by at least `RTR_SOLICITATION_INTERVAL` as defined in
/// [RFC 4861 section 10].
///
/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
pub(crate) const MAX_RTR_SOLICITATIONS: u8 = 3;
/// Minimum duration between router solicitation messages as defined in
/// [RFC 4861 section 10].
///
/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
const RTR_SOLICITATION_INTERVAL: Duration = Duration::from_secs(4);
/// Amount of time to wait after sending `MAX_RTR_SOLICITATIONS` Router
/// Solicitation messages before determining that there are no routers on
/// the link for the purpose of IPv6 Stateless Address Autoconfiguration
/// if no Router Advertisement messages have been received as defined in
/// [RFC 4861 section 10].
///
/// This parameter is also used when a host sends its initial Router
/// Solicitation message, as per [RFC 4861 section 6.3.7]. Before a node
/// sends an initial solicitation, it SHOULD delay the transmission for
/// a random amount of time between 0 and `MAX_RTR_SOLICITATION_DELAY`.
/// This serves to alleviate congestion when many hosts start up on a
/// link at the same time.
///
/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
/// [RFC 4861 section 6.3.7]: https://tools.ietf.org/html/rfc4861#section-6.3.7
const MAX_RTR_SOLICITATION_DELAY: Duration = Duration::from_secs(1);
/// A link layer address that can be discovered using NDP.
pub(crate) trait LinkLayerAddress: Copy + Clone + Debug + PartialEq {
/// The length, in bytes, expected for the `LinkLayerAddress`
const BYTES_LENGTH: usize;
/// Returns the underlying bytes of a `LinkLayerAddress`
fn bytes(&self) -> &[u8];
/// Attempts to construct a `LinkLayerAddress` from the provided bytes.
///
/// `bytes` is guaranteed to be **exactly** `BYTES_LENGTH` long.
fn from_bytes(bytes: &[u8]) -> Self;
}
/// The various states an IP address can be on an interface.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum AddressState {
/// The address is assigned to an interface and can be considered
/// bound to it (all packets destined to the address will be
/// accepted).
Assigned,
/// The address is unassigned to an interface. Packets destined to
/// an unassigned address will be dropped, or forwarded if the
/// interface is acting as a router and possible to forward.
Unassigned,
/// The address is considered unassigned to an interface for normal
/// operations, but has the intention of being assigned in the future
/// (e.g. once NDP's Duplicate Address Detection is completed).
Tentative,
}
impl AddressState {
/// Is this address unassigned?
pub(crate) fn is_unassigned(self) -> bool {
self == AddressState::Unassigned
}
}
/// A device layer protocol which can support NDP.
///
/// An `NdpDevice` is a device layer protocol which can support NDP.
pub(crate) trait NdpDevice: Sized {
/// The link-layer address type used by this device.
type LinkAddress: LinkLayerAddress;
/// The broadcast value for link addresses on this device.
// NOTE(brunodalbo): RFC 4861 mentions the possibility of running NDP on
// link types that do not support broadcasts, but this implementation does
// not cover that for simplicity.
const BROADCAST: Self::LinkAddress;
/// Get a reference to a device's NDP state.
fn get_ndp_state<D: EventDispatcher>(
state: &StackState<D>,
device_id: usize,
) -> &NdpState<Self>;
/// Get a mutable reference to a device's NDP state.
fn get_ndp_state_mut<D: EventDispatcher>(
state: &mut StackState<D>,
device_id: usize,
) -> &mut NdpState<Self>;
/// Get the link layer address for a device.
fn get_link_layer_addr<D: EventDispatcher>(
state: &StackState<D>,
device_id: usize,
) -> Self::LinkAddress;
/// Get the link-local address for a device.
///
/// If no link-local address is assigned for `device_id`, `None` will be returned. Otherwise,
/// a `Some(a)` will be returned.
fn get_link_local_addr<D: EventDispatcher>(
state: &StackState<D>,
device_id: usize,
) -> Option<Tentative<Ipv6Addr>>;
/// Get a (possibly tentative) IPv6 address for this device.
///
/// Any **unicast** IPv6 address is a valid return value. Violating this
/// rule may result in incorrect IP packets being sent.
fn get_ipv6_addr<D: EventDispatcher>(
state: &StackState<D>,
device_id: usize,
) -> Option<Tentative<Ipv6Addr>>;
/// Returns the state of `address` on the device identified
/// by `device_id`.
///
/// `address` is guaranteed to be a valid unicast address.
fn ipv6_addr_state<D: EventDispatcher>(
state: &StackState<D>,
device_id: usize,
address: &Ipv6Addr,
) -> AddressState;
/// Send a packet in a device layer frame to a destination `LinkAddress`.
///
/// `send_ipv6_frame_to` accepts a device ID, a destination hardware
/// address, and a `Serializer`. Implementers are expected simply to form
/// a link-layer frame and encapsulate the provided IPv6 body.
///
/// # Panics
///
/// May panic if `device_id` is not intialized. See [`crate::device::initialize_device`]
/// for more information.
fn send_ipv6_frame_to<D: EventDispatcher, S: Serializer<Buffer = EmptyBuf>>(
ctx: &mut Context<D>,
device_id: usize,
dst: Self::LinkAddress,
body: S,
) -> Result<(), S>;
/// Send a packet in a device layer frame.
///
/// `send_ipv6_frame` accepts a device ID, a next hop IP address, and a
/// `Serializer`. Implementers must resolve the destination link-layer
/// address from the provided `next_hop` IPv6 address.
///
/// # Panics
///
/// May panic if `device_id` is not intialized. See [`crate::device::initialize_device`]
/// for more information.
fn send_ipv6_frame<D: EventDispatcher, S: Serializer<Buffer = EmptyBuf>>(
ctx: &mut Context<D>,
device_id: usize,
next_hop: Ipv6Addr,
body: S,
) -> Result<(), S>;
/// Retrieves the complete `DeviceId` for a given `id`.
fn get_device_id(id: usize) -> DeviceId;
/// Notifies device layer that the link-layer address for the neighbor in
/// `address` has been resolved to `link_address`.
///
/// Implementers may use this signal to dispatch any packets that
/// were queued waiting for address resolution.
fn address_resolved<D: EventDispatcher>(
ctx: &mut Context<D>,
device_id: usize,
address: &Ipv6Addr,
link_address: Self::LinkAddress,
);
/// Notifies the device layer that the link-layer address resolution for
/// the neighbor in `address` failed.
fn address_resolution_failed<D: EventDispatcher>(
ctx: &mut Context<D>,
device_id: usize,
address: &Ipv6Addr,
);
/// Notifies the device layer that a duplicate address has been detected. The
/// device should want to remove the address.
fn duplicate_address_detected<D: EventDispatcher>(
ctx: &mut Context<D>,
device_id: usize,
addr: Ipv6Addr,
);
/// Notifies the device layer that the address is very likely (because DAD
/// is not reliable) to be unique, it is time to mark it to be permanent.
///
/// # Panics
///
/// Panics if `addr` is not tentative on the devide identified by `device_id`.
fn unique_address_determined<D: EventDispatcher>(
state: &mut StackState<D>,
device_id: usize,
addr: Ipv6Addr,
);
/// Set Link MTU.
///
/// `set_mtu` is used when a host receives a Router Advertisement with the MTU option.
///
/// `set_mtu` MAY set the device's new MTU to a value less than `mtu` if the device does not
/// support using `mtu` as its new MTU. `set_mtu` MUST NOT use a new MTU value that is greater
/// than `mtu`.
///
/// See [RFC 4861 section 6.3.4] for more information.
///
/// # Panics
///
/// `set_mtu` is allowed to panic if `mtu` is less than the minimum IPv6 MTU, [`IPV6_MIN_MTU`].
///
/// [`IPV6_MIN_MTU`]: crate::ip::path_mtu::IPV6_MIN_MTU
/// [RFC 4861 section 6.3.4]: https://tools.ietf.org/html/rfc4861#section-6.3.4
fn set_mtu<D: EventDispatcher>(ctx: &mut StackState<D>, device_id: usize, mtu: u32);
/// Set default hop limit for IP packets sent from `device_id`.
///
/// # Panics
///
/// Panics if the new hop limit is `0`.
fn set_hop_limit<D: EventDispatcher>(
state: &mut StackState<D>,
device_id: usize,
hop_limit: NonZeroU8,
);
/// Can `device_id` route IP packets not destined for it?
///
/// If `is_router` returns `true`, we know that both the `device_id` and the netstack (`ctx`)
/// have forwarding enabled; if `is_router` returns false, either `device_id` or the netstack
/// (`ctx`) has forwarding disabled.
fn is_router<D: EventDispatcher>(ctx: &Context<D>, device_id: usize) -> bool;
}
/// Cleans up state associated with the device.
///
/// The contract is that after deinitialize is called, nothing else should be done
/// with the state.
pub(crate) fn deinitialize<D: EventDispatcher>(ctx: &mut Context<D>, device_id: usize) {
// Remove all timers associated with the device
// TODO(rheacock): this logic can be removed when NDP becomes contextified.
ctx.dispatcher_mut().cancel_timeouts_with(|timer_id| match timer_id {
TimerId(TimerIdInner::DeviceLayer(DeviceLayerTimerId::Ndp(inner_id))) => {
let timer_device_id = inner_id.get_device_id();
(timer_device_id.protocol == DeviceProtocol::Ethernet)
&& (timer_device_id.id == device_id)
}
_ => false,
});
// TODO(rheacock): Send any immediate packets, and potentially flag the state as uninitialized?
}
/// Per interface configurations for NDP.
#[derive(Debug, Clone)]
pub struct NdpConfigurations {
/// Value for NDP's DUP_ADDR_DETECT_TRANSMITS parameter.
///
/// As per [RFC 4862 section 5.1], the DUP_ADDR_DETECT_TRANSMITS
/// is configurable per interface.
///
/// A value of `None` means DAD will not be performed on the interface.
///
/// Default: [`DUP_ADDR_DETECT_TRANSMITS`].
///
/// [RFC 4862 section 5.1]: https://tools.ietf.org/html/rfc4862#section-5.1
dup_addr_detect_transmits: Option<NonZeroU8>,
/// Value for NDP's MAX_RTR_SOLICITATIONS parameter to configure
/// how many router solicitation messages to send on interface enable.
///
/// As per [RFC 4861 section 6.3.7], a host SHOULD transmit up to
/// `MAX_RTR_SOLICITATIONS` Router Solicitation messages. Given the
/// RFC does not require us to send `MAX_RTR_SOLICITATIONS` messages,
/// we allow a configurable value, up to `MAX_RTR_SOLICITATIONS`.
///
/// Default: [`MAX_RTR_SOLICITATIONS`].
max_router_solicitations: Option<NonZeroU8>,
/// Interface specific router configurations used by NDP.
///
/// See [`NdpRouterConfigurations`] for more details.
router_configurations: NdpRouterConfigurations,
}
impl Default for NdpConfigurations {
fn default() -> Self {
Self {
dup_addr_detect_transmits: NonZeroU8::new(DUP_ADDR_DETECT_TRANSMITS),
max_router_solicitations: NonZeroU8::new(MAX_RTR_SOLICITATIONS),
router_configurations: NdpRouterConfigurations::default(),
}
}
}
impl NdpConfigurations {
/// Get the value for NDP's DUP_ADDR_DETECT_TRANSMITS parameter.
pub fn get_dup_addr_detect_transmits(&self) -> Option<NonZeroU8> {
self.dup_addr_detect_transmits
}
/// Set the value for NDP's DUP_ADDR_DETECT_TRANSMITS parameter.
///
/// A value of `None` means DAD will not be performed on the interface.
pub fn set_dup_addr_detect_transmits(&mut self, v: Option<NonZeroU8>) {
self.dup_addr_detect_transmits = v;
}
/// Get the value for NDP's MAX_RTR_SOLICITATIONS parameter.
pub fn get_max_router_solicitations(&self) -> Option<NonZeroU8> {
self.max_router_solicitations
}
/// Set the value for NDP's MAX_RTR_SOLICITATIONS parameter.
///
/// A value of `None` means no router solicitations will be sent.
/// `MAX_RTR_SOLICITATIONS` is the maximum possible value; values
/// will be saturated at `MAX_RTR_SOLICITATIONS`.
pub fn set_max_router_solicitations(&mut self, mut v: Option<NonZeroU8>) {
if let Some(inner) = v {
if inner.get() > MAX_RTR_SOLICITATIONS {
v = NonZeroU8::new(MAX_RTR_SOLICITATIONS);
}
}
self.max_router_solicitations = v;
}
/// Get the router configurations used by NDP.
pub fn get_router_configurations(&self) -> &NdpRouterConfigurations {
&self.router_configurations
}
/// Set the router configurations used by NDP.
///
/// Note, unless the device is operating as a router (both netstack and the device has
/// forwarding enabled (See [`crate::device::can_forward`]), these values will not be
/// used. However, if a device or netstack configuration update occurs and the device
/// ends up operating as a router, these values will be used for Router Advertisements.
pub fn set_router_configurations(&mut self, v: NdpRouterConfigurations) {
self.router_configurations = v;
}
}
/// Interface specific router configurations used by NDP.
///
/// See [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
#[derive(Debug, Clone)]
pub struct NdpRouterConfigurations {
/// A flag indicating whether or not the router sends periodic Router Advertisements and
/// responds to Router Solicitations.
///
/// Default: false.
///
/// Note that AdvSendAdvertisements MUST be FALSE by default so that a node will not
/// accidentally start acting as a default router. Nodes must be explicitly configured
/// by system management to send Router Advertisements.
///
/// See AdvSendAdvertisements in RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
should_send_advertisements: bool,
/// The range of time allowed between sending unsolicited multicast Router Advertisements from
/// the interface, in seconds.
///
/// Maximum time MUST be no less than 4 seconds and no greater than 1800 seconds. Minimum time
/// MUST be no less than 3 seconds and no greater than 0.75 * maximum time.
///
/// Default: [200, 600].
///
/// See MaxRtrAdvInterval and MinRtrAdvInterval in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
router_advertisements_interval: RangeInclusive<u16>,
/// The value to be placed in the "Managed address configuration" flag field in the Router
/// Advertisement.
///
/// Default: false.
///
/// See AdvManagedFlag in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
advertised_managed_flag: bool,
/// The value to be placed in the "Other configuration" flag field in the Router Advertisement.
///
/// Default: false.
///
/// See AdvOtherConfigFlag in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
advertised_other_config_flag: bool,
/// The value to be placed in the MTU options sent by the router. A value of `None` indicates
/// that no MTU options are sent.
///
/// Default: None.
///
/// See AdvLinkMTU in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
advertised_link_mtu: Option<NonZeroU32>,
/// The value to be placed in the Reachable Time field in the Router Advertisement messages sent
/// by the router, in milliseconds. A value of 0 means unspecified (by this router).
///
/// The value MUST be no greater than 3600000 milliseconds (1 hour).
///
/// Default: 0.
///
/// See AdvReachableTime in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
advertised_reachable_time: u32,
/// The value to be placed in the Retrans Timer field in the Router Advertisement messages sent
/// by the router. The value 0 means unspecified (by this router).
///
/// Default: 0.
///
/// See AdvRetransTimer in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
advertised_retransmit_timer: u32,
/// The default value to be placed in the Cur Hop Limit field in the Router Advertisement
/// messages sent by the router. The value should be set to the current diameter of the
/// Internet. The value zero means unspecified (by this router).
///
/// Default: [`HOP_LIMIT_DEFAULT`].
///
/// See AdvCurHopLimit in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
advertised_current_hop_limit: u8,
/// The value to be placed in the Router Lifetime field of Router Advertisements sent from the
/// interface, in seconds. MUST be either zero or between MaxRtrAdvInterval and 9000 seconds.
/// A value of zero indicates that the router is not to be used as a default router.
///
/// Default: 1800.
///
/// See AdvDefaultLifetime in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
advertised_default_lifetime: Option<NonZeroU16>,
/// A list of prefixes to be placed in Prefix Information options in Router Advertisement
/// messages sent from the interface.
///
/// Note, the link-local prefix SHOULD NOT be included in the list of advertised prefixes.
///
/// See AdvPrefixList in in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
advertised_prefix_list: Vec<PrefixInformation>,
}
impl Default for NdpRouterConfigurations {
fn default() -> Self {
Self {
should_send_advertisements: SHOULD_SEND_ADVERTISEMENTS_DEFAULT,
router_advertisements_interval: ROUTER_ADVERTISEMENTS_INTERVAL_DEFAULT,
advertised_managed_flag: ADVERTISED_MANAGED_FLAG_DEFAULT,
advertised_other_config_flag: ADVERTISED_OTHER_CONFIG_FLAG,
advertised_link_mtu: ADVERTISED_LINK_MTU,
advertised_reachable_time: ADVERTISED_REACHABLE_TIME,
advertised_retransmit_timer: ADVERTISED_RETRANSMIT_TIMER,
advertised_current_hop_limit: ADVERTISED_CURRENT_HOP_LIMIT,
advertised_default_lifetime: ADVERTISED_DEFAULT_LIFETIME,
advertised_prefix_list: Vec::new(),
}
}
}
impl NdpRouterConfigurations {
/// Create a new Router Advertisement from the configurations in this `NdpRouterConfigurations`.
fn new_router_advertisement(&self) -> RouterAdvertisement {
RouterAdvertisement::new(
self.get_advertised_current_hop_limit(),
self.get_advertised_managed_flag(),
self.get_advertised_other_config_flag(),
self.get_advertised_default_lifetime().map_or(0, |x| x.get()),
self.get_advertised_reachable_time(),
self.get_advertised_retransmit_timer(),
)
}
/// Get enable/disable status of sending periodic Router Advertisements and responding to Router
/// Solicitations.
pub fn get_should_send_advertisements(&self) -> bool {
self.should_send_advertisements
}
/// Enable/disable sending periodic Router Advertisements and responding to Router
/// Solicitations.
///
/// See AdvSendAdvertisements in RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
pub fn set_should_send_advertisements(&mut self, v: bool) {
self.should_send_advertisements = v;
}
/// Get the range of time allowed between sending unsolicited multicast Router Advertisements
/// from the interface, in seconds.
pub fn get_router_advertisements_interval(&self) -> RangeInclusive<u16> {
self.router_advertisements_interval.clone()
}
/// Set the range of time allowed between sending unsolicited multicast Router Advertisements
/// from the interface, in seconds.
///
/// Maximum time MUST be no less than 4 seconds and no greater than 1800 seconds. Minimum time
/// MUST be no less than 3 seconds and no greater than 0.75 * maximum time.
///
/// If AdvDefaultLifetime is currently less than the new maximum time between sending
/// unsolicited Router Advertisements, AdvDefaultLifetime will be updated to the new maximum
/// time value.
///
/// See MaxRtrAdvInterval and MinRtrAdvInterval in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
pub fn set_router_advertisements_interval(&mut self, v: RangeInclusive<u16>) {
let start = *v.start();
let end = *v.end();
let start_upper_bound = (end * 3) / 4;
if end < 4 {
trace!("set_router_advertisements_interval: maximum time of {:?}s is less than 4s, ignoring", end);
return;
} else if end > 1800 {
trace!("set_router_advertisements_interval: maximum time of {:?}s is greater than 1800s, ignoring", end);
return;
} else if start < 3 {
trace!("set_router_advertisements_interval: minimum time of {:?}s is less than 3s, ignoring", start);
return;
} else if start > start_upper_bound {
trace!("set_router_advertisements_interval: minimum time of {:?}s is greater than 0.75 * maximum time of {:?}s ( = {:?}s, ignoring", start, end, start_upper_bound);
return;
}
if let Some(v) = self.advertised_default_lifetime {
if v.get() < end {
trace!("set_router_advertisements_interval: router_advertiements_interval of {:?} is less than new maximum router advertisements interval, setting to new max of {:?}", v.get(), end);
self.advertised_default_lifetime = NonZeroU16::new(end);
}
}
self.router_advertisements_interval = v;
}
/// Get the value to be placed in the "Managed address configuration" flag field in the Router
/// Advertisement.
pub fn get_advertised_managed_flag(&self) -> bool {
self.advertised_managed_flag
}
/// Set the value to be placed in the "Managed address configuration" flag field in the Router
/// Advertisement.
///
/// See AdvManagedFlag in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
pub fn set_advertised_managed_flag(&mut self, v: bool) {
self.advertised_managed_flag = v;
}
/// Get the value to be placed in the "Other configuration" flag field in the Router
/// Advertisement.
pub fn get_advertised_other_config_flag(&self) -> bool {
self.advertised_other_config_flag
}
/// Set the value to be placed in the "Other configuration" flag field in the Router
/// Advertisement.
///
/// See AdvOtherConfigFlag in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
pub fn set_advertised_other_config_flag(&mut self, v: bool) {
self.advertised_other_config_flag = v;
}
/// Get the value to be placed in the MTU options sent by the router. A value of `None`
/// indicates that no MTU options are sent.
pub fn get_advertised_link_mtu(&self) -> Option<NonZeroU32> {
self.advertised_link_mtu
}
/// Set the value to be placed in the MTU options sent by the router. A value of `None`
/// indicates that no MTU options are sent.
///
/// See AdvLinkMTU in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
pub fn set_advertised_link_mtu(&mut self, v: Option<NonZeroU32>) {
self.advertised_link_mtu = v;
}
/// Get the value to be placed in the Reachable Time field in the Router Advertisement messages
/// sent by the router, in milliseconds. A value of 0 means unspecified (by this router).
pub fn get_advertised_reachable_time(&self) -> u32 {
self.advertised_reachable_time
}
/// Set the value to be placed in the Reachable Time field in the Router Advertisement messages
/// sent by the router, in milliseconds. A value of 0 means unspecified (by this router).
///
/// The value MUST be no greater than 3600000 milliseconds (1 hour).
///
/// See AdvReachableTime in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
pub fn set_advertised_reachable_time(&mut self, v: u32) {
if v > 3600000 {
trace!("set_advertised_reachable_time: value of {:?} greater than 3600000ms (1hr), ignoring", v);
return;
}
self.advertised_reachable_time = v;
}
/// Get the value to be placed in the Retrans Timer field in the Router Advertisement messages
/// sent by the router. The value 0 means unspecified (by this router).
pub fn get_advertised_retransmit_timer(&self) -> u32 {
self.advertised_retransmit_timer
}
/// Set the value to be placed in the Retrans Timer field in the Router Advertisement messages
/// sent by the router. The value 0 means unspecified (by this router).
///
/// See AdvRetransTimer in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
pub fn set_advertised_retransmit_timer(&mut self, v: u32) {
self.advertised_retransmit_timer = v;
}
/// Get the default value to be placed in the Cur Hop Limit field in the Router Advertisement
/// messages sent by the router. The value should be set to the current diameter of the
/// Internet. The value zero means unspecified (by this router).
pub fn get_advertised_current_hop_limit(&self) -> u8 {
self.advertised_current_hop_limit
}
/// Set the default value to be placed in the Cur Hop Limit field in the Router Advertisement
/// messages sent by the router. The value should be set to the current diameter of the
/// Internet. The value zero means unspecified (by this router).
///
/// See AdvCurHopLimit in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
pub fn set_advertised_current_hop_limit(&mut self, v: u8) {
self.advertised_current_hop_limit = v;
}
/// Get the value to be placed in the Router Lifetime field of Router Advertisements sent from
/// the interface, in seconds. MUST be either zero or between MaxRtrAdvInterval and 9000
/// seconds. A value of zero indicates that the router is not to be used as a default router.
pub fn get_advertised_default_lifetime(&self) -> Option<NonZeroU16> {
self.advertised_default_lifetime
}
/// Set the value to be placed in the Router Lifetime field of Router Advertisements sent from
/// the interface, in seconds. MUST be either zero or between MaxRtrAdvInterval and 9000
/// seconds. A value of zero indicates that the router is not to be used as a default router.
///
/// See AdvDefaultLifetime in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
pub fn set_advertised_default_lifetime(&mut self, v: Option<NonZeroU16>) {
if let Some(v) = v {
let v = v.get();
let lower_bound = *self.router_advertisements_interval.end();
if v < lower_bound {
trace!("set_advertised_default_lifetime: value of {:?} less than MaxRtrAdvInterval of {:?}, ignoring", v, lower_bound);
return;
} else if v > 9000 {
trace!(
"set_advertised_default_lifetime: value of {:?} less than 9000s, ignoring",
v
);
return;
}
}
self.advertised_default_lifetime = v;
}
/// Get the list of prefixes to be placed in Prefix Information options in Router Advertisement
/// messages sent from the interface.
pub fn get_advertised_prefix_list(&self) -> &Vec<PrefixInformation> {
&self.advertised_prefix_list
}
/// Set the list of prefixes to be placed in Prefix Information options in Router Advertisement
/// messages sent from the interface.
///
/// Note, the link-local prefix SHOULD NOT be included in the list of advertised prefixes.
///
/// See AdvPrefixList in in [RFC 4861 section 6.2] for more information.
///
/// [RFC 4861 section 6.2]: https://tools.ietf.org/html/rfc4861#section-6.2
pub fn set_advertised_prefix_list(&mut self, v: Vec<PrefixInformation>) {
// TODO(ghanan): Check for duplicates and link local prefixes.
self.advertised_prefix_list = v;
}
}
/// The state associated with an instance of the Neighbor Discovery Protocol
/// (NDP).
///
/// Each device will contain an `NdpState` object to keep track of discovery
/// operations.
pub(crate) struct NdpState<D: NdpDevice> {
//
// NDP operation data structures.
//
/// List of neighbors.
neighbors: NeighborTable<D::LinkAddress>,
/// List of default routers, indexed by their link-local address.
default_routers: HashSet<LinkLocalAddr<Ipv6Addr>>,
/// List of on-link prefixes.
on_link_prefixes: HashSet<AddrSubnet<Ipv6Addr>>,
/// Number of Neighbor Solicitation messages left to send before we can
/// assume that an IPv6 address is not currently in use.
dad_transmits_remaining: HashMap<Ipv6Addr, u8>,
/// Number of remaining router solicitation messages to send.
router_solicitations_remaining: u8,
//
// Interace parameters learned from Router Advertisements.
//
// See RFC 4861 section 6.3.2.
//
/// A base value used for computing the random `reachable_time` value.
///
/// Default: `REACHABLE_TIME_DEFAULT`.
///
/// See BaseReachableTime in [RFC 4861 section 6.3.2] for more details.
///
/// [RFC 4861 section 6.3.2]: https://tools.ietf.org/html/rfc4861#section-6.3.2
base_reachable_time: Duration,
/// The time a neighbor is considered reachable after receiving a
/// reachability confirmation.
///
/// This value should be uniformly distributed between MIN_RANDOM_FACTOR (0.5)
/// and MAX_RANDOM_FACTOR (1.5) times `base_reachable_time` milliseconds. A new
/// random should be calculated when `base_reachable_time` changes (due to Router
/// Advertisements) or at least every few hours even if no Router Advertisements
/// are received.
///
/// See ReachableTime in [RFC 4861 section 6.3.2] for more details.
///
/// [RFC 4861 section 6.3.2]: https://tools.ietf.org/html/rfc4861#section-6.3.2
reachable_time: Duration,
/// The time between retransmissions of Neighbor Solicitation messages to
/// a neighbor when resolving the address or when probing the reachability
/// of a neighbor.
///
/// Default: `RETRANS_TIMER_DEFAULT`.
///
/// See RetransTimer in [RFC 4861 section 6.3.2] for more details.
///
/// [RFC 4861 section 6.3.2]: https://tools.ietf.org/html/rfc4861#section-6.3.2
retrans_timer: Duration,
//
// NDP configurations.
//
/// NDP Configurations.
configs: NdpConfigurations,
}
impl<D: NdpDevice> NdpState<D> {
pub(crate) fn new(configs: NdpConfigurations) -> Self {
let mut ret = Self {
neighbors: NeighborTable::default(),
default_routers: HashSet::new(),
dad_transmits_remaining: HashMap::new(),
on_link_prefixes: HashSet::new(),
router_solicitations_remaining: 0,
base_reachable_time: REACHABLE_TIME_DEFAULT,
reachable_time: REACHABLE_TIME_DEFAULT,
retrans_timer: RETRANS_TIMER_DEFAULT,
configs,
};
// Calculate an actually random `reachable_time` value instead of using
// a constant.
ret.recalculate_reachable_time();
ret
}
//
// NDP operation data structure helpers.
//
/// Do we know about the default router identified by `ip`?
fn has_default_router(&mut self, ip: &LinkLocalAddr<Ipv6Addr>) -> bool {
self.default_routers.contains(&ip)
}
/// Adds a new router to our list of default routers.
fn add_default_router(&mut self, ip: LinkLocalAddr<Ipv6Addr>) {
// Router must not already exist if we are adding it.
assert!(self.default_routers.insert(ip));
}
/// Removes a router from our list of default routers.
fn remove_default_router(&mut self, ip: &LinkLocalAddr<Ipv6Addr>) {
// Router must exist if we are removing it.
assert!(self.default_routers.remove(&ip));
}
/// Handle the invalidation of a default router.
///
/// # Panics
///
/// Panics if the router has not yet been discovered.
fn invalidate_default_router(&mut self, ip: LinkLocalAddr<Ipv6Addr>) {
// As per RFC 4861 section 6.3.5:
// Whenever the Lifetime of an entry in the Default Router List expires,
// that entry is discarded. When removing a router from the Default
// Router list, the node MUST update the Destination Cache in such a way
// that all entries using the router perform next-hop determination
// again rather than continue sending traffic to the (deleted) router.
self.remove_default_router(&ip);
}
/// Do we already know about this prefix?
fn has_prefix(&self, addr_sub: &AddrSubnet<Ipv6Addr>) -> bool {
self.on_link_prefixes.contains(addr_sub)
}
/// Adds a new prefix to our list of on-link prefixes.
///
/// # Panics
///
/// Panics if the prefix already exists in our list of on-link
/// prefixes.
fn add_prefix(&mut self, addr_sub: AddrSubnet<Ipv6Addr>) {
assert!(self.on_link_prefixes.insert(addr_sub));
}
/// Removes a prefix from our list of on-link prefixes.
///
/// # Panics
///
/// Panics if the prefix doesn't exist in our list of on-link
/// prefixes.
fn remove_prefix(&mut self, addr_sub: &AddrSubnet<Ipv6Addr>) {
assert!(self.on_link_prefixes.remove(addr_sub));
}
/// Handle the invalidation of a prefix.
///
/// # Panics
///
/// Panics if the prefix doesn't exist in our list of on-link
/// prefixes.
fn invalidate_prefix(&mut self, addr_sub: AddrSubnet<Ipv6Addr>) {
// As per RFC 4861 section 6.3.5:
// Whenever the invalidation timer expires for a Prefix List entry, that
// entry is discarded. No existing Destination Cache entries need be
// updated, however. Should a reachability problem arise with an
// existing Neighbor Cache entry, Neighbor Unreachability Detection will
// perform any needed recovery.
self.remove_prefix(&addr_sub);
}
//
// Interace parameters learned from Router Advertisements.
//
/// Set the base value used for computing the random `reachable_time` value.
///
/// This method will also recalculate the `reachable_time` if the new base value
/// is different from the current value. If the new base value is the same as
/// the current value, `set_base_reachable_time` does nothing.
pub(crate) fn set_base_reachable_time(&mut self, v: Duration) {
assert_ne!(Duration::new(0, 0), v);
if self.base_reachable_time == v {
return;
}
self.base_reachable_time = v;
self.recalculate_reachable_time();
}
/// Recalculate `reachable_time`.
///
/// The new `reachable_time` will be a random value between a factor of
/// MIN_RANDOM_FACTOR and MAX_RANDOM_FACTOR, as per [RFC 4861 section 6.3.2].
///
/// [RFC 4861 section 6.3.2]: https://tools.ietf.org/html/rfc4861#section-6.3.2
pub(crate) fn recalculate_reachable_time(&mut self) -> Duration {
let base = self.base_reachable_time;
let half = base / 2;
let reachable_time = half + thread_rng().gen_range(Duration::new(0, 0), base);
// Random value must between a factor of MIN_RANDOM_FACTOR (0.5) and
// MAX_RANDOM_FACTOR (1.5), as per RFC 4861 section 6.3.2.
assert!((reachable_time >= half) && (reachable_time <= (base + half)));
self.reachable_time = reachable_time;
reachable_time
}
/// Set the time between retransmissions of Neighbor Solicitation messages to
/// a neighbor when resolving the address or when probing the reachability of
/// a neighbor.
pub(crate) fn set_retrans_timer(&mut self, v: Duration) {
assert_ne!(Duration::new(0, 0), v);
self.retrans_timer = v;
}
//
// NDP Configurations.
//
/// Set the number of Neighbor Solicitation messages to send when performing DAD.
pub(crate) fn set_dad_transmits(&mut self, v: Option<NonZeroU8>) {
self.configs.set_dup_addr_detect_transmits(v);
}
}
/// The identifier for timer events in NDP operations.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub(crate) struct NdpTimerId {
device_id: DeviceId,
inner: InnerNdpTimerId,
}
/// The types of NDP timers.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub(crate) enum InnerNdpTimerId {
/// This is used to retry sending Neighbor Discovery Protocol requests.
LinkAddressResolution { neighbor_addr: Ipv6Addr },
/// This is used to resend Duplicate Address Detection Neighbor Solicitation
/// messages if `DUP_ADDR_DETECTION_TRANSMITS` is greater than one.
DadNsTransmit { addr: Ipv6Addr },
/// Timer to send Router Solicitation messages.
RouterSolicitationTransmit,
/// Timer to invalidate a router.
/// `ip` is the identifying IP of the router.
RouterInvalidation { ip: LinkLocalAddr<Ipv6Addr> },
/// Timer to invalidate a prefix.
PrefixInvalidation { addr_subnet: AddrSubnet<Ipv6Addr> },
// TODO: The RFC suggests that we SHOULD make a random delay to
// join the solicitation group. When we support MLD, we probably
// want one for that.
}
impl NdpTimerId {
/// Creates a new `NdpTimerId` wrapped inside a `TimerId` with the provided
/// `device_id` and `neighbor_addr`.
pub(crate) fn new_link_address_resolution_timer_id<ND: NdpDevice>(
device_id: usize,
neighbor_addr: Ipv6Addr,
) -> TimerId {
NdpTimerId {
device_id: ND::get_device_id(device_id),
inner: InnerNdpTimerId::LinkAddressResolution { neighbor_addr },
}
.into()
}
pub(crate) fn new_dad_ns_transmission_timer_id<ND: NdpDevice>(
device_id: usize,
tentative_addr: Ipv6Addr,
) -> TimerId {
NdpTimerId {
device_id: ND::get_device_id(device_id),
inner: InnerNdpTimerId::DadNsTransmit { addr: tentative_addr },
}
.into()
}
pub(crate) fn new_router_solicitation_timer_id<ND: NdpDevice>(device_id: usize) -> TimerId {
NdpTimerId {
device_id: ND::get_device_id(device_id),
inner: InnerNdpTimerId::RouterSolicitationTransmit,
}
.into()
}
pub(crate) fn new_router_invalidation_timer_id<ND: NdpDevice>(
device_id: usize,
ip: LinkLocalAddr<Ipv6Addr>,
) -> TimerId {
NdpTimerId {
device_id: ND::get_device_id(device_id),
inner: InnerNdpTimerId::RouterInvalidation { ip },
}
.into()
}
pub(crate) fn new_prefix_invalidation_timer_id<ND: NdpDevice>(
device_id: usize,
addr_subnet: AddrSubnet<Ipv6Addr>,
) -> TimerId {
NdpTimerId {
device_id: ND::get_device_id(device_id),
inner: InnerNdpTimerId::PrefixInvalidation { addr_subnet },
}
.into()
}
pub(crate) fn get_device_id(&self) -> DeviceId {
self.device_id
}
}
impl From<NdpTimerId> for TimerId {
fn from(v: NdpTimerId) -> Self {
TimerId(TimerIdInner::DeviceLayer(DeviceLayerTimerId::Ndp(v)))
}
}
/// Handles a timeout event.
///
/// This currently only supports Ethernet NDP, since we know that that is
/// the only case that the netstack currently handles. In the future, this may
/// be extended to support other hardware types.
pub(crate) fn handle_timeout<D: EventDispatcher>(ctx: &mut Context<D>, id: NdpTimerId) {
match id.device_id.protocol() {
DeviceProtocol::Ethernet => {
handle_timeout_inner::<_, EthernetNdpDevice>(ctx, id.device_id.id(), id.inner)
}
}
}
fn handle_timeout_inner<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
inner_id: InnerNdpTimerId,
) {
match inner_id {
InnerNdpTimerId::LinkAddressResolution { neighbor_addr } => {
let ndp_state = ND::get_ndp_state_mut(ctx.state_mut(), device_id);
if let Some(NeighborState {
state: NeighborEntryState::Incomplete { transmit_counter },
..
}) = ndp_state.neighbors.get_neighbor_state_mut(&neighbor_addr)
{
if *transmit_counter < MAX_MULTICAST_SOLICIT {
let retrans_timer = ndp_state.retrans_timer;
// Increase the transmit counter and send the solicitation again
*transmit_counter += 1;
send_neighbor_solicitation::<_, ND>(ctx, device_id, neighbor_addr);
ctx.dispatcher.schedule_timeout(
retrans_timer,
NdpTimerId::new_link_address_resolution_timer_id::<ND>(
device_id,
neighbor_addr,
),
);
} else {
// To make sure we don't get stuck in this neighbor unreachable
// state forever, remove the neighbor from the database:
ndp_state.neighbors.delete_neighbor_state(&neighbor_addr);
increment_counter!(ctx, "ndp::neighbor_solicitation_timeout");
ND::address_resolution_failed(ctx, device_id, &neighbor_addr);
}
} else {
unreachable!("handle_timeout_inner: timer for neighbor {:?} address resolution should not exist if no entry exists", neighbor_addr);
}
}
InnerNdpTimerId::DadNsTransmit { addr } => {
// Get device NDP state.
//
// We know this call to unwrap will not fail because we will only reach here
// if DAD has been started for some device - address pair. When we start DAD,
// we setup the `NdpState` so we should have a valid entry.
let ndp_state = ND::get_ndp_state_mut(ctx.state_mut(), device_id);
let remaining = *ndp_state.dad_transmits_remaining.get(&addr).unwrap();
// We have finished.
if remaining == 0 {
// We know `unwrap` will not fail because we just succesfully
// called `get` then `unwrap` earlier.
ndp_state.dad_transmits_remaining.remove(&addr).unwrap();
// `unique_address_determined` may panic if we attempt to resolve an `addr`
// that is not tentative on the device with id `device_id`. However, we
// can only reach here if `addr` was tentative on `device_id` and we are
// performing DAD so we know `unique_address_determined` will not panic.
ND::unique_address_determined(ctx.state_mut(), device_id, addr);
} else {
do_duplicate_address_detection::<D, ND>(ctx, device_id, addr, remaining);
}
}
InnerNdpTimerId::RouterSolicitationTransmit => {
do_router_solicitation::<_, ND>(ctx, device_id)
}
InnerNdpTimerId::RouterInvalidation { ip } => {
// Invalidate the router.
//
// The call to `invalidate_default_router` may panic if `ip` does not reference a
// known default router, but we will only reach here if we received an NDP Router
// Advertisement from a router with a valid lifetime > 0, at which point this timeout.
// would have been set. Givem this, we know that `invalidate_default_router` will not
// panic.
ND::get_ndp_state_mut(ctx.state_mut(), device_id).invalidate_default_router(ip)
}
InnerNdpTimerId::PrefixInvalidation { addr_subnet } => {
// Invalidate the prefix.
//
// The call to `invalidate_prefix` may panic if `addr_subnet` is not in the
// list of on-link prefixes. However, we will only reach here if we received
// an NDP Router Advertisement with the prefix option with the on-link flag
// set. Given this we know that `addr_subnet` must exist if this timer was
// fired so `invalidate_prefix` will not panic.
ND::get_ndp_state_mut(ctx.state_mut(), device_id).invalidate_prefix(addr_subnet);
}
}
}
/// Updates the NDP Configurations for a `device_id`.
///
/// Note, some values may not take effect immediately, and may only take effect the next time they
/// are used. These scenarios documented below:
///
/// - Updates to [`NdpConfiguration::dup_addr_detect_transmits`] will only take effect the next
/// time Duplicate Address Detection (DAD) is done. Any DAD processes that have already started
/// will continue using the old value.
///
/// - Updates to [`NdpConfiguration::max_router_solicitations`] will only take effect the next
/// time routers are explicitly solicited. Current router solicitation will continue using the
/// old value.
pub(crate) fn set_ndp_configurations<D: EventDispatcher, ND: NdpDevice + 'static>(
ctx: &mut Context<D>,
device_id: usize,
configs: NdpConfigurations,
) {
ND::get_ndp_state_mut(ctx.state_mut(), device_id).configs = configs;
}
/// Gets the NDP Configurations for a `device_id`.
pub(crate) fn get_ndp_configurations<D: EventDispatcher, ND: NdpDevice + 'static>(
ctx: &Context<D>,
device_id: usize,
) -> &NdpConfigurations {
&ND::get_ndp_state(ctx.state(), device_id).configs
}
/// Look up the link layer address.
///
/// Begins the address resolution process if the link layer address
/// for `lookup_addr` is not already known.
pub(crate) fn lookup<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
lookup_addr: Ipv6Addr,
) -> Option<ND::LinkAddress> {
trace!("ndp::lookup: {:?}", lookup_addr);
// An IPv6 multicast address should always be sent on a broadcast
// link address.
if lookup_addr.is_multicast() {
// TODO(brunodalbo): this is currently out of spec, we need to form a
// MAC multicast from the lookup address conforming to RFC 2464.
return Some(ND::BROADCAST);
}
// TODO(brunodalbo): Figure out what to do if a frame can't be sent
let ndpstate = ND::get_ndp_state_mut(ctx.state_mut(), device_id);
let result = ndpstate.neighbors.get_neighbor_state(&lookup_addr);
match result {
// TODO(ghanan): As long as have ever received a link layer address for
// `lookup_addr` from any NDP packet with the source link
// layer option, we would have stored that address. Here
// we simply return that address without checking the
// actual state of the neighbor entry. We should make sure
// that the entry is not Stale before returning the address.
// If it is stale, we should make sure it is reachable first.
// See RFC 4861 section 7.3.2 for more information.
Some(NeighborState { link_address: Some(address), .. }) => Some(*address),
// We do not know about the neighbor and need to start address resolution.
None => {
trace!("ndp::lookup: starting address resolution process for {:?}", lookup_addr);
let retrans_timer = ndpstate.retrans_timer;
// If we're not already waiting for a neighbor solicitation
// response, mark it as Incomplete and send a neighbor solicitation,
// also setting the transmission count to 1.
ndpstate.neighbors.add_incomplete_neighbor_state(lookup_addr);
send_neighbor_solicitation::<_, ND>(ctx, device_id, lookup_addr);
// Also schedule a timer to retransmit in case we don't get
// neighbor advertisements back.
ctx.dispatcher.schedule_timeout(
retrans_timer,
NdpTimerId::new_link_address_resolution_timer_id::<ND>(device_id, lookup_addr),
);
// Returning `None` as we do not have a link-layer address
// to give yet.
None
}
// Address resolution is currently in progress.
Some(NeighborState { state: NeighborEntryState::Incomplete { .. }, .. }) => {
trace!(
"ndp::lookup: still waiting for address resolution to complete for {:?}",
lookup_addr
);
None
}
// TODO(ghanan): Handle case where a neighbor entry exists for a `link_addr`
// but no link address as been discovered.
_ => unimplemented!("A neighbor entry exists but no link address is discovered"),
}
}
/// Insert a neighbor to the known neighbors table.
///
/// This method only gets called when testing to force a neighbor
/// so link address lookups completes immediately without doing
/// address resolution.
#[cfg(test)]
pub(crate) fn insert_neighbor<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
net: Ipv6Addr,
hw: ND::LinkAddress,
) {
// Neighbor `net` should be marked as reachable.
ND::get_ndp_state_mut(ctx.state_mut(), device_id).neighbors.set_link_address(net, hw, true)
}
/// `NeighborState` keeps all state that NDP may want to keep about neighbors,
/// like link address resolution and reachability information, for example.
struct NeighborState<H> {
is_router: bool,
state: NeighborEntryState,
link_address: Option<H>,
}
impl<H> NeighborState<H> {
fn new() -> Self {
Self {
is_router: false,
state: NeighborEntryState::Incomplete { transmit_counter: 0 },
link_address: None,
}
}
}
/// The various states a Neighbor cache entry can be in.
///
/// See [RFC 4861 section 7.3.2].
///
/// [RFC 4861 section 7.3.2]: https://tools.ietf.org/html/rfc4861#section-7.3.2
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum NeighborEntryState {
/// Address resolution is being performed on the entry.
/// Specifically, a Neighbor Solicitation has been sent to
/// the solicited-node multicast address of the target,
/// but the corresponding Neighbor Advertisement has not
/// yet been received.
///
/// `transmit_counter` is the count of Neighbor Solicitation
/// messages sent as part of the Address resolution process.
Incomplete { transmit_counter: u8 },
/// Positive confirmation was received within the last
/// ReachableTime milliseconds that the forward path to
/// the neighbor was functioning properly. While
/// `Reachable`, no special action takes place as packets
/// are sent.
Reachable,
/// More than ReachableTime milliseconds have elapsed
/// since the last positive confirmation was received that
/// the forward path was functioning properly. While
/// stale, no action takes place until a packet is sent.
///
/// The `Stale` state is entered upon receiving an
/// unsolicited Neighbor Discovery message that updates
/// the cached link-layer address. Receipt of such a
/// message does not confirm reachability, and entering
/// the `Stale` state ensures reachability is verified
/// quickly if the entry is actually being used. However,
/// reachability is not actually verified until the entry
/// is actually used.
Stale,
/// More than ReachableTime milliseconds have elapsed
/// since the last positive confirmation was received that
/// the forward path was functioning properly, and a
/// packet was sent within the last DELAY_FIRST_PROBE_TIME
/// seconds. If no reachability confirmation is received
/// within DELAY_FIRST_PROBE_TIME seconds of entering the
/// DELAY state, send a Neighbor Solicitation and change
/// the state to PROBE.
///
/// The DELAY state is an optimization that gives upper-
/// layer protocols additional time to provide
/// reachability confirmation in those cases where
/// ReachableTime milliseconds have passed since the last
/// confirmation due to lack of recent traffic. Without
/// this optimization, the opening of a TCP connection
/// after a traffic lull would initiate probes even though
/// the subsequent three-way handshake would provide a
/// reachability confirmation almost immediately.
Delay,
/// A reachability confirmation is actively sought by
/// retransmitting Neighbor Solicitations every
/// RetransTimer milliseconds until a reachability
/// confirmation is received.
Probe,
}
impl NeighborEntryState {
fn is_incomplete(self) -> bool {
if let NeighborEntryState::Incomplete { .. } = self {
true
} else {
false
}
}
}
struct NeighborTable<H> {
table: HashMap<Ipv6Addr, NeighborState<H>>,
}
impl<H: PartialEq + Debug> NeighborTable<H> {
/// Sets the link address for a neighbor.
///
/// If `is_reachable` is `true`, the state of the neighbor will be
/// set to `NeighborEntryState::Reachable`. Otherwise, it will be
/// set to `NeighborEntryState::Stale` if the address was updated.
/// A `false` value for `is_reachable` does not mean that the
/// neighbor is unreachable, it just means that we do not know if it
/// is reachable.
fn set_link_address(&mut self, neighbor: Ipv6Addr, address: H, is_reachable: bool) {
let address = Some(address);
let neighbor_state = self.table.entry(neighbor).or_insert_with(NeighborState::new);
trace!("set_link_address: setting link address for neighbor {:?} to address", address);
if is_reachable {
trace!("set_link_address: reachability is known, so setting state for neighbor {:?} to Reachable", neighbor);
neighbor_state.state = NeighborEntryState::Reachable;
} else if neighbor_state.link_address != address {
trace!("set_link_address: new link addr different from old and reachability is unknown, so setting state for neighbor {:?} to Stale", neighbor);
neighbor_state.state = NeighborEntryState::Stale;
}
neighbor_state.link_address = address;
}
}
impl<H> NeighborTable<H> {
/// Create a new incomplete state of a neighbor, setting the transmit counter to 1.
fn add_incomplete_neighbor_state(&mut self, neighbor: Ipv6Addr) {
let mut state = NeighborState::new();
state.state = NeighborEntryState::Incomplete { transmit_counter: 1 };
self.table.insert(neighbor, state);
}
/// Get the neighbor's state, if it exists.
fn get_neighbor_state(&self, neighbor: &Ipv6Addr) -> Option<&NeighborState<H>> {
self.table.get(neighbor)
}
/// Get a the neighbor's mutable state, if it exists.
fn get_neighbor_state_mut(&mut self, neighbor: &Ipv6Addr) -> Option<&mut NeighborState<H>> {
self.table.get_mut(neighbor)
}
/// Delete the neighbor's state, if it exists.
fn delete_neighbor_state(&mut self, neighbor: &Ipv6Addr) {
self.table.remove(neighbor);
}
}
impl<H> Default for NeighborTable<H> {
fn default() -> Self {
NeighborTable { table: HashMap::default() }
}
}
/// Start soliciting routers.
///
/// Does nothing if a device's MAX_RTR_SOLICITATIONS parameter is `0`.
///
/// # Panics
///
/// Panics if we attempt to start router solicitation as a router, or if
/// router solicitation is already in progress.
pub(crate) fn start_soliciting_routers<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
) {
// MUST NOT be a router.
assert!(!ND::is_router(ctx, device_id));
let ndp_state = ND::get_ndp_state_mut(ctx.state_mut(), device_id);
// MUST NOT already be performing router solicitation.
assert_eq!(ndp_state.router_solicitations_remaining, 0);
trace!(
"ndp::start_soliciting_routers: start soliciting routers for device: {:?}",
ND::get_device_id(device_id)
);
if let Some(v) = ndp_state.configs.max_router_solicitations {
ndp_state.router_solicitations_remaining = v.get();
// As per RFC 4861 section 6.3.7, delay the first transmission for a random amount of time
// between 0 and `MAX_RTR_SOLICITATION_DELAY` to alleviate congestion when many hosts start
// up on a link at the same time.
let delay =
ctx.dispatcher_mut().rng().gen_range(Duration::new(0, 0), MAX_RTR_SOLICITATION_DELAY);
// MUST NOT already be performing router solicitation.
assert!(ctx
.dispatcher_mut()
.schedule_timeout(delay, NdpTimerId::new_router_solicitation_timer_id::<ND>(device_id))
.is_none());
}
}
/// Stop soliciting routers.
///
/// Does nothing if the device is not soliciting routers.
///
/// # Panics
///
/// Panics if we attempt to stop router solicitations on a router (this should never happen
/// as routers should not be soliciting other routers).
pub(crate) fn stop_soliciting_routers<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
) {
trace!(
"ndp::stop_soliciting_routers: stop soliciting routers for device: {:?}",
ND::get_device_id(device_id)
);
assert!(!ND::is_router(ctx, device_id));
ctx.dispatcher_mut()
.cancel_timeout(NdpTimerId::new_router_solicitation_timer_id::<ND>(device_id));
// No more router solicitations remaining since we are cancelling.
ND::get_ndp_state_mut(ctx.state_mut(), device_id).router_solicitations_remaining = 0;
}
/// Solicit routers once amd schedule next message.
///
/// # Panics
///
/// Panics if we attempt to do router solicitation as a router or if
/// we are already done soliciting routers.
fn do_router_solicitation<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
) {
assert!(!ND::is_router(ctx, device_id));
let ndp_state = ND::get_ndp_state_mut(ctx.state_mut(), device_id);
let remaining = &mut ndp_state.router_solicitations_remaining;
assert!(*remaining > 0);
*remaining -= 1;
let remaining = *remaining;
let src_ip = ND::get_ipv6_addr(ctx.state(), device_id)
.map(Tentative::try_into_permanent)
.unwrap_or(None);
trace!(
"do_router_solicitation: soliciting routers for device {:?} using src_ip {:?}",
device_id,
src_ip
);
send_router_solicitation::<_, ND>(ctx, device_id, src_ip);
if remaining == 0 {
trace!(
"do_router_solicitation: done sending router solicitation messages for device {:?}",
device_id
);
return;
} else {
// TODO(ghanan): Make the interval between messages configurable.
ctx.dispatcher_mut().schedule_timeout(
RTR_SOLICITATION_INTERVAL,
NdpTimerId::new_router_solicitation_timer_id::<ND>(device_id),
);
}
}
/// Send a router solicitation packet.
///
/// # Panics
///
/// Panics if we attempt to send a router solicitation as a router.
fn send_router_solicitation<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
src_ip: Option<Ipv6Addr>,
) {
assert!(!ND::is_router(ctx, device_id));
let src_ip = src_ip.unwrap_or(Ipv6::UNSPECIFIED_ADDRESS);
trace!("send_router_solicitation: sending router solicitation from {:?}", src_ip);
if src_ip.is_unspecified() {
// Must not include the source link layer address if the source address
// is unspecified as per RFC 4861 section 4.1.
send_ndp_packet::<_, ND, &[u8], _>(
ctx,
device_id,
src_ip,
Ipv6::ALL_ROUTERS_LINK_LOCAL_ADDRESS,
ND::BROADCAST,
RouterSolicitation::default(),
&[],
);
} else {
let src_ll = ND::get_link_layer_addr(ctx.state(), device_id);
send_ndp_packet::<_, ND, &[u8], _>(
ctx,
device_id,
src_ip,
Ipv6::ALL_ROUTERS_LINK_LOCAL_ADDRESS,
ND::BROADCAST,
RouterSolicitation::default(),
&[NdpOption::SourceLinkLayerAddress(src_ll.bytes())],
);
}
}
/// Begin the Duplicate Address Detection process.
///
/// If the device is configured to not do DAD, then this method will
/// immediately assign `tentative_addr` to the device.
pub(crate) fn start_duplicate_address_detection<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
tentative_addr: Ipv6Addr,
) {
let transmits =
ND::get_ndp_state_mut(ctx.state_mut(), device_id).configs.dup_addr_detect_transmits;
if let Some(transmits) = transmits {
trace!("ndp::start_duplicate_address_detection: starting duplicate address detection for address {:?} on device {:?}", tentative_addr, ND::get_device_id(device_id));
do_duplicate_address_detection::<D, ND>(ctx, device_id, tentative_addr, transmits.get());
} else {
// DAD is turned off since the interface's DUP_ADDR_DETECT_TRANSMIT parameter
// is `None`.
trace!("ndp::start_duplicate_address_detection: assigning address {:?} on device {:?} immediately because duplicate address detection is disabled", tentative_addr, ND::get_device_id(device_id));
ND::unique_address_determined(ctx.state_mut(), device_id, tentative_addr);
}
}
/// Cancels the Duplicate Address Detection process.
///
/// Note, the address will now be in a tentative state forever unless the
/// caller assigns a new address to the device (DAD will restart), explicitly
/// restarts DAD, or the device receives a Neighbor Solicitation or Neighbor
/// Advertisement message (the address will be found to be a duplicate and
/// unassigned from the device).
///
/// # Panics
///
/// Panics if we are not currently performing DAD for `tentative_addr` on `device_id`.
pub(crate) fn cancel_duplicate_address_detection<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
tentative_addr: Ipv6Addr,
) {
trace!("ndp::cancel_duplicate_address_detection: cancelling duplicate address detection for address {:?} on device {:?}", tentative_addr, ND::get_device_id(device_id));
ctx.dispatcher_mut().cancel_timeout(NdpTimerId::new_dad_ns_transmission_timer_id::<ND>(
device_id,
tentative_addr,
));
// `unwrap` may panic if we have no entry in `dad_transmits_remaining` for
// `tentative_addr` which means that we are not performing DAD on
// `tentative_add`. This case is documented as a panic condition.
ND::get_ndp_state_mut(ctx.state_mut(), device_id)
.dad_transmits_remaining
.remove(&tentative_addr)
.unwrap();
}
fn do_duplicate_address_detection<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
tentative_addr: Ipv6Addr,
remaining: u8,
) {
trace!("do_duplicate_address_detection: tentative_addr {:?}", tentative_addr);
assert!(remaining > 0);
let src_ll = ND::get_link_layer_addr(ctx.state(), device_id);
send_ndp_packet::<_, ND, &[u8], _>(
ctx,
device_id,
Ipv6::UNSPECIFIED_ADDRESS,
tentative_addr.to_solicited_node_address().get(),
ND::BROADCAST,
NeighborSolicitation::new(tentative_addr),
&[NdpOption::SourceLinkLayerAddress(src_ll.bytes())],
);
let ndp_state = ND::get_ndp_state_mut(ctx.state_mut(), device_id);
ndp_state.dad_transmits_remaining.insert(tentative_addr, remaining - 1);
// Uses same RETRANS_TIMER definition per RFC 4862 section-5.1
let retrans_timer = ndp_state.retrans_timer;
ctx.dispatcher_mut().schedule_timeout(
retrans_timer,
NdpTimerId::new_dad_ns_transmission_timer_id::<ND>(device_id, tentative_addr),
);
}
fn send_neighbor_solicitation<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
lookup_addr: Ipv6Addr,
) {
trace!("send_neighbor_solicitation: lookip_addr {:?}", lookup_addr);
// TODO(brunodalbo) when we send neighbor solicitations, we SHOULD set
// the source IP to the same IP as the packet that triggered the
// solicitation, so that when we hit the neighbor they'll have us in their
// cache, reducing overall burden on the network.
if let Some(tentative_device_addr) = ND::get_ipv6_addr(ctx.state(), device_id) {
let device_addr = tentative_device_addr.into_inner();
debug_assert!(device_addr.is_valid_unicast());
let src_ll = ND::get_link_layer_addr(ctx.state(), device_id);
let dst_ip = lookup_addr.to_solicited_node_address().get();
send_ndp_packet::<_, ND, &[u8], _>(
ctx,
device_id,
device_addr,
dst_ip,
ND::BROADCAST,
NeighborSolicitation::new(lookup_addr),
&[NdpOption::SourceLinkLayerAddress(src_ll.bytes())],
);
} else {
// Nothing can be done if we don't have any ipv6 addresses to send
// packets out to.
debug!("Not sending NDP request, since we don't know our IPv6 address");
}
}
fn send_neighbor_advertisement<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
solicited: bool,
device_addr: Ipv6Addr,
dst_ip: Ipv6Addr,
) {
debug!("send_neighbor_advertisement from {:?} to {:?}", device_addr, dst_ip);
debug_assert!(device_addr.is_valid_unicast());
// We currently only allow the destination address to be:
// 1) a unicast address.
// 2) a multicast destination but the message should be a unsolicited neighbor
// advertisement.
// NOTE: this assertion may need change if more messages are to be allowed in the future.
debug_assert!(dst_ip.is_valid_unicast() || (!solicited && dst_ip.is_multicast()));
// TODO(brunodalbo) if we're a router, flags must also set FLAG_ROUTER.
let flags = if solicited { NeighborAdvertisement::FLAG_SOLICITED } else { 0x00 };
// We must call into the higher level send_ipv6_frame function because it is
// not guaranteed that we have actually saved the link layer address of the
// destination ip. Typically, the solicitation request will carry that
// information, but it is not necessary. So it is perfectly valid that
// trying to send this advertisement will end up triggering a neighbor
// solicitation to be sent.
let src_ll = ND::get_link_layer_addr(ctx.state(), device_id);
let options = [NdpOption::TargetLinkLayerAddress(src_ll.bytes())];
let body = ndp::OptionsSerializer::<_>::new(options.iter())
.into_serializer()
.encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new(
device_addr,
dst_ip,
IcmpUnusedCode,
NeighborAdvertisement::new(flags, device_addr),
))
.encapsulate(Ipv6PacketBuilder::new(device_addr, dst_ip, 1, IpProto::Icmpv6));
ND::send_ipv6_frame(ctx, device_id, dst_ip, body)
.unwrap_or_else(|_| debug!("Failed to send neighbor advertisement: MTU exceeded"));
}
/// Send a router advertisement message from `device_id` to `dst_ip`.
///
/// `dst_ip` is typically the source address of an invoking Router Solicitation, or the all-nodes
/// multicast address.
///
/// `send_router_advertisement` does nothing if `device_id` is configured to not send Router
/// Advertisements.
///
/// # Panics
///
/// Panics if `device_id` does not have an assigned (non-tentative) link-local address, if it is
/// not operating as a router, or if it is not configured to send router advertisements.
fn send_router_advertisement<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
dst_ip: Ipv6Addr,
) {
assert!(ND::is_router(ctx, device_id));
// If we are attempting to send a router advertisement, we need to have a valid
// link-local address, as per RFC 4861 section 4.2. The call to either `unwrap` may
// panic if `device_id` does not have an assigned (non-tentative) link-local address,
// but this is documented for this function.
let src_ip =
ND::get_link_local_addr(ctx.state(), device_id).unwrap().try_into_permanent().unwrap();
let router_configurations =
ND::get_ndp_state(ctx.state(), device_id).configs.get_router_configurations();
// Device MUST be configured to send router advertisements if we reach this point. If it is not
// configured to send router advertisements, we should not have called this method.
assert!(router_configurations.get_should_send_advertisements());
trace!(
"send_router_advertisement: sending router advertisement from {:?} (dev = {:?}) to {:?}",
src_ip,
ND::get_device_id(device_id),
dst_ip
);
let src_ll = ND::get_link_layer_addr(ctx.state(), device_id);
let mut options = vec![NdpOption::SourceLinkLayerAddress(src_ll.bytes())];
let router_configurations =
ND::get_ndp_state(ctx.state_mut(), device_id).configs.get_router_configurations();
// If the link mtu is set to `None`, do not include the mtu option.
//
// See AdvLinkMtu in RFC 4861 section 6.2.1 for more information.
if let Some(mtu) = router_configurations.get_advertised_link_mtu() {
options.push(NdpOption::MTU(mtu.get()));
}
let prefix_list = router_configurations.get_advertised_prefix_list().clone();
for p in &prefix_list {
// We know that `unwrap` will not panic because `new_unaligned` checks to make sure that the
// byte slice we give it has exactly the number of bytes required for a `PrefixInformation`.
// Here, we pass it the byte slice representation of a `PrefixInformation`, so we know that
// `new_unaligned` will not return `None`.
options.push(NdpOption::PrefixInformation(&p));
}
let message = router_configurations.new_router_advertisement();
let body = ndp::OptionsSerializer::<_>::new(options.iter())
.into_serializer()
.encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new(
src_ip,
dst_ip,
IcmpUnusedCode,
message,
))
// The hop limit of a Router Advertisement message is set to `255` by RFC 4861 section 4.2.
.encapsulate(Ipv6PacketBuilder::new(src_ip, dst_ip, 255, IpProto::Icmpv6));
// Attempt to send the router advertisement message.
ND::send_ipv6_frame(ctx, device_id, dst_ip, body).unwrap_or_else(|_| {
error!("send_router_advertisement: failed to send router advertisement")
});
}
/// Helper function to send ndp packet over an NdpDevice
fn send_ndp_packet<D: EventDispatcher, ND: NdpDevice, B: ByteSlice, M>(
ctx: &mut Context<D>,
device_id: usize,
src_ip: Ipv6Addr,
dst_ip: Ipv6Addr,
link_addr: ND::LinkAddress,
message: M,
options: &[NdpOption],
) where
M: IcmpMessage<Ipv6, B, Code = IcmpUnusedCode>,
{
trace!("send_ndp_packet: src_ip={:?} dst_ip={:?}", src_ip, dst_ip);
ND::send_ipv6_frame_to(
ctx,
device_id,
link_addr,
ndp::OptionsSerializer::<_>::new(options.iter())
.into_serializer()
.encapsulate(IcmpPacketBuilder::<Ipv6, B, M>::new(
src_ip,
dst_ip,
IcmpUnusedCode,
message,
))
.encapsulate(Ipv6PacketBuilder::new(src_ip, dst_ip, 1, IpProto::Icmpv6)),
);
}
/// A handler for incoming NDP packets.
///
/// `NdpPacketHandler` is implemented by any `Context<D>` where `D:
/// EventDispatcher`, and it can also be mocked for use in testing.
pub(crate) trait NdpPacketHandler: IpDeviceIdContext {
/// Receive an NDP packet.
///
/// # Panics
///
/// `receive_ndp_packet` panics if `packet` is not one of
/// `RouterSolicitation`, `RouterAdvertisement`, `NeighborSolicitation`,
/// `NeighborAdvertisement`, or `Redirect`.
fn receive_ndp_packet<B: ByteSlice>(
&mut self,
device: Option<Self::DeviceId>,
src_ip: Ipv6Addr,
dst_ip: Ipv6Addr,
packet: Icmpv6Packet<B>,
) {
}
}
impl<D: EventDispatcher> NdpPacketHandler for Context<D> {
fn receive_ndp_packet<B: ByteSlice>(
&mut self,
device: Option<DeviceId>,
src_ip: Ipv6Addr,
dst_ip: Ipv6Addr,
packet: Icmpv6Packet<B>,
) {
trace!("receive_ndp_packet");
match device {
Some(d) => {
// TODO(brunodalbo) we're assuming the device Id is for an ethernet
// device, but it could be for another protocol.
receive_ndp_packet_inner::<_, EthernetNdpDevice, _>(
self,
d.id(),
src_ip,
dst_ip,
packet,
);
}
None => {
// NDP needs a device identifier context to operate on.
debug!("Got NDP packet without device identifier. Ignoring it.");
}
}
}
}
fn duplicate_address_detected<D: EventDispatcher, ND: NdpDevice>(
ctx: &mut Context<D>,
device_id: usize,
addr: Ipv6Addr,
) {
cancel_duplicate_address_detection::<_, ND>(ctx, device_id, addr);
// let's notify our device
ND::duplicate_address_detected(ctx, device_id, addr);
}
fn receive_ndp_packet_inner<D: EventDispatcher, ND: NdpDevice, B>(
ctx: &mut Context<D>,
device_id: usize,
src_ip: Ipv6Addr,
dst_ip: Ipv6Addr,
packet: Icmpv6Packet<B>,
) where
B: ByteSlice,
{
match packet {
Icmpv6Packet::RouterSolicitation(p) => {
trace!("receive_ndp_packet_inner: Received NDP RS");
if !ND::is_router(ctx, device_id) {
// Hosts MUST silently discard Router Solicitation messages
// as per RFC 4861 section 6.1.1.
trace!("receive_ndp_packet_inner: not a router, discarding NDP RS");
return;
}
// TODO(ghanan): Make sure IP's hop limit is set to 255 as per RFC 4861 section 6.1.1.
let source_link_layer_option = get_source_link_layer_option::<ND, _>(p.body());
if src_ip.is_unspecified() && source_link_layer_option.is_some() {
// If the IP source address is the unspecified address and there is a
// source link-layer address option in the message, we MUST silently
// discard the Router Solicitation message as per RFC 4861 section 6.1.1.
trace!("receive_ndp_packet_inner: source is unspcified but it has the source link-layer address option, discarding NDP RS");
return;
}
increment_counter!(ctx, "ndp::rx_router_solicitation");
log_unimplemented!((), "NDP Router Solicitation not implemented")
}
Icmpv6Packet::RouterAdvertisement(p) => {
trace!("receive_ndp_packet_inner: Received NDP RA from router: {:?}", src_ip);
let src_ip = if let Some(src_ip) = LinkLocalAddr::new(src_ip) {
src_ip
} else {
// Nodes MUST silently discard any received Router Advertisement message
// where the IP source address is not a link-local address as routers must
// use their link-local address as the source for Router Advertisements so
// hosts can uniquely identify routers, as per RFC 4861 section 6.1.2.
trace!("receive_ndp_packet_inner: source is not a link-local address, discarding NDP RA");
return;
};
// TODO(ghanan): Make sure IP's hop limit is set to 255 as per RFC 4861 section 6.1.2.
increment_counter!(ctx, "ndp::rx_router_advertisement");
if ND::is_router(ctx, device_id) {
// TODO(ghanan): Handle receiving Router Advertisements when this node is a router.
trace!("receive_ndp_packet_inner: received NDP RA as a router, discarding NDP RA");
return;
}
let (state, dispatcher) = ctx.state_and_dispatcher();
let ndp_state = ND::get_ndp_state_mut(state, device_id);
let ra = p.message();
let timer_id = NdpTimerId::new_router_invalidation_timer_id::<ND>(device_id, src_ip);
if ra.router_lifetime() == 0 {
if ndp_state.has_default_router(&src_ip) {
trace!("receive_ndp_packet_inner: NDP RA has zero-valued router lifetime, invaliding router: {:?}", src_ip);
// As per RFC 4861 section 6.3.4, immediately timeout the entry as specified in
// RFC 4861 section 6.3.5.
assert!(dispatcher.cancel_timeout(timer_id).is_some());
// `invalidate_default_router` may panic if `src_ip` does not reference a known
// default router, but we will only reach here if the router is already in our
// list of default routers, so we know `invalidate_default_router` will not
// panic.
ndp_state.invalidate_default_router(src_ip);
} else {
trace!("receive_ndp_packet_inner: NDP RA has zero-valued router lifetime, but the router {:?} is unknown so doing nothing", src_ip);
}
// As per RFC 4861 section 4.2, a zero-valued router lifetime only indicates the
// router is not to be used as a default router and is only applied to its
// usefulness as a default router; it does not apply to the other information
// contained in this message's fields or options. Given this, we continue as normal.
} else {
if ndp_state.has_default_router(&src_ip) {
trace!(
"receive_ndp_packet_inner: NDP RA from an already known router: {:?}",
src_ip
);
} else {
trace!("receive_ndp_packet_inner: NDP RA from a new router: {:?}", src_ip);
// TODO(ghanan): Make the number of default routers we store configurable?
ndp_state.add_default_router(src_ip);
};
// As per RFC 4861 section 4.2, the router livetime is in units of seconds.
let timer_duration = Duration::from_secs(ra.router_lifetime().into());
trace!("receive_ndp_packet_inner: NDP RA: updating invalidation timer to {:?} for router: {:?}", timer_duration, src_ip);
// Reset invalidation timeout.
dispatcher.schedule_timeout(timer_duration, timer_id);
}
// As per RFC 4861 section 6.3.4:
// If the received Reachable Time value is non-zero, the host SHOULD set
// its BaseReachableTime variable to the received value. If the new
// value differs from the previous value, the host SHOULD re-compute a
// new random ReachableTime value.
//
// TODO(ghanan): Make the updating of this field from the RA message configurable
// since the RFC does not say we MUST update the field.
//
// TODO(ghanan): In most cases, the advertised Reachable Time value will be the same
// in consecutive Router Advertisements, and a host's BaseReachableTime
// rarely changes. In such cases, an implementation SHOULD ensure that
// a new random value gets re-computed at least once every few hours.
if ra.reachable_time() != 0 {
let base_reachable_time = Duration::from_millis(ra.reachable_time().into());
trace!("receive_ndp_packet_inner: NDP RA: updating base_reachable_time to {:?} for router: {:?}", base_reachable_time, src_ip);
// As per RFC 4861 section 4.2, the reachable time field is the time in
// milliseconds.
ndp_state.set_base_reachable_time(base_reachable_time);
}
// As per RFC 4861 section 6.3.4:
// The RetransTimer variable SHOULD be copied from the Retrans Timer
// field, if the received value is non-zero.
//
// TODO(ghanan): Make the updating of this field from the RA message configurable
// since the RFC does not say we MUST update the field.
if ra.retransmit_timer() != 0 {
let retransmit_timer = Duration::from_millis(ra.retransmit_timer().into());
trace!("receive_ndp_packet_inner: NDP RA: updating retrans_timer to {:?} for router: {:?}", retransmit_timer, src_ip);
// As per RFC 4861 section 4.2, the retransmit timer field is the time in
// milliseconds.
ndp_state.set_retrans_timer(retransmit_timer);
}
// As per RFC 4861 section 6.3.4:
// If the received Cur Hop Limit value is non-zero, the host SHOULD set
// its CurHopLimit variable to the received value.
//
// TODO(ghanan): Make the updating of this field from the RA message configurable
// since the RFC does not say we MUST update the field.
if let Some(hop_limit) = NonZeroU8::new(ra.current_hop_limit()) {
trace!("receive_ndp_packet_inner: NDP RA: updating device's hop limit to {:?} for router: {:?}", ra.current_hop_limit(), src_ip);
ND::set_hop_limit(state, device_id, hop_limit);
}
for option in p.body().iter() {
match option {
// As per RFC 4861 section 6.3.4, if a Neighbor Cache entry is created
// for the router, its reachability state MUST be set to STALE as
// specified in Section 7.3.3. If a cache entry already exists and is
// updated with a different link-layer address, the reachability state
// MUST also be set to STALE.
//
// TODO(ghanan): Mark NDP state as STALE as per the RFC once we implement
// the RFC compliant states.
NdpOption::SourceLinkLayerAddress(a) => {
let ndp_state = ND::get_ndp_state_mut(ctx.state_mut(), device_id);
let link_addr =
ND::LinkAddress::from_bytes(&a[..ND::LinkAddress::BYTES_LENGTH]);
trace!("receive_ndp_packet_inner: NDP RA: setting link address for router {:?} to {:?}", src_ip, link_addr);
// Set the link address and mark it as stale if we either created
// the neighbor entry, or updated an existing one.
ndp_state.neighbors.set_link_address(src_ip.get(), link_addr, false);
}
NdpOption::MTU(mtu) => {
trace!("receive_ndp_packet_inner: mtu option with mtu = {:?}", mtu);
// TODO(ghanan): Make updating the MTU from an RA message configurable.
if mtu >= crate::ip::path_mtu::IPV6_MIN_MTU {
// `set_mtu` may panic if `mtu` is less than `IPV6_MIN_MTU` but we just
// checked to make sure that `mtu` is at least `IPV6_MIN_MTU` so we know
// `set_mtu` will not panic.
ND::set_mtu(ctx.state_mut(), device_id, mtu);
} else {
trace!("receive_ndp_packet_inner: NDP RA: not setting link MTU (from {:?}) to {:?} as it is less than IPV6_MIN_MTU", src_ip, mtu);
}
}
NdpOption::PrefixInformation(prefix_info) => {
let ndp_state = ND::get_ndp_state_mut(ctx.state_mut(), device_id);
trace!("receive_ndp_packet_inner: prefix information option with prefix = {:?}", prefix_info);
let addr_sub = match prefix_info.addr_subnet() {
None => {
trace!("receive_ndp_packet_inner: malformed prefix information, so ignoring");
continue;
}
Some(a) => a,
};
if !prefix_info.on_link_flag() {
// TODO(ghanan): Do something?
return;
}
if prefix_info.prefix().is_linklocal() {
// If the on-link flag is set and the prefix is the link-local prefix,
// ignore the option, as per RFC 4861 section 6.3.4.
trace!("receive_ndp_packet_inner: prefix is a link local, so ignoring");
continue;
}
// Timer ID for this prefix's invalidation.
let timer_id =
NdpTimerId::new_prefix_invalidation_timer_id::<ND>(device_id, addr_sub);
if prefix_info.valid_lifetime() == 0 {
if ndp_state.has_prefix(&addr_sub) {
trace!("receive_ndp_packet_inner: prefix is known and has valid lifetime = 0, so invaliding");
// If the on-link flag is set, the valid lifetime is 0 and the
// prefix is already present in our prefix list, timeout the prefix
// immediately, as per RFC 4861 section 6.3.4.
// Cancel the prefix invalidation timeout if it exists.
ctx.dispatcher_mut().cancel_timeout(timer_id);
let ndp_state = ND::get_ndp_state_mut(ctx.state_mut(), device_id);
ndp_state.invalidate_prefix(addr_sub);
} else {
// If the on-link flag is set, the valid lifetime is 0 and the
// prefix is not present in our prefix list, ignore the option, as
// per RFC 4861 section 6.3.4.
trace!("receive_ndp_packet_inner: prefix is unknown and is has valid lifetime = 0, so ignoring");
}
continue;
}
if !ndp_state.has_prefix(&addr_sub) {
// `add_prefix` may panic if the prefix already exists in
// our prefix list, but we will only reach here if it doesn't
// so we know `add_prefix` will not panic.
ndp_state.add_prefix(addr_sub);
}
// Reset invalidation timer.
if prefix_info.valid_lifetime() == std::u32::MAX {
// A valid lifetime of all 1 bits (== `std::u32::MAX`) represents
// infinity, as per RFC 4861 section 4.6.2. Given this, we do not need a
// timer to mark the prefix as invalid.
ctx.dispatcher_mut().cancel_timeout(timer_id);
} else {
ctx.dispatcher_mut().schedule_timeout(
Duration::from_secs(prefix_info.valid_lifetime().into()),
timer_id,
);
}
}
_ => {}
}
}
// If the router exists in our router table, make sure it is marked as a router as
// per RFC 4861 section 6.3.4.
let ndp_state = ND::get_ndp_state_mut(ctx.state_mut(), device_id);
if let Some(state) = ndp_state.neighbors.get_neighbor_state_mut(&src_ip) {
state.is_router = true;
}
}
Icmpv6Packet::NeighborSolicitation(p) => {
trace!("receive_ndp_packet_inner: Received NDP NS");
let target_address = p.message().target_address();
if !target_address.is_valid_unicast()
|| ND::ipv6_addr_state(ctx.state(), device_id, target_address).is_unassigned()
{
// just ignore packet, either it was not really meant for us or
// is malformed.
trace!("receive_ndp_packet_inner: Dropping NDP NS packet that is not meant for us or malformed");
return;
}
// This solicitation message is used for DAD.
if let Some(my_addr) = ND::get_ipv6_addr(ctx.state_mut(), device_id) {
if my_addr.is_tentative() {
// we are not allowed to respond to the solicitation
if src_ip.is_unspecified() {
trace!(
"receive_ndp_packet_inner: Received NDP NS: duplicate address detected"
);
// If the source address of the packet is the unspecified address,
// the source of the packet is performing DAD for the same target
// address as our `my_addr`. A duplicate address has been detected.
duplicate_address_detected::<_, ND>(ctx, device_id, my_addr.into_inner());
}
} else {
increment_counter!(ctx, "ndp::rx_neighbor_solicitation");
// If we have a source link layer address option, we take it and
// save to our cache.
if !src_ip.is_unspecified() {
// We only update the cache if it is not from an unspecified address,
// i.e., it is not a DAD message. (RFC 4861)
if let Some(ll) = get_source_link_layer_option::<ND, _>(p.body()) {
trace!("receive_ndp_packet_inner: Received NDP NS from {:?} has source link layer option w/ link address {:?}", src_ip, ll);
// Set the link address and mark it as stale if we either created
// the neighbor entry, or updated an existing one, as per RFC 4861
// section 7.2.3.
ND::get_ndp_state_mut(ctx.state_mut(), device_id)
.neighbors
.set_link_address(src_ip, ll, false);
}
trace!("receive_ndp_packet_inner: Received NDP NS: sending NA to source of NS {:?}", src_ip);
// Finally we ought to reply to the Neighbor Solicitation with a
// Neighbor Advertisement.
send_neighbor_advertisement::<_, ND>(
ctx,
device_id,
true,
*target_address,
src_ip,
);
} else {
trace!("receive_ndp_packet_inner: Received NDP NS: sending NA to all nodes multicast");
// Send out Unsolicited Advertisement in response to neighbor who's
// performing DAD, as described in RFC 4861 and 4862
send_neighbor_advertisement::<_, ND>(
ctx,
device_id,
false,
*target_address,
Ipv6::ALL_NODES_LINK_LOCAL_ADDRESS,
)
}
}
}
}
Icmpv6Packet::NeighborAdvertisement(p) => {
trace!("receive_ndp_packet_inner: Received NDP NA");
let message = p.message();
let target_address = message.target_address();
match ND::ipv6_addr_state(ctx.state(), device_id, target_address) {
AddressState::Tentative => {
duplicate_address_detected::<_, ND>(ctx, device_id, *target_address);
return;
}
AddressState::Assigned => {
// RFC 4862 says this situation is out of the scope, so we
// just log out the situation.
//
// TODO(ghanan): Signal to bindings that a duplicate address is detected?
error!("A duplicated address found when we are not in DAD process!");
return;
}
// Do nothing
AddressState::Unassigned => {}
}
increment_counter!(ctx, "ndp::rx_neighbor_advertisement");
let state = ND::get_ndp_state_mut(ctx.state_mut(), device_id);
match state.neighbors.get_neighbor_state_mut(&src_ip) {
None => {
// If the neighbor is not in the cache, we just ignore the
// advertisement, as we're not interested in communicating
// with it.
trace!("receive_ndp_packet_inner: Ignoring NDP NA from {:?} does not already exist in our list of neighbors, so discarding", src_ip);
}
Some(NeighborState { state, link_address, is_router }) if state.is_incomplete() => {
if let Some(address) = get_target_link_layer_option::<ND, _>(p.body()) {
*link_address = Some(address);
// If the advertisement's Solicited flag is set, the state of the
// entry is set to REACHABLE; otherwise, it is set to STALE, as
// per RFC 4861 section 7.2.5.
if message.solicited_flag() {
*state = NeighborEntryState::Reachable;
} else {
*state = NeighborEntryState::Stale;
}
// Set ths IsRouter flag as per RFC 4861 section 7.2.5.
*is_router = message.router_flag();
// Cancel the resolution timeout.
ctx.dispatcher.cancel_timeout(
NdpTimerId::new_link_address_resolution_timer_id::<ND>(
device_id, src_ip,
),
);
ND::address_resolved(ctx, device_id, &src_ip, address);
} else {
trace!("receive_ndp_packet_inner: Performing address resolution but the NDP NA from {:?} does not have a target link layer address option, so discarding", src_ip);
}
}
_ => {
// TODO(brunodalbo) In any other case, the neighbor
// advertisement is used to update reachability and router
// status in the cache.
log_unimplemented!((), "Received already known neighbor advertisement")
}
}
}
Icmpv6Packet::Redirect(p) => log_unimplemented!((), "NDP Redirect not implemented"),
_ => unreachable!("Invalid ICMP packet passed to NDP"),
}
}
fn get_source_link_layer_option<ND: NdpDevice, B>(options: &Options<B>) -> Option<ND::LinkAddress>
where
B: ByteSlice,
{
options.iter().find_map(|o| match o {
NdpOption::SourceLinkLayerAddress(a) => {
if a.len() >= ND::LinkAddress::BYTES_LENGTH {
Some(ND::LinkAddress::from_bytes(&a[..ND::LinkAddress::BYTES_LENGTH]))
} else {
None
}
}
_ => None,
})
}
fn get_target_link_layer_option<ND: NdpDevice, B>(options: &Options<B>) -> Option<ND::LinkAddress>
where
B: ByteSlice,
{
options.iter().find_map(|o| match o {
NdpOption::TargetLinkLayerAddress(a) => {
if a.len() >= ND::LinkAddress::BYTES_LENGTH {
Some(ND::LinkAddress::from_bytes(&a[..ND::LinkAddress::BYTES_LENGTH]))
} else {
None
}
}
_ => None,
})
}
#[cfg(test)]
mod tests {
use super::*;
use net_types::ethernet::Mac;
use net_types::ip::AddrSubnet;
use packet::{Buf, Buffer, ParseBuffer};
use crate::device::{
ethernet::EthernetNdpDevice, get_ip_addr_subnet, get_ipv6_hop_limit, get_mtu,
is_forwarding_enabled, is_in_ip_multicast, set_forwarding_enabled, set_ip_addr_subnet,
};
use crate::ip::IPV6_MIN_MTU;
use crate::testutil::{
self, get_counter_val, get_dummy_config, parse_ethernet_frame,
parse_icmp_packet_in_ip_packet_in_ethernet_frame, set_logger_for_test, trigger_next_timer,
DummyEventDispatcher, DummyEventDispatcherBuilder, DummyInstant, DummyNetwork,
};
use crate::wire::icmp::ndp::{
options::PrefixInformation, OptionsSerializer, RouterAdvertisement, RouterSolicitation,
};
use crate::wire::icmp::{IcmpEchoRequest, IcmpParseArgs, Icmpv6Packet};
use crate::{Instant, StackStateBuilder, TimerId};
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]);
fn local_ip() -> Ipv6Addr {
TEST_LOCAL_MAC.to_ipv6_link_local().get()
}
fn remote_ip() -> Ipv6Addr {
TEST_REMOTE_MAC.to_ipv6_link_local().get()
}
fn router_advertisement_message(
src_ip: Ipv6Addr,
dst_ip: Ipv6Addr,
current_hop_limit: u8,
managed_flag: bool,
other_config_flag: bool,
router_lifetime: u16,
reachable_time: u32,
retransmit_timer: u32,
) -> Buf<Vec<u8>> {
Buf::new(Vec::new(), ..)
.encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new(
src_ip,
dst_ip,
IcmpUnusedCode,
RouterAdvertisement::new(
current_hop_limit,
managed_flag,
other_config_flag,
router_lifetime,
reachable_time,
retransmit_timer,
),
))
.serialize_vec_outer()
.unwrap()
.into_inner()
}
fn check_router_config(
c: &NdpRouterConfigurations,
should_send: bool,
interval: RangeInclusive<u16>,
managed: bool,
other_config: bool,
mtu: Option<NonZeroU32>,
reachable_time: u32,
retransmit_timer: u32,
hop_limit: u8,
default_lifetime: Option<NonZeroU16>,
prefix_list: &Vec<PrefixInformation>,
) {
assert_eq!(c.get_should_send_advertisements(), should_send);
assert_eq!(c.get_router_advertisements_interval(), interval);
assert_eq!(c.get_advertised_managed_flag(), managed);
assert_eq!(c.get_advertised_other_config_flag(), other_config);
assert_eq!(c.get_advertised_link_mtu(), mtu);
assert_eq!(c.get_advertised_reachable_time(), reachable_time);
assert_eq!(c.get_advertised_retransmit_timer(), retransmit_timer);
assert_eq!(c.get_advertised_current_hop_limit(), hop_limit);
assert_eq!(c.get_advertised_default_lifetime(), default_lifetime);
assert_eq!(c.get_advertised_prefix_list(), prefix_list);
}
#[test]
fn test_ndp_configurations() {
let prefix = Vec::new();
let def_life = NonZeroU16::new(1800);
let mut c = NdpConfigurations::default();
assert_eq!(c.get_dup_addr_detect_transmits(), NonZeroU8::new(DUP_ADDR_DETECT_TRANSMITS));
assert_eq!(c.get_max_router_solicitations(), NonZeroU8::new(MAX_RTR_SOLICITATIONS));
c.set_dup_addr_detect_transmits(None);
assert_eq!(c.get_dup_addr_detect_transmits(), None);
assert_eq!(c.get_max_router_solicitations(), NonZeroU8::new(MAX_RTR_SOLICITATIONS));
check_router_config(
c.get_router_configurations(),
false,
200..=600,
false,
false,
None,
0,
0,
64,
def_life,
&prefix,
);
c.set_dup_addr_detect_transmits(NonZeroU8::new(100));
assert_eq!(c.get_dup_addr_detect_transmits(), NonZeroU8::new(100));
assert_eq!(c.get_max_router_solicitations(), NonZeroU8::new(MAX_RTR_SOLICITATIONS));
check_router_config(
c.get_router_configurations(),
false,
200..=600,
false,
false,
None,
0,
0,
64,
def_life,
&prefix,
);
c.set_max_router_solicitations(None);
assert_eq!(c.get_dup_addr_detect_transmits(), NonZeroU8::new(100));
assert_eq!(c.get_max_router_solicitations(), None);
check_router_config(
c.get_router_configurations(),
false,
200..=600,
false,
false,
None,
0,
0,
64,
def_life,
&prefix,
);
c.set_max_router_solicitations(NonZeroU8::new(2));
assert_eq!(c.get_dup_addr_detect_transmits(), NonZeroU8::new(100));
assert_eq!(c.get_max_router_solicitations(), NonZeroU8::new(2));
check_router_config(
c.get_router_configurations(),
false,
200..=600,
false,
false,
None,
0,
0,
64,
def_life,
&prefix,
);
// Max Router Solicitations gets saturated at `MAX_RTR_SOLICITATIONS`.
c.set_max_router_solicitations(NonZeroU8::new(5));
assert_eq!(c.get_dup_addr_detect_transmits(), NonZeroU8::new(100));
assert_eq!(c.get_max_router_solicitations(), NonZeroU8::new(MAX_RTR_SOLICITATIONS));
check_router_config(
c.get_router_configurations(),
false,
200..=600,
false,
false,
None,
0,
0,
64,
def_life,
&prefix,
);
let mut rc = NdpRouterConfigurations::default();
rc.set_should_send_advertisements(true);
rc.set_router_advertisements_interval(3..=4);
rc.set_advertised_reachable_time(3600000);
c.set_router_configurations(rc);
assert_eq!(c.get_dup_addr_detect_transmits(), NonZeroU8::new(100));
assert_eq!(c.get_max_router_solicitations(), NonZeroU8::new(MAX_RTR_SOLICITATIONS));
check_router_config(
c.get_router_configurations(),
true,
3..=4,
false,
false,
None,
3600000,
0,
64,
def_life,
&prefix,
);
}
#[test]
fn test_ndp_router_configurations() {
let prefix = Vec::new();
let def_life = NonZeroU16::new(1800);
let mut c = NdpRouterConfigurations::default();
check_router_config(&c, false, 200..=600, false, false, None, 0, 0, 64, def_life, &prefix);
c.set_should_send_advertisements(true);
check_router_config(&c, true, 200..=600, false, false, None, 0, 0, 64, def_life, &prefix);
c.set_should_send_advertisements(false);
check_router_config(&c, false, 200..=600, false, false, None, 0, 0, 64, def_life, &prefix);
c.set_router_advertisements_interval(3..=4);
check_router_config(&c, false, 3..=4, false, false, None, 0, 0, 64, def_life, &prefix);
c.set_router_advertisements_interval(300..=500);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 64, def_life, &prefix);
// Max cannot be less than 4.
c.set_router_advertisements_interval(3..=3);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 64, def_life, &prefix);
// Max cannot be greater than 1800
c.set_router_advertisements_interval(3..=2000);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 64, def_life, &prefix);
// Min cannot be less than 3.
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 64, def_life, &prefix);
c.set_router_advertisements_interval(2..=500);
// Min cannot be greater than 0.75 * max
c.set_router_advertisements_interval(301..=400);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 64, def_life, &prefix);
// Min cannot be less than max
c.set_router_advertisements_interval(300..=200);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 64, def_life, &prefix);
c.set_advertised_managed_flag(true);
check_router_config(&c, false, 300..=500, true, false, None, 0, 0, 64, def_life, &prefix);
c.set_advertised_managed_flag(false);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 64, def_life, &prefix);
c.set_advertised_other_config_flag(true);
check_router_config(&c, false, 300..=500, false, true, None, 0, 0, 64, def_life, &prefix);
c.set_advertised_other_config_flag(false);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 64, def_life, &prefix);
c.set_advertised_link_mtu(NonZeroU32::new(1500));
check_router_config(
&c,
false,
300..=500,
false,
false,
NonZeroU32::new(1500),
0,
0,
64,
def_life,
&prefix,
);
c.set_advertised_link_mtu(None);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 64, def_life, &prefix);
c.set_advertised_reachable_time(500);
check_router_config(
&c,
false,
300..=500,
false,
false,
None,
500,
0,
64,
def_life,
&prefix,
);
c.set_advertised_reachable_time(3600000);
check_router_config(
&c,
false,
300..=500,
false,
false,
None,
3600000,
0,
64,
def_life,
&prefix,
);
// cannot be greater than 3600000
c.set_advertised_reachable_time(3600001);
check_router_config(
&c,
false,
300..=500,
false,
false,
None,
3600000,
0,
64,
def_life,
&prefix,
);
c.set_advertised_reachable_time(0);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 64, def_life, &prefix);
c.set_advertised_retransmit_timer(500);
check_router_config(
&c,
false,
300..=500,
false,
false,
None,
0,
500,
64,
def_life,
&prefix,
);
c.set_advertised_retransmit_timer(0);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 64, def_life, &prefix);
c.set_advertised_current_hop_limit(50);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 50, def_life, &prefix);
c.set_advertised_current_hop_limit(0);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 0, def_life, &prefix);
c.set_advertised_current_hop_limit(64);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 64, def_life, &prefix);
c.set_advertised_default_lifetime(None);
check_router_config(&c, false, 300..=500, false, false, None, 0, 0, 64, None, &prefix);
c.set_advertised_default_lifetime(NonZeroU16::new(600));
check_router_config(
&c,
false,
300..=500,
false,
false,
None,
0,
0,
64,
NonZeroU16::new(600),
&prefix,
);
// cannot be less than max router advertisement interval
c.set_advertised_default_lifetime(NonZeroU16::new(499));
check_router_config(
&c,
false,
300..=500,
false,
false,
None,
0,
0,
64,
NonZeroU16::new(600),
&prefix,
);
// cannot be greater than 9000
c.set_advertised_default_lifetime(NonZeroU16::new(9001));
check_router_config(
&c,
false,
300..=500,
false,
false,
None,
0,
0,
64,
NonZeroU16::new(600),
&prefix,
);
// updating router advertisement interval should update default lifetime to max if the new
// max is greater than the lifetime.
c.set_router_advertisements_interval(300..=800);
check_router_config(
&c,
false,
300..=800,
false,
false,
None,
0,
0,
64,
NonZeroU16::new(800),
&prefix,
);
let prefix = vec![PrefixInformation::new(
64,
true,
false,
500,
400,
Ipv6Addr::new([51, 52, 53, 54, 55, 56, 57, 58, 0, 0, 0, 0, 0, 0, 0, 0]),
)];
c.set_advertised_prefix_list(prefix.clone());
check_router_config(
&c,
false,
300..=800,
false,
false,
None,
0,
0,
64,
NonZeroU16::new(800),
&prefix,
);
let prefix = Vec::new();
c.set_advertised_prefix_list(prefix.clone());
check_router_config(
&c,
false,
300..=800,
false,
false,
None,
0,
0,
64,
NonZeroU16::new(800),
&prefix,
);
}
#[test]
fn test_send_neighbor_solicitation_on_cache_miss() {
set_logger_for_test();
let mut ctx = DummyEventDispatcherBuilder::default().build();
let dev_id = ctx.state_mut().device.add_ethernet_device(TEST_LOCAL_MAC, IPV6_MIN_MTU);
crate::device::initialize_device(&mut ctx, dev_id);
// Now we have to manually assign the ip addresses, see `EthernetNdpDevice::get_ipv6_addr`
set_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(local_ip(), 128).unwrap());
lookup::<DummyEventDispatcher, EthernetNdpDevice>(&mut ctx, dev_id.id(), remote_ip());
// Check that we send the original neighbor solicitation,
// then resend a few times if we don't receive a response.
for packet_num in 0..usize::from(MAX_MULTICAST_SOLICIT) {
assert_eq!(ctx.dispatcher.frames_sent().len(), packet_num + 1);
testutil::trigger_next_timer(&mut ctx);
}
// check that we hit the timeout after MAX_MULTICAST_SOLICIT
assert_eq!(
*ctx.state().test_counters.get("ndp::neighbor_solicitation_timeout"),
1,
"timeout counter at zero"
);
}
#[test]
fn test_address_resolution() {
set_logger_for_test();
let mut local = DummyEventDispatcherBuilder::default();
local.add_device(TEST_LOCAL_MAC);
let mut remote = DummyEventDispatcherBuilder::default();
remote.add_device(TEST_REMOTE_MAC);
let device_id = DeviceId::new_ethernet(0);
let mut net = DummyNetwork::new(
vec![("local", local.build()), ("remote", remote.build())].into_iter(),
|ctx, dev| {
if *ctx == "local" {
("remote", device_id, None)
} else {
("local", device_id, None)
}
},
);
// let's try to ping the remote device from the local device:
let req = IcmpEchoRequest::new(0, 0);
let req_body = &[1, 2, 3, 4];
let body = Buf::new(req_body.to_vec(), ..).encapsulate(
IcmpPacketBuilder::<Ipv6, &[u8], _>::new(local_ip(), remote_ip(), IcmpUnusedCode, req),
);
// Manually assigning the addresses
set_ip_addr_subnet(
net.context("local"),
device_id,
AddrSubnet::new(local_ip(), 128).unwrap(),
);
set_ip_addr_subnet(
net.context("remote"),
device_id,
AddrSubnet::new(remote_ip(), 128).unwrap(),
);
assert_eq!(net.context("local").dispatcher.frames_sent().len(), 0);
assert_eq!(net.context("remote").dispatcher.frames_sent().len(), 0);
crate::ip::send_ip_packet_from_device(
net.context("local"),
device_id,
local_ip(),
remote_ip(),
remote_ip(),
IpProto::Icmpv6,
body,
None,
);
// this should've triggered a neighbor solicitation to come out of local
assert_eq!(net.context("local").dispatcher.frames_sent().len(), 1);
// and a timer should've been started.
assert_eq!(net.context("local").dispatcher.timer_events().count(), 1);
net.step();
// Neighbor entry for remote should be marked as Incomplete.
assert_eq!(
EthernetNdpDevice::get_ndp_state_mut::<_>(
net.context("local").state_mut(),
device_id.id()
)
.neighbors
.get_neighbor_state(&remote_ip())
.unwrap()
.state,
NeighborEntryState::Incomplete { transmit_counter: 1 }
);
assert_eq!(
*net.context("remote").state().test_counters.get("ndp::rx_neighbor_solicitation"),
1,
"remote received solicitation"
);
assert_eq!(net.context("remote").dispatcher.frames_sent().len(), 1);
// forward advertisement response back to local
net.step();
assert_eq!(
*net.context("local").state().test_counters.get("ndp::rx_neighbor_advertisement"),
1,
"local received advertisement"
);
// at the end of the exchange, both sides should have each other on
// their ndp tables:
let local_neighbor = EthernetNdpDevice::get_ndp_state_mut::<_>(
net.context("local").state_mut(),
device_id.id(),
)
.neighbors
.get_neighbor_state(&remote_ip())
.unwrap();
assert_eq!(local_neighbor.link_address.unwrap(), TEST_REMOTE_MAC,);
// Remote must be reachable from local since it responded with
// an NA message with the solicited flag set.
assert_eq!(local_neighbor.state, NeighborEntryState::Reachable,);
let remote_neighbor = EthernetNdpDevice::get_ndp_state_mut::<_>(
net.context("remote").state_mut(),
device_id.id(),
)
.neighbors
.get_neighbor_state(&local_ip())
.unwrap();
assert_eq!(remote_neighbor.link_address.unwrap(), TEST_LOCAL_MAC,);
// Local must be marked as stale because remote got an NS from it
// but has not itself sent any packets to it and confirmed that
// local actually received it.
assert_eq!(remote_neighbor.state, NeighborEntryState::Stale);
// and the local timer should've been unscheduled:
assert_eq!(net.context("local").dispatcher.timer_events().count(), 0);
// upon link layer resolution, the original ping request should've been
// sent out:
assert_eq!(net.context("local").dispatcher.frames_sent().len(), 1);
net.step();
assert_eq!(
*net.context("remote").state().test_counters.get("receive_icmpv6_packet::echo_request"),
1
);
// TODO(brunodalbo): we should be able to verify that remote also sends
// back an echo reply, but we're having some trouble with IPv6 link
// local addresses.
}
#[test]
fn test_deinitialize_cancels_timers() {
// Test that associated timers are cancelled when the NDP device
// is deinitialized.
set_logger_for_test();
let mut ctx = DummyEventDispatcherBuilder::default().build();
let dev_id = ctx.state_mut().device.add_ethernet_device(TEST_LOCAL_MAC, IPV6_MIN_MTU);
crate::device::initialize_device(&mut ctx, dev_id);
// Now we have to manually assign the IP addresses, see `EthernetNdpDevice::get_ipv6_addr`
set_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(local_ip(), 128).unwrap());
lookup::<DummyEventDispatcher, EthernetNdpDevice>(&mut ctx, dev_id.id(), remote_ip());
// This should have scheduled a timer
assert_eq!(ctx.dispatcher.timer_events().count(), 1);
// Deinitializing a different ID should not impact the current timer
deinitialize(&mut ctx, dev_id.id + 1);
assert_eq!(ctx.dispatcher.timer_events().count(), 1);
// Deinitializing the correct ID should cancel the timer.
deinitialize(&mut ctx, dev_id.id);
assert_eq!(ctx.dispatcher.timer_events().count(), 0);
}
#[test]
fn test_dad_duplicate_address_detected_solicitation() {
// Tests whether a duplicate address will get detected by solicitation
// In this test, two nodes having the same MAC address will come up
// at the same time. And both of them will use the EUI address. Each
// of them should be able to detect each other is using the same address,
// so they will both give up using that address.
set_logger_for_test();
let mac = Mac::new([1, 2, 3, 4, 5, 6]);
let addr = AddrSubnet::new(mac.to_ipv6_link_local().get(), 128).unwrap();
let multicast_addr = mac.to_ipv6_link_local().get().to_solicited_node_address();
let mut local = DummyEventDispatcherBuilder::default();
local.add_device(mac);
let mut remote = DummyEventDispatcherBuilder::default();
remote.add_device(mac);
let device_id = DeviceId::new_ethernet(0);
let mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = crate::device::ndp::NdpConfigurations::default();
ndp_configs.set_max_router_solicitations(None);
stack_builder.device_builder().set_default_ndp_configs(ndp_configs);
// We explicitly call `build_with` when building our contexts below because `build` will
// set the default NDP parameter DUP_ADDR_DETECT_TRANSMITS to 0 (effectively disabling
// DAD) so we use our own custom `StackStateBuilder` to set it to the default value
// of `1` (see `DUP_ADDR_DETECT_TRANSMITS`).
let mut net = DummyNetwork::new(
vec![
("local", local.build_with(stack_builder.clone(), DummyEventDispatcher::default())),
("remote", remote.build_with(stack_builder, DummyEventDispatcher::default())),
]
.into_iter(),
|ctx, dev| {
if *ctx == "local" {
("remote", device_id, None)
} else {
("local", device_id, None)
}
},
);
set_ip_addr_subnet(net.context("local"), device_id, addr);
set_ip_addr_subnet(net.context("remote"), device_id, addr);
assert_eq!(net.context("local").dispatcher.frames_sent().len(), 1);
assert_eq!(net.context("remote").dispatcher.frames_sent().len(), 1);
// Both devices should be in the solicited-node multicast group.
assert!(is_in_ip_multicast(net.context("local"), device_id, multicast_addr));
assert!(is_in_ip_multicast(net.context("remote"), device_id, multicast_addr));
net.step();
// they should now realize the address they intend to use has a duplicate
// in the local network
assert!(get_ip_addr_subnet::<_, Ipv6Addr>(net.context("local"), device_id).is_none());
assert!(get_ip_addr_subnet::<_, Ipv6Addr>(net.context("remote"), device_id).is_none());
// Both devices should not be in the multicast group
assert!(!is_in_ip_multicast(net.context("local"), device_id, multicast_addr));
assert!(!is_in_ip_multicast(net.context("remote"), device_id, multicast_addr));
}
#[test]
fn test_dad_duplicate_address_detected_advertisement() {
// Tests whether a duplicate address will get detected by advertisement
// In this test, one of the node first assigned itself the local_ip(),
// then the second node comes up and it should be able to find out that
// it cannot use the address because someone else has already taken that
// address.
set_logger_for_test();
let mut local = DummyEventDispatcherBuilder::default();
local.add_device(TEST_LOCAL_MAC);
let mut remote = DummyEventDispatcherBuilder::default();
remote.add_device(TEST_REMOTE_MAC);
let device_id = DeviceId::new_ethernet(0);
let mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = crate::device::ndp::NdpConfigurations::default();
ndp_configs.set_max_router_solicitations(None);
stack_builder.device_builder().set_default_ndp_configs(ndp_configs);
// We explicitly call `build_with` when building our contexts below because `build` will
// set the default NDP parameter DUP_ADDR_DETECT_TRANSMITS to 0 (effectively disabling
// DAD) so we use our own custom `StackStateBuilder` to set it to the default value
// of `1` (see `DUP_ADDR_DETECT_TRANSMITS`).
let mut net = DummyNetwork::new(
vec![
("local", local.build_with(stack_builder.clone(), DummyEventDispatcher::default())),
("remote", remote.build_with(stack_builder, DummyEventDispatcher::default())),
]
.into_iter(),
|ctx, dev| {
if *ctx == "local" {
("remote", device_id, None)
} else {
("local", device_id, None)
}
},
);
println!("Setting new IP on local");
let addr = AddrSubnet::new(local_ip(), 128).unwrap();
let multicast_addr = local_ip().to_solicited_node_address();
set_ip_addr_subnet(net.context("local"), device_id, addr);
// Only local should be in the solicited node multicast group.
assert!(is_in_ip_multicast(net.context("local"), device_id, multicast_addr));
assert!(!is_in_ip_multicast(net.context("remote"), device_id, multicast_addr));
assert!(testutil::trigger_next_timer(net.context("local")));
let local_addr =
EthernetNdpDevice::get_ipv6_addr(net.context("local").state_mut(), device_id.id())
.unwrap();
assert!(!local_addr.is_tentative());
assert_eq!(local_ip(), local_addr.into_inner());
println!("Set new IP on remote");
set_ip_addr_subnet(net.context("remote"), device_id, addr);
// local & remote should be in the multicast group.
assert!(is_in_ip_multicast(net.context("local"), device_id, multicast_addr));
assert!(is_in_ip_multicast(net.context("remote"), device_id, multicast_addr));
net.step();
let remote_addr = get_ip_addr_subnet::<_, Ipv6Addr>(net.context("remote"), device_id);
assert!(remote_addr.is_none());
// let's make sure that our local node still can use that address
let local_addr =
EthernetNdpDevice::get_ipv6_addr(net.context("local").state_mut(), device_id.id())
.unwrap();
assert!(!local_addr.is_tentative());
assert_eq!(local_ip(), local_addr.into_inner());
// Only local should be in the solicited node multicast group.
assert!(is_in_ip_multicast(net.context("local"), device_id, multicast_addr));
assert!(!is_in_ip_multicast(net.context("remote"), device_id, multicast_addr));
}
#[test]
fn test_dad_set_ipv6_address_when_ongoing() {
// Test that we can make our tentative address change when DAD is ongoing.
// We explicitly call `build_with` when building our context below because `build` will
// set the default NDP parameter DUP_ADDR_DETECT_TRANSMITS to 0 (effectively disabling
// DAD) so we use our own custom `StackStateBuilder` to set it to the default value
// of `1` (see `DUP_ADDR_DETECT_TRANSMITS`).
let mut ctx = DummyEventDispatcherBuilder::default()
.build_with(StackStateBuilder::default(), DummyEventDispatcher::default());
let dev_id = ctx.state_mut().add_ethernet_device(TEST_LOCAL_MAC, IPV6_MIN_MTU);
crate::device::initialize_device(&mut ctx, dev_id);
set_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(local_ip(), 128).unwrap());
assert_eq!(
EthernetNdpDevice::get_ipv6_addr(ctx.state(), dev_id.id()).unwrap(),
Tentative::new_tentative(local_ip())
);
set_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(remote_ip(), 128).unwrap());
assert_eq!(
EthernetNdpDevice::get_ipv6_addr(ctx.state(), dev_id.id()).unwrap(),
Tentative::new_tentative(remote_ip())
);
}
#[test]
fn test_dad_three_transmits_no_conflicts() {
let mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = crate::device::ndp::NdpConfigurations::default();
ndp_configs.set_max_router_solicitations(None);
stack_builder.device_builder().set_default_ndp_configs(ndp_configs);
let mut ctx = Context::new(stack_builder.build(), DummyEventDispatcher::default());
let dev_id = ctx.state_mut().add_ethernet_device(TEST_LOCAL_MAC, IPV6_MIN_MTU);
crate::device::initialize_device(&mut ctx, dev_id);
EthernetNdpDevice::get_ndp_state_mut(&mut ctx.state_mut(), dev_id.id())
.set_dad_transmits(NonZeroU8::new(3));
set_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(local_ip(), 128).unwrap());
for i in 0..3 {
testutil::trigger_next_timer(&mut ctx);
}
let addr = EthernetNdpDevice::get_ipv6_addr(ctx.state(), dev_id.id()).unwrap();
assert_eq!(addr.try_into_permanent().unwrap(), local_ip());
}
#[test]
fn test_dad_three_transmits_with_conflicts() {
// test if the implementation is correct when we have more than 1
// NS packets to send.
set_logger_for_test();
let mac = Mac::new([1, 2, 3, 4, 5, 6]);
let mut local = DummyEventDispatcherBuilder::default();
local.add_device(mac);
let mut remote = DummyEventDispatcherBuilder::default();
remote.add_device(mac);
let device_id = DeviceId::new_ethernet(0);
let mut net = DummyNetwork::new(
vec![("local", local.build()), ("remote", remote.build())].into_iter(),
|ctx, dev| {
if *ctx == "local" {
("remote", device_id, None)
} else {
("local", device_id, None)
}
},
);
EthernetNdpDevice::get_ndp_state_mut(&mut net.context("local").state_mut(), device_id.id())
.set_dad_transmits(NonZeroU8::new(3));
EthernetNdpDevice::get_ndp_state_mut(
&mut net.context("remote").state_mut(),
device_id.id(),
)
.set_dad_transmits(NonZeroU8::new(3));
set_ip_addr_subnet(
net.context("local"),
device_id,
AddrSubnet::new(mac.to_ipv6_link_local().get(), 128).unwrap(),
);
// during the first and second period, the remote host is still down.
assert!(testutil::trigger_next_timer(net.context("local")));
assert!(testutil::trigger_next_timer(net.context("local")));
set_ip_addr_subnet(
net.context("remote"),
device_id,
AddrSubnet::new(mac.to_ipv6_link_local().get(), 128).unwrap(),
);
// the local host should have sent out 3 packets while the remote one
// should only have sent out 1.
assert_eq!(net.context("local").dispatcher.frames_sent().len(), 3);
assert_eq!(net.context("remote").dispatcher.frames_sent().len(), 1);
net.step();
// lets make sure that all timers are cancelled properly
assert_eq!(net.context("local").dispatcher.timer_events().count(), 0);
assert_eq!(net.context("remote").dispatcher.timer_events().count(), 0);
// they should now realize the address they intend to use has a duplicate
// in the local network
assert!(get_ip_addr_subnet::<_, Ipv6Addr>(net.context("local"), device_id).is_none());
assert!(get_ip_addr_subnet::<_, Ipv6Addr>(net.context("remote"), device_id).is_none());
}
#[test]
fn test_receiving_router_solicitation_validity_check() {
let config = get_dummy_config::<Ipv6Addr>();
let src_ip = Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 10]);
let src_mac = [10, 11, 12, 13, 14, 15];
let options = vec![NdpOption::SourceLinkLayerAddress(&src_mac[..])];
//
// Test receiving NDP RS when not a router (should not receive)
//
let mut ctx = DummyEventDispatcherBuilder::from_config(config.clone())
.build::<DummyEventDispatcher>();
let device = Some(DeviceId::new_ethernet(0));
let mut icmpv6_packet_buf = OptionsSerializer::new(options.iter())
.into_serializer()
.encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new(
src_ip,
config.local_ip,
IcmpUnusedCode,
RouterSolicitation::default(),
))
.serialize_vec_outer()
.unwrap();
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip, config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_solicitation"), 0);
//
// Test receiving NDP RS as a router (should receive)
//
let mut state_builder = StackStateBuilder::default();
state_builder.ip_builder().forward(true);
let mut ctx = DummyEventDispatcherBuilder::from_config(config.clone())
.build_with(state_builder, DummyEventDispatcher::default());
set_forwarding_enabled::<_, Ipv6>(&mut ctx, DeviceId::new_ethernet(0), true);
icmpv6_packet_buf.reset();
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip, config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_solicitation"), 1);
//
// Test receiving NDP RS as a router, but source is unspecified and the source
// link layer option is included (should not receive)
//
let unspecified_source = Ipv6Addr::default();
let mut icmpv6_packet_buf = OptionsSerializer::new(options.iter())
.into_serializer()
.encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new(
unspecified_source,
config.local_ip,
IcmpUnusedCode,
RouterSolicitation::default(),
))
.serialize_vec_outer()
.unwrap();
println!("{:?}", icmpv6_packet_buf);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(
unspecified_source,
config.local_ip,
))
.unwrap();
ctx.receive_ndp_packet(device, unspecified_source, config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_solicitation"), 1);
}
#[test]
fn test_receiving_router_advertisement_validity_check() {
let config = get_dummy_config::<Ipv6Addr>();
let src_mac = [10, 11, 12, 13, 14, 15];
let src_ip = Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 10]);
let mut ctx = DummyEventDispatcherBuilder::from_config(config.clone())
.build::<DummyEventDispatcher>();
let device = Some(DeviceId::new_ethernet(0));
//
// Test receiving NDP RA where source ip is not a link local address (should not receive)
//
let mut icmpv6_packet_buf =
router_advertisement_message(src_ip, config.local_ip, 1, false, false, 3, 4, 5);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip, config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 0);
//
// Test receiving NDP RA where source ip is a link local address (should receive)
//
let src_ip = Mac::new(src_mac).to_ipv6_link_local().get();
let mut icmpv6_packet_buf =
router_advertisement_message(src_ip, config.local_ip, 1, false, false, 3, 4, 5);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip, config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 1);
}
#[test]
fn test_receiving_router_advertisement_fixed_message() {
let config = get_dummy_config::<Ipv6Addr>();
let mut ctx = DummyEventDispatcherBuilder::from_config(config.clone())
.build::<DummyEventDispatcher>();
let device = Some(DeviceId::new_ethernet(0));
let device_id = device.map(|x| x.id()).unwrap();
let src_ip = config.remote_mac.to_ipv6_link_local();
//
// Receive a router advertisement for a brand new router with a valid lifetime.
//
let mut icmpv6_packet_buf =
router_advertisement_message(src_ip.get(), config.local_ip, 1, false, false, 3, 4, 5);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), config.local_ip))
.unwrap();
assert!(!EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id)
.has_default_router(&src_ip));
ctx.receive_ndp_packet(device, src_ip.get(), config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 1);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
// We should have the new router in our list with our NDP parameters updated.
assert!(ndp_state.has_default_router(&src_ip));
let base = Duration::from_millis(4);
let min_reachable = base / 2;
let max_reachable = min_reachable * 3;
assert_eq!(ndp_state.base_reachable_time, base);
assert!(
ndp_state.reachable_time >= min_reachable && ndp_state.reachable_time <= max_reachable
);
assert_eq!(ndp_state.retrans_timer, Duration::from_millis(5));
assert_eq!(get_ipv6_hop_limit(&ctx, device.unwrap()).get(), 1);
//
// Receive a new router advertisement for the same router with a valid lifetime.
//
let mut icmpv6_packet_buf =
router_advertisement_message(src_ip.get(), config.local_ip, 7, false, false, 9, 10, 11);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip.get(), config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 2);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
assert!(ndp_state.has_default_router(&src_ip));
let base = Duration::from_millis(10);
let min_reachable = base / 2;
let max_reachable = min_reachable * 3;
let reachable_time = ndp_state.reachable_time;
assert_eq!(ndp_state.base_reachable_time, base);
assert!(
ndp_state.reachable_time >= min_reachable && ndp_state.reachable_time <= max_reachable
);
assert_eq!(ndp_state.retrans_timer, Duration::from_millis(11));
assert_eq!(get_ipv6_hop_limit(&ctx, device.unwrap()).get(), 7);
//
// Receive a new router advertisement for the same router with a valid lifetime and
// zero valued parameters.
//
// Zero value for Reachable Time should not update base_reachable_time.
// Other non zero values should update.
let mut icmpv6_packet_buf = router_advertisement_message(
src_ip.get(),
config.local_ip,
13,
false,
false,
15,
0,
17,
);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip.get(), config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 3);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
assert!(ndp_state.has_default_router(&src_ip));
// Should be the same value as before.
assert_eq!(ndp_state.base_reachable_time, base);
// Should be the same randomly calculated value as before.
assert_eq!(ndp_state.reachable_time, reachable_time);
// Should update to new value.
assert_eq!(ndp_state.retrans_timer, Duration::from_millis(17));
// Should update to new value.
assert_eq!(get_ipv6_hop_limit(&ctx, device.unwrap()).get(), 13);
// Zero value for Retransmit Time should not update our retrans_time.
// Other non zero values should update.
let mut icmpv6_packet_buf = router_advertisement_message(
src_ip.get(),
config.local_ip,
19,
false,
false,
21,
22,
0,
);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip.get(), config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 4);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
assert!(ndp_state.has_default_router(&src_ip));
// Should update to new value.
let base = Duration::from_millis(22);
let min_reachable = base / 2;
let max_reachable = min_reachable * 3;
assert_eq!(ndp_state.base_reachable_time, base);
assert!(
ndp_state.reachable_time >= min_reachable && ndp_state.reachable_time <= max_reachable
);
// Should be the same value as before.
assert_eq!(ndp_state.retrans_timer, Duration::from_millis(17));
// Should update to new value.
assert_eq!(get_ipv6_hop_limit(&ctx, device.unwrap()).get(), 19);
// Zero value for CurrHopLimit should not update our hop_limit.
// Other non zero values should update.
let mut icmpv6_packet_buf = router_advertisement_message(
src_ip.get(),
config.local_ip,
0,
false,
false,
27,
28,
29,
);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip.get(), config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 5);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
assert!(ndp_state.has_default_router(&src_ip));
// Should update to new value.
let base = Duration::from_millis(28);
let min_reachable = base / 2;
let max_reachable = min_reachable * 3;
assert_eq!(ndp_state.base_reachable_time, base);
assert!(
ndp_state.reachable_time >= min_reachable && ndp_state.reachable_time <= max_reachable
);
// Should update to new value.
assert_eq!(ndp_state.retrans_timer, Duration::from_millis(29));
// Should be the same value as before.
assert_eq!(get_ipv6_hop_limit(&ctx, device.unwrap()).get(), 19);
//
// Receive new router advertisement with 0 router lifetime, but new parameters.
//
let mut icmpv6_packet_buf = router_advertisement_message(
src_ip.get(),
config.local_ip,
31,
false,
false,
0,
34,
35,
);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip.get(), config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 6);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
// Router should no longer be in our list.
assert!(!ndp_state.has_default_router(&src_ip));
let base = Duration::from_millis(34);
let min_reachable = base / 2;
let max_reachable = min_reachable * 3;
let reachable_time = ndp_state.reachable_time;
assert_eq!(ndp_state.base_reachable_time, base);
assert!(
ndp_state.reachable_time >= min_reachable && ndp_state.reachable_time <= max_reachable
);
assert_eq!(ndp_state.retrans_timer, Duration::from_millis(35));
assert_eq!(get_ipv6_hop_limit(&ctx, device.unwrap()).get(), 31);
// Router invalidation timeout must have been cleared since we invalided with the
// received router advertisement with lifetime 0.
assert!(!trigger_next_timer(&mut ctx));
//
// Receive new router advertisement with non-0 router lifetime, but let it get invalidated
//
let mut icmpv6_packet_buf = router_advertisement_message(
src_ip.get(),
config.local_ip,
37,
false,
false,
39,
40,
41,
);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip.get(), config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 7);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
// Router should be re-added.
assert!(ndp_state.has_default_router(&src_ip));
let base = Duration::from_millis(40);
let min_reachable = base / 2;
let max_reachable = min_reachable * 3;
let reachable_time = ndp_state.reachable_time;
assert_eq!(ndp_state.base_reachable_time, base);
assert!(
ndp_state.reachable_time >= min_reachable && ndp_state.reachable_time <= max_reachable
);
assert_eq!(ndp_state.retrans_timer, Duration::from_millis(41));
assert_eq!(get_ipv6_hop_limit(&ctx, device.unwrap()).get(), 37);
// Invaldate the router by triggering the timeout.
assert!(trigger_next_timer(&mut ctx));
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
assert!(!ndp_state.has_default_router(&src_ip));
// No more timers.
assert!(!trigger_next_timer(&mut ctx));
}
#[test]
fn test_sending_ipv6_packet_after_hop_limit_change() {
// Sets the hop limit with a router advertisement
// and sends a packet to make sure the packet uses
// the new hop limit.
fn inner_test(ctx: &mut Context<DummyEventDispatcher>, hop_limit: u8, frame_offset: usize) {
let config = get_dummy_config::<Ipv6Addr>();
let device = Some(DeviceId::new_ethernet(0));
let device_id = device.map(|x| x.id()).unwrap();
let src_ip = config.remote_mac.to_ipv6_link_local();
let mut icmpv6_packet_buf = router_advertisement_message(
src_ip.get(),
config.local_ip,
hop_limit,
false,
false,
0,
0,
0,
);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), config.local_ip))
.unwrap();
assert!(!EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id)
.has_default_router(&src_ip));
ctx.receive_ndp_packet(device, src_ip.get(), config.local_ip, icmpv6_packet);
assert_eq!(get_ipv6_hop_limit(&ctx, device.unwrap()).get(), hop_limit);
crate::ip::send_ip_packet_from_device(
ctx,
device.unwrap(),
config.local_ip,
config.remote_ip,
config.remote_ip,
IpProto::Tcp,
Buf::new(vec![0; 10], ..),
None,
)
.unwrap();
let (buf, _, _, _) =
parse_ethernet_frame(&ctx.dispatcher().frames_sent()[frame_offset].1[..]).unwrap();
// Packet's hop limit should be 100.
assert_eq!(buf[7], hop_limit);
}
let mut ctx = DummyEventDispatcherBuilder::from_config(get_dummy_config::<Ipv6Addr>())
.build::<DummyEventDispatcher>();
// Set hop limit to 100.
inner_test(&mut ctx, 100, 0);
// Set hop limit to 30.
inner_test(&mut ctx, 30, 1);
}
#[test]
fn test_receiving_router_advertisement_source_link_layer_option() {
let config = get_dummy_config::<Ipv6Addr>();
let mut ctx = DummyEventDispatcherBuilder::from_config(config.clone())
.build::<DummyEventDispatcher>();
let device = Some(DeviceId::new_ethernet(0));
let device_id = device.map(|x| x.id()).unwrap();
let src_mac = Mac::new([10, 11, 12, 13, 14, 15]);
let src_ip = src_mac.to_ipv6_link_local();
let src_mac_bytes = src_mac.bytes();
let options = vec![NdpOption::SourceLinkLayerAddress(&src_mac_bytes[..])];
//
// First receive a Router Advertisement without the source link layer and
// make sure no new neighbor gets added.
//
let mut icmpv6_packet_buf =
router_advertisement_message(src_ip.get(), config.local_ip, 1, false, false, 3, 4, 5);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), config.local_ip))
.unwrap();
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
assert!(!ndp_state.has_default_router(&src_ip));
assert!(ndp_state.neighbors.get_neighbor_state(&src_ip).is_none());
ctx.receive_ndp_packet(device, src_ip.get(), config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 1);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
// We should have the new router in our list with our NDP parameters updated.
assert!(ndp_state.has_default_router(&src_ip));
// Should still not have a neighbor added.
assert!(ndp_state.neighbors.get_neighbor_state(&src_ip).is_none());
//
// Receive a new RA but with the source link layer option
//
let mut icmpv6_packet_buf = OptionsSerializer::new(options.iter())
.into_serializer()
.encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new(
src_ip.get(),
config.local_ip,
IcmpUnusedCode,
RouterAdvertisement::new(1, false, false, 3, 4, 5),
))
.serialize_vec_outer()
.unwrap();
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip.get(), config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 2);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
assert!(ndp_state.has_default_router(&src_ip));
let neighbor = ndp_state.neighbors.get_neighbor_state(&src_ip).unwrap();
assert_eq!(neighbor.link_address.unwrap(), src_mac);
assert!(neighbor.is_router);
// Router should be marked stale as a neighbor.
assert_eq!(neighbor.state, NeighborEntryState::Stale);
}
#[test]
fn test_receiving_router_advertisement_mtu_option() {
fn packet_buf(src_ip: Ipv6Addr, dst_ip: Ipv6Addr, mtu: u32) -> Buf<Vec<u8>> {
let options = &[NdpOption::MTU(mtu)];
OptionsSerializer::new(options.iter())
.into_serializer()
.encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new(
src_ip,
dst_ip,
IcmpUnusedCode,
RouterAdvertisement::new(1, false, false, 3, 4, 5),
))
.serialize_vec_outer()
.unwrap()
.unwrap_b()
}
let config = get_dummy_config::<Ipv6Addr>();
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let hw_mtu = 5000;
let device = ctx.state.add_ethernet_device(TEST_LOCAL_MAC, hw_mtu);
let device_id = device.id();
let src_mac = Mac::new([10, 11, 12, 13, 14, 15]);
let src_ip = src_mac.to_ipv6_link_local();
crate::device::initialize_device(&mut ctx, device);
//
// Receive a new RA with a valid mtu option (but the new mtu should only be 5000
// as that is the max MTU of the device).
//
let mut icmpv6_packet_buf = packet_buf(src_ip.get(), config.local_ip, 5781);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), config.local_ip))
.unwrap();
ctx.receive_ndp_packet(Some(device), src_ip.get(), config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 1);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
assert!(ndp_state.has_default_router(&src_ip));
assert_eq!(get_mtu(ctx.state(), device), hw_mtu);
//
// Receive a new RA with an invalid MTU option (value is lower than IPv6 min mtu)
//
let mut icmpv6_packet_buf =
packet_buf(src_ip.get(), config.local_ip, crate::ip::path_mtu::IPV6_MIN_MTU - 1);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), config.local_ip))
.unwrap();
ctx.receive_ndp_packet(Some(device), src_ip.get(), config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 2);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
assert!(ndp_state.has_default_router(&src_ip));
assert_eq!(get_mtu(ctx.state(), device), hw_mtu);
//
// Receive a new RA with a valid MTU option (value is exactly IPv6 min mtu)
//
let mut icmpv6_packet_buf =
packet_buf(src_ip.get(), config.local_ip, crate::ip::path_mtu::IPV6_MIN_MTU);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip.get(), config.local_ip))
.unwrap();
ctx.receive_ndp_packet(Some(device), src_ip.get(), config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 3);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
assert!(ndp_state.has_default_router(&src_ip));
assert_eq!(get_mtu(ctx.state(), device), crate::ip::path_mtu::IPV6_MIN_MTU);
}
#[test]
fn test_receiving_router_advertisement_prefix_option() {
fn packet_buf(
src_ip: Ipv6Addr,
dst_ip: Ipv6Addr,
prefix: Ipv6Addr,
prefix_length: u8,
on_link_flag: bool,
autonomous_address_configuration_flag: bool,
valid_lifetime: u32,
preferred_lifetime: u32,
) -> Buf<Vec<u8>> {
let p = PrefixInformation::new(
prefix_length,
on_link_flag,
autonomous_address_configuration_flag,
valid_lifetime,
preferred_lifetime,
prefix,
);
let options = &[NdpOption::PrefixInformation(&p)];
OptionsSerializer::new(options.iter())
.into_serializer()
.encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new(
src_ip,
dst_ip,
IcmpUnusedCode,
RouterAdvertisement::new(1, false, false, 0, 4, 5),
))
.serialize_vec_outer()
.unwrap()
.unwrap_b()
}
let config = get_dummy_config::<Ipv6Addr>();
let mut ctx = DummyEventDispatcherBuilder::from_config(config.clone())
.build::<DummyEventDispatcher>();
let device = DeviceId::new_ethernet(0);
let device_id = device.id();
let src_mac = Mac::new([10, 11, 12, 13, 14, 15]);
let src_ip = src_mac.to_ipv6_link_local().get();
let prefix = Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 0]);
let prefix_length = 120;
let addr_subnet = AddrSubnet::new(prefix, prefix_length).unwrap();
//
// Receive a new RA with new prefix.
//
let mut icmpv6_packet_buf =
packet_buf(src_ip, config.local_ip, prefix, prefix_length, true, false, 100, 0);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, config.local_ip))
.unwrap();
ctx.receive_ndp_packet(Some(device), src_ip, config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 1);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
// Prefix should be in our list now.
assert!(ndp_state.has_prefix(&addr_subnet));
// Invalidation timeout should be set.
assert_eq!(ctx.dispatcher().timer_events().count(), 1);
//
// Receive a RA with same prefix but valid_lifetime = 0;
//
let mut icmpv6_packet_buf =
packet_buf(src_ip, config.local_ip, prefix, prefix_length, true, false, 0, 0);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, config.local_ip))
.unwrap();
ctx.receive_ndp_packet(Some(device), src_ip, config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 2);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
// Should remove the prefix from our list now.
assert!(!ndp_state.has_prefix(&addr_subnet));
// Invalidation timeout should be unset.
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
//
// Receive a new RA with new prefix (same as before but new since it isn't in our list
// right now).
//
let mut icmpv6_packet_buf =
packet_buf(src_ip, config.local_ip, prefix, prefix_length, true, false, 100, 0);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, config.local_ip))
.unwrap();
ctx.receive_ndp_packet(Some(device), src_ip, config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 3);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
// Prefix should be in our list now.
assert!(ndp_state.has_prefix(&addr_subnet));
// Invalidation timeout should be set.
assert_eq!(ctx.dispatcher().timer_events().count(), 1);
//
// Receive the exact same RA as before.
//
let mut icmpv6_packet_buf =
packet_buf(src_ip, config.local_ip, prefix, prefix_length, true, false, 100, 0);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, config.local_ip))
.unwrap();
ctx.receive_ndp_packet(Some(device), src_ip, config.local_ip, icmpv6_packet);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 4);
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
// Prefix should be in our list still.
assert!(ndp_state.has_prefix(&addr_subnet));
// Invalidation timeout should still be set.
assert_eq!(ctx.dispatcher().timer_events().count(), 1);
//
// Timeout the prefix.
//
assert!(trigger_next_timer(&mut ctx));
// Prefix should no longer be in our list.
let ndp_state = EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device_id);
assert!(!ndp_state.has_prefix(&addr_subnet));
// No more timers.
assert!(!trigger_next_timer(&mut ctx));
}
#[test]
fn test_host_send_router_solicitations() {
fn validate_params(
src_mac: Mac,
src_ip: Ipv6Addr,
message: RouterSolicitation,
code: IcmpUnusedCode,
) {
let dummy_config = get_dummy_config::<Ipv6Addr>();
assert_eq!(src_mac, dummy_config.local_mac);
assert_eq!(src_ip, dummy_config.local_mac.to_ipv6_link_local().get());
assert_eq!(message, RouterSolicitation::default());
assert_eq!(code, IcmpUnusedCode);
}
//
// By default, we should send `MAX_RTR_SOLICITATIONS` number of Router
// Solicitation messages.
//
let dummy_config = get_dummy_config::<Ipv6Addr>();
let mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = crate::device::ndp::NdpConfigurations::default();
ndp_configs.set_dup_addr_detect_transmits(None);
stack_builder.device_builder().set_default_ndp_configs(ndp_configs);
let mut ctx = Context::new(stack_builder.build(), DummyEventDispatcher::default());
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
let device_id = ctx.state.add_ethernet_device(dummy_config.local_mac, IPV6_MIN_MTU);
crate::device::initialize_device(&mut ctx, device_id);
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
let time = ctx.dispatcher().now();
assert!(trigger_next_timer(&mut ctx));
// Initial router solicitation should be a random delay between 0 and
// `MAX_RTR_SOLICITATION_DELAY`.
assert!(ctx.dispatcher().now().duration_since(time) < MAX_RTR_SOLICITATION_DELAY);
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
let (src_mac, dst_mac, src_ip, dst_ip, message, code) =
parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv6, _, RouterSolicitation, _>(
&ctx.dispatcher().frames_sent()[0].1,
|_| {},
)
.unwrap();
validate_params(src_mac, src_ip, message, code);
// Should get 2 more router solicitation messages
let time = ctx.dispatcher().now();
assert!(trigger_next_timer(&mut ctx));
assert_eq!(ctx.dispatcher().now().duration_since(time), RTR_SOLICITATION_INTERVAL);
let (src_mac, dst_mac, src_ip, dst_ip, message, code) =
parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv6, _, RouterSolicitation, _>(
&ctx.dispatcher().frames_sent()[1].1,
|_| {},
)
.unwrap();
validate_params(src_mac, src_ip, message, code);
// Before the next one, lets assign an IP address (DAD won't be performed so
// it will be assigned immediately. The router solicitation message will
// now use the new assigned IP as the source.
set_ip_addr_subnet(
&mut ctx,
device_id,
AddrSubnet::new(dummy_config.local_ip, 128).unwrap(),
);
let time = ctx.dispatcher().now();
assert!(trigger_next_timer(&mut ctx));
assert_eq!(ctx.dispatcher().now().duration_since(time), RTR_SOLICITATION_INTERVAL);
let (src_mac, dst_mac, src_ip, dst_ip, message, code) =
parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv6, _, RouterSolicitation, _>(
&ctx.dispatcher().frames_sent()[2].1,
|p| {
// We should have a source link layer option now because we have a
// source ip address set.
assert_eq!(p.body().iter().count(), 1);
if let Some(ll) = get_source_link_layer_option::<EthernetNdpDevice, _>(p.body())
{
assert_eq!(ll, dummy_config.local_mac);
} else {
panic!("Should have a source link layer option");
}
},
)
.unwrap();
assert_eq!(src_mac, dummy_config.local_mac);
assert_eq!(src_ip, dummy_config.local_ip);
assert_eq!(message, RouterSolicitation::default());
assert_eq!(code, IcmpUnusedCode);
// No more timers.
assert!(!trigger_next_timer(&mut ctx));
// Should have only sent 3 packets (Router solicitations).
assert_eq!(ctx.dispatcher().frames_sent().len(), 3);
//
// Configure MAX_RTR_SOLICITATIONS in the stack.
//
let mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = crate::device::ndp::NdpConfigurations::default();
ndp_configs.set_dup_addr_detect_transmits(None);
ndp_configs.set_max_router_solicitations(NonZeroU8::new(2));
stack_builder.device_builder().set_default_ndp_configs(ndp_configs);
let mut ctx = Context::new(stack_builder.build(), DummyEventDispatcher::default());
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
let device_id = ctx.state.add_ethernet_device(dummy_config.local_mac, IPV6_MIN_MTU);
crate::device::initialize_device(&mut ctx, device_id);
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
let time = ctx.dispatcher().now();
assert!(trigger_next_timer(&mut ctx));
// Initial router solicitation should be a random delay between 0 and
// `MAX_RTR_SOLICITATION_DELAY`.
assert!(ctx.dispatcher().now().duration_since(time) < MAX_RTR_SOLICITATION_DELAY);
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
// Should trigger 1 more router solicitations
let time = ctx.dispatcher().now();
assert!(trigger_next_timer(&mut ctx));
assert_eq!(ctx.dispatcher().now().duration_since(time), RTR_SOLICITATION_INTERVAL);
assert_eq!(ctx.dispatcher().frames_sent().len(), 2);
// Each packet would be the same.
for f in ctx.dispatcher().frames_sent() {
let (src_mac, dst_mac, src_ip, dst_ip, message, code) =
parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv6, _, RouterSolicitation, _>(
&f.1,
|_| {},
)
.unwrap();
validate_params(src_mac, src_ip, message, code);
}
// No more timers.
assert!(!trigger_next_timer(&mut ctx));
}
#[test]
fn test_router_solicitation_on_routing_enabled_changes() {
//
// Make sure that when an interface goes from host -> router, it stops sending Router
// Solicitations, and starts sending them when it goes form router -> host as routers
// should not send Router Solicitation messages, but hosts should.
//
let dummy_config = get_dummy_config::<Ipv6Addr>();
//
// If netstack is not set to forward packets, make sure router solicitations do not get
// cancelled when we enable forwading on the device.
//
let mut state_builder = StackStateBuilder::default();
state_builder.ip_builder().forward(false);
let mut ndp_configs = NdpConfigurations::default();
ndp_configs.set_dup_addr_detect_transmits(None);
state_builder.device_builder().set_default_ndp_configs(ndp_configs);
let mut ctx = DummyEventDispatcherBuilder::default()
.build_with(state_builder, DummyEventDispatcher::default());
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
let device = ctx.state.add_ethernet_device(dummy_config.local_mac, IPV6_MIN_MTU);
crate::device::initialize_device(&mut ctx, device);
let timer_id =
NdpTimerId::new_router_solicitation_timer_id::<EthernetNdpDevice>(device.id());
// Send the first router solicitation.
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
let timers: Vec<(&DummyInstant, &TimerId)> =
ctx.dispatcher().timer_events().filter(|x| *x.1 == timer_id).collect();
assert_eq!(timers.len(), 1);
assert!(trigger_next_timer(&mut ctx));
// Should have sent a router solicitation and still have the timer setup.
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
let (src_mac, dst_mac, src_ip, dst_ip, message, code) =
parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv6, _, RouterSolicitation, _>(
&ctx.dispatcher().frames_sent()[0].1,
|_| {},
)
.unwrap();
let timers: Vec<(&DummyInstant, &TimerId)> =
ctx.dispatcher().timer_events().filter(|x| *x.1 == timer_id).collect();
assert_eq!(timers.len(), 1);
// Capture the instant when the timer was supposed to fire so we can make sure that a new
// timer doesn't replace the current one.
let instant = timers[0].0.clone();
// Enable forwarding on device.
set_forwarding_enabled::<_, Ipv6>(&mut ctx, device, true);
assert!(is_forwarding_enabled::<_, Ipv6>(&ctx, device));
// Should have not send any new packets and still have the original router solicitation
// timer set.
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
let timers: Vec<(&DummyInstant, &TimerId)> =
ctx.dispatcher().timer_events().filter(|x| *x.1 == timer_id).collect();
assert_eq!(timers.len(), 1);
assert_eq!(*timers[0].0, instant);
//
// Now make the netstack and a device actually forwarding capable.
//
let mut state_builder = StackStateBuilder::default();
state_builder.ip_builder().forward(true);
let mut ndp_configs = NdpConfigurations::default();
ndp_configs.set_dup_addr_detect_transmits(None);
state_builder.device_builder().set_default_ndp_configs(ndp_configs);
let mut ctx = DummyEventDispatcherBuilder::default()
.build_with(state_builder, DummyEventDispatcher::default());
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
let device = ctx.state.add_ethernet_device(dummy_config.local_mac, IPV6_MIN_MTU);
crate::device::initialize_device(&mut ctx, device);
let timer_id =
NdpTimerId::new_router_solicitation_timer_id::<EthernetNdpDevice>(device.id());
// Send the first router solicitation.
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
let timers: Vec<(&DummyInstant, &TimerId)> =
ctx.dispatcher().timer_events().filter(|x| *x.1 == timer_id).collect();
assert_eq!(timers.len(), 1);
assert!(trigger_next_timer(&mut ctx));
// Should have sent a frame and have a router solicitation timer setup.
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
let (src_mac, dst_mac, src_ip, dst_ip, message, code) =
parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv6, _, RouterSolicitation, _>(
&ctx.dispatcher().frames_sent()[0].1,
|_| {},
)
.unwrap();
assert_eq!(ctx.dispatcher().timer_events().filter(|x| *x.1 == timer_id).count(), 1);
// Enable forwarding on the device.
set_forwarding_enabled::<_, Ipv6>(&mut ctx, device, true);
assert!(is_forwarding_enabled::<_, Ipv6>(&ctx, device));
// Should have not sent any new packets, but unset the router solicitation timer.
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
assert_eq!(ctx.dispatcher().timer_events().filter(|x| *x.1 == timer_id).count(), 0);
// Unsetting routing should succeed.
set_forwarding_enabled::<_, Ipv6>(&mut ctx, device, false);
assert!(!is_forwarding_enabled::<_, Ipv6>(&ctx, device));
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
let timers: Vec<(&DummyInstant, &TimerId)> =
ctx.dispatcher().timer_events().filter(|x| *x.1 == timer_id).collect();
assert_eq!(timers.len(), 1);
// Send the first router solicitation after being turned into a host.
assert!(trigger_next_timer(&mut ctx));
// Should have sent a router solicitation.
assert_eq!(ctx.dispatcher().frames_sent().len(), 2);
let (src_mac, dst_mac, src_ip, dst_ip, message, code) =
parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv6, _, RouterSolicitation, _>(
&ctx.dispatcher().frames_sent()[1].1,
|_| {},
)
.unwrap();
assert_eq!(ctx.dispatcher().timer_events().filter(|x| *x.1 == timer_id).count(), 1);
}
#[test]
fn test_set_ndp_configs_dup_addr_detect_transmits() {
//
// Test that updating the DupAddrDetectTransmits parameter on an interface updates
// the number of DAD messages (NDP Neighbor Solicitations) sent before concluding
// that an address is not a duplicate.
//
let dummy_config = get_dummy_config::<Ipv6Addr>();
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device = ctx.state_mut().add_ethernet_device(dummy_config.local_mac, IPV6_MIN_MTU);
crate::device::initialize_device(&mut ctx, device);
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
// Updating the IP should resolve immediately since DAD is turned off by
// `DummyEventDispatcherBuilder::build`.
set_ip_addr_subnet(&mut ctx, device, AddrSubnet::new(dummy_config.local_ip, 128).unwrap());
assert_eq!(
EthernetNdpDevice::ipv6_addr_state(ctx.state(), device.id(), &dummy_config.local_ip),
AddressState::Assigned
);
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
// Enable DAD for the device.
let mut ndp_configs = crate::device::get_ndp_configurations(&ctx, device).clone();
ndp_configs.set_dup_addr_detect_transmits(NonZeroU8::new(3));
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs.clone());
// Updating the IP should start the DAD process.
set_ip_addr_subnet(&mut ctx, device, AddrSubnet::new(dummy_config.remote_ip, 128).unwrap());
assert_eq!(
EthernetNdpDevice::ipv6_addr_state(ctx.state(), device.id(), &dummy_config.local_ip),
AddressState::Unassigned
);
assert_eq!(
EthernetNdpDevice::ipv6_addr_state(ctx.state(), device.id(), &dummy_config.remote_ip),
AddressState::Tentative
);
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
assert_eq!(ctx.dispatcher().timer_events().count(), 1);
// Disable DAD during DAD.
ndp_configs.set_dup_addr_detect_transmits(None);
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs.clone());
// Allow aleady started DAD to complete (2 more more NS, 3 more timers).
assert!(trigger_next_timer(&mut ctx));
assert_eq!(ctx.dispatcher().frames_sent().len(), 2);
assert!(trigger_next_timer(&mut ctx));
assert_eq!(ctx.dispatcher().frames_sent().len(), 3);
assert!(trigger_next_timer(&mut ctx));
assert_eq!(ctx.dispatcher().frames_sent().len(), 3);
assert_eq!(
EthernetNdpDevice::ipv6_addr_state(ctx.state(), device.id(), &dummy_config.remote_ip),
AddressState::Assigned
);
// Updating the IP should resolve immediately since DAD has just been turned off.
set_ip_addr_subnet(&mut ctx, device, AddrSubnet::new(dummy_config.local_ip, 128).unwrap());
assert_eq!(
EthernetNdpDevice::ipv6_addr_state(ctx.state(), device.id(), &dummy_config.remote_ip),
AddressState::Unassigned
);
assert_eq!(
EthernetNdpDevice::ipv6_addr_state(ctx.state(), device.id(), &dummy_config.local_ip),
AddressState::Assigned
);
}
#[test]
#[should_panic]
fn test_send_router_advertisement_as_non_router_panic() {
//
// Attempting to send a router advertisement when a device is not a router should result in
// a panic.
//
let dummy_config = get_dummy_config::<Ipv6Addr>();
let mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = crate::device::ndp::NdpConfigurations::default();
ndp_configs.set_dup_addr_detect_transmits(None);
ndp_configs.set_max_router_solicitations(None);
stack_builder.device_builder().set_default_ndp_configs(ndp_configs);
stack_builder.ip_builder().forward(true);
let mut ctx = Context::new(stack_builder.build(), DummyEventDispatcher::default());
let device = ctx.state_mut().add_ethernet_device(TEST_LOCAL_MAC, IPV6_MIN_MTU);
crate::device::initialize_device(&mut ctx, device);
// Enable sending router advertisements (`device`) is still not a router though.
EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device.id())
.configs
.router_configurations
.set_should_send_advertisements(true);
// Should panic since `device` is not a router.
send_router_advertisement::<_, EthernetNdpDevice>(
&mut ctx,
device.id(),
Ipv6::ALL_NODES_LINK_LOCAL_ADDRESS,
);
}
#[test]
#[should_panic]
fn test_send_router_advertisement_with_should_send_advertisement_unset_panic() {
//
// Attempting to send a router advertisements when it is configured to not do so should
// result in a panic.
//
let dummy_config = get_dummy_config::<Ipv6Addr>();
let mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = crate::device::ndp::NdpConfigurations::default();
ndp_configs.set_dup_addr_detect_transmits(None);
ndp_configs.set_max_router_solicitations(None);
stack_builder.device_builder().set_default_ndp_configs(ndp_configs);
stack_builder.ip_builder().forward(true);
let mut ctx = Context::new(stack_builder.build(), DummyEventDispatcher::default());
let device = ctx.state_mut().add_ethernet_device(TEST_LOCAL_MAC, IPV6_MIN_MTU);
crate::device::initialize_device(&mut ctx, device);
// Make `device` a router (netstack is configured to forward packets already).
crate::device::set_forwarding_enabled::<_, Ipv6>(&mut ctx, device, true);
// Should panic since sending router advertisements is disabled.
send_router_advertisement::<_, EthernetNdpDevice>(
&mut ctx,
device.id(),
Ipv6::ALL_NODES_LINK_LOCAL_ADDRESS,
);
}
#[test]
fn test_sending_router_advertisement() {
//
// Test that valid router advertisements are sent based on the device's
// NDP router configurations (`NdpRouterConfigurations`).
//
let dummy_config = get_dummy_config::<Ipv6Addr>();
let mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = crate::device::ndp::NdpConfigurations::default();
ndp_configs.set_dup_addr_detect_transmits(None);
ndp_configs.set_max_router_solicitations(None);
stack_builder.device_builder().set_default_ndp_configs(ndp_configs);
stack_builder.ip_builder().forward(true);
let mut ctx = Context::new(stack_builder.build(), DummyEventDispatcher::default());
let device = ctx.state_mut().add_ethernet_device(TEST_LOCAL_MAC, IPV6_MIN_MTU);
crate::device::initialize_device(&mut ctx, device);
// Make `device` a router (netstack is configured to forward packets already).
crate::device::set_forwarding_enabled::<_, Ipv6>(&mut ctx, device, true);
// Enable and send router advertisements.
EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device.id())
.configs
.router_configurations
.set_should_send_advertisements(true);
send_router_advertisement::<_, EthernetNdpDevice>(
&mut ctx,
device.id(),
Ipv6::ALL_NODES_LINK_LOCAL_ADDRESS,
);
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
let (src_mac, dst_mac, src_ip, dst_ip, message, code) =
parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv6, _, RouterAdvertisement, _>(
&ctx.dispatcher().frames_sent()[0].1,
|p| {
assert_eq!(p.body().iter().count(), 1);
assert!(p
.body()
.iter()
.any(|x| x == NdpOption::SourceLinkLayerAddress(&TEST_LOCAL_MAC.bytes())));
},
)
.unwrap();
assert_eq!(src_ip, TEST_LOCAL_MAC.to_ipv6_link_local().get());
assert_eq!(dst_ip, Ipv6::ALL_NODES_LINK_LOCAL_ADDRESS);
assert_eq!(code, IcmpUnusedCode);
assert_eq!(message, RouterAdvertisement::new(64, false, false, 1800, 0, 0));
// Set new values for a new router advertisement.
let mut router_configs =
&mut EthernetNdpDevice::get_ndp_state_mut(ctx.state_mut(), device.id())
.configs
.router_configurations;
router_configs.set_advertised_managed_flag(true);
router_configs.set_advertised_link_mtu(NonZeroU32::new(1500));
router_configs.set_advertised_reachable_time(50);
router_configs.set_advertised_retransmit_timer(200);
router_configs.set_advertised_current_hop_limit(75);
router_configs.set_advertised_default_lifetime(NonZeroU16::new(2000));
let prefix1 = PrefixInformation::new(
64,
true,
false,
500,
400,
Ipv6Addr::new([51, 52, 53, 54, 55, 56, 57, 58, 0, 0, 0, 0, 0, 0, 0, 0]),
);
let prefix2 = PrefixInformation::new(
70,
false,
true,
501,
401,
Ipv6Addr::new([51, 52, 53, 54, 55, 56, 57, 59, 0, 0, 0, 0, 0, 0, 0, 0]),
);
router_configs.set_advertised_prefix_list(vec![prefix1.clone(), prefix2.clone()]);
send_router_advertisement::<_, EthernetNdpDevice>(
&mut ctx,
device.id(),
Ipv6::ALL_NODES_LINK_LOCAL_ADDRESS,
);
assert_eq!(ctx.dispatcher().frames_sent().len(), 2);
let (src_mac, dst_mac, src_ip, dst_ip, message, code) =
parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv6, _, RouterAdvertisement, _>(
&ctx.dispatcher().frames_sent()[1].1,
|p| {
assert_eq!(p.body().iter().count(), 4);
let mtu = 1500;
assert!(p.body().iter().any(|x| x == NdpOption::MTU(mtu)));
assert!(p.body().iter().any(|x| x == NdpOption::PrefixInformation(&prefix1)));
assert!(p.body().iter().any(|x| x == NdpOption::PrefixInformation(&prefix2)));
assert!(p
.body()
.iter()
.any(|x| x == NdpOption::SourceLinkLayerAddress(&TEST_LOCAL_MAC.bytes())));
},
)
.unwrap();
assert_eq!(src_ip, TEST_LOCAL_MAC.to_ipv6_link_local().get());
assert_eq!(dst_ip, Ipv6::ALL_NODES_LINK_LOCAL_ADDRESS);
assert_eq!(code, IcmpUnusedCode);
assert_eq!(message, RouterAdvertisement::new(75, true, false, 2000, 50, 200));
}
}