blob: 0f59fe87d37bebbc4ea6c98c2dc45b15bb536f11 [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 alloc::collections::{HashMap, HashSet};
use alloc::vec::Vec;
use core::fmt::Debug;
use core::marker::PhantomData;
use core::num::{NonZeroU16, NonZeroU32, NonZeroU8};
use core::ops::RangeInclusive;
use core::time::Duration;
use log::{debug, error, trace};
use net_types::ip::{AddrSubnet, Ip, IpAddress, Ipv6, Ipv6Addr, Subnet};
use net_types::{
LinkLocalAddr, LinkLocalAddress, MulticastAddr, MulticastAddress, SpecifiedAddr,
SpecifiedAddress, Witness,
};
use packet::{EmptyBuf, InnerPacketBuilder, Serializer};
use packet_formats::icmp::ndp::options::{NdpOption, PrefixInformation, INFINITE_LIFETIME};
use packet_formats::icmp::ndp::{
self, NeighborAdvertisement, NeighborSolicitation, Options, RouterAdvertisement,
RouterSolicitation,
};
use packet_formats::icmp::{ndp::NdpPacket, IcmpMessage, IcmpPacketBuilder, IcmpUnusedCode};
use packet_formats::ip::IpProto;
use packet_formats::ipv6::Ipv6PacketBuilder;
use rand::{thread_rng, Rng};
use zerocopy::ByteSlice;
use crate::context::{CounterContext, InstantContext, RngContext, StateContext, TimerContext};
use crate::device::link::{LinkAddress, LinkDevice};
use crate::device::{
AddressConfigurationType, AddressEntry, AddressError, AddressState, DeviceIdContext, Tentative,
};
use crate::Instant;
const ZERO_DURATION: Duration = Duration::from_secs(0);
/// The IP packet hop limit for all NDP packets.
///
/// See [RFC 4861 section 4.1], [RFC 4861 section 4.2], [RFC 4861 section 4.2],
/// [RFC 4861 section 4.3], [RFC 4861 section 4.4], and [RFC 4861 section 4.5]
/// for more information.
///
/// [RFC 4861 section 4.1]: https://tools.ietf.org/html/rfc4861#section-4.1
/// [RFC 4861 section 4.2]: https://tools.ietf.org/html/rfc4861#section-4.2
/// [RFC 4861 section 4.3]: https://tools.ietf.org/html/rfc4861#section-4.3
/// [RFC 4861 section 4.4]: https://tools.ietf.org/html/rfc4861#section-4.4
/// [RFC 4861 section 4.5]: https://tools.ietf.org/html/rfc4861#section-4.5
const REQUIRED_NDP_IP_PACKET_HOP_LIMIT: u8 = 255;
//
// 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;
/// Minimum Valid Lifetime value to actually update an address's valid lifetime.
///
/// 2 hours.
const MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE: Duration = Duration::from_secs(7200);
/// Required prefix length for SLAAC.
///
/// We need 64 bits in the prefix because the interface identifier is 64 bits,
/// and IPv6 addresses are 128 bits.
const REQUIRED_PREFIX_BITS: u8 = 64;
//
// 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);
//
// Router Constants
//
/// The number of initial Router Advertisements sent from a device when it transitions into an
/// advertising interface where the randomly chosen delay before sending the next Router
/// Advertisement SHOULD be saturated at `MAX_INITIAL_RTR_ADVERT_INTERVAL`.
///
/// See [RFC 4861 section 6.3.4] and [RFC 4861 section 10] for more details.
///
/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
/// [RFC 4861 section 6.3.4]: https://tools.ietf.org/html/rfc4861#section-6.3.4
const MAX_INITIAL_RTR_ADVERTISEMENTS: u64 = 3;
/// The maximum delay before sending the next Router Advertisement for the first
/// `MAX_INITIAL_RTR_ADVERTISEMENTS` Router Advertisements when an interface transitions into an
/// advertising interface.
///
/// See [RFC 4861 section 6.3.4] and [RFC 4861 section 10] for more details.
///
/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
/// [RFC 4861 section 6.3.4]: https://tools.ietf.org/html/rfc4861#section-6.3.4
const MAX_INITIAL_RTR_ADVERT_INTERVAL: Duration = Duration::from_secs(16);
/// The number of final Router Advertisements sent from a device when it transitions into a
/// non-advertising interface. The Router Lifetime field in these Router Advertisements will
/// be set to 0 to let hosts know not to use the router as a default router.
///
/// See See [RFC 4861 section 6.2.5] and [RFC 4861 section 10] for more details.
///
/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
/// [RFC 4861 section 6.3.5]: https://tools.ietf.org/html/rfc4861#section-6.2.5
// This call to `new_unchecked` is okay becase 3 is non-zero, so we will not be violating
// `NonZeroU8`'s contract.
const MAX_FINAL_RTR_ADVERTISEMENTS: Option<NonZeroU8> =
unsafe { Some(NonZeroU8::new_unchecked(3)) };
/// The minimum delay for sending a Router Advertisement message in response to a Router
/// Solicitation.
const MIN_RA_DELAY_TIME: Duration = Duration::from_millis(1);
/// The maximum delay for sending a Router Advertisement message in response to a Router
/// Solicitation.
///
/// See [RFC 4861 section 6.2.6] and [RFC 4861 section 10] for more details.
///
/// [RFC 4861 section 6.2.6]: https://tools.ietf.org/html/rfc4861#section-6.2.6
/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
const MAX_RA_DELAY_TIME: Duration = Duration::from_millis(500);
/// The minimum delay between Router Advertisements sent to the IPv6 all nodes multicast address.
///
/// See [RFC 4861 section 6.2.6] and [RFC 4861 section 10] for more details.
///
/// [RFC 4861 section 6.2.6]: https://tools.ietf.org/html/rfc4861#section-6.2.6
/// [RFC 4861 section 10]: https://tools.ietf.org/html/rfc4861#section-10
const MIN_DELAY_BETWEEN_RAS: Duration = Duration::from_secs(3);
// NOTE(joshlf): The `LinkDevice` parameter may seem unnecessary. We only ever
// use the associated `Address` type, so why not just take that directly? By the
// same token, why have it as a parameter on `NdpState` and `NdpTimerId`? The
// answer is that, if we did, there would be no way to distinguish between
// different link device protocols that all happened to use the same hardware
// addressing scheme.
//
// Consider that the way that we implement context traits is via blanket impls.
// Even though each module's code _feels_ isolated from the rest of the system,
// in reality, all context impls end up on the same context type. In particular,
// all impls are of the form `impl<C: SomeContextTrait> SomeOtherContextTrait
// for C`. The `C` is the same throughout the whole stack.
//
// Thus, for two different link device protocols with the same hardware address
// type, if we used an `LinkAddress` parameter rather than a `LinkDevice`
// parameter, the `NdpContext` impls would conflict (in fact, the `StateContext`
// and `TimerContext` impls would conflict for similar reasons).
/// An NDP handler for NDP Events.
///
/// `NdpHandler<D>` is implemented for any type which implements [`NdpContext<D>`],
/// and it can also be mocked for use in testing.
pub(crate) trait NdpHandler<D: LinkDevice>: DeviceIdContext<D> {
/// Cleans up state associated with the device.
///
/// The contract is that after `deinitialize` is called, nothing else should be done
/// with the state.
fn deinitialize(&mut self, device_id: Self::DeviceId);
/// 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.
fn set_configurations(&mut self, device_id: Self::DeviceId, configs: NdpConfigurations);
/// Gets the NDP Configurations for a `device_id`.
fn get_configurations(&self, device_id: Self::DeviceId) -> &NdpConfigurations;
/// 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.
///
/// # Panics
///
/// Panics if DAD is already being performed on this address.
fn start_duplicate_address_detection(
&mut self,
device_id: Self::DeviceId,
tentative_addr: Ipv6Addr,
);
/// 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`.
fn cancel_duplicate_address_detection(
&mut self,
device_id: Self::DeviceId,
tentative_addr: Ipv6Addr,
);
/// Start soliciting routers.
///
/// Does nothing if a device's MAX_RTR_SOLICITATIONS parameters is `0`.
///
/// # Panics
///
/// Panics if we attempt to start router solicitation as a router, or if
/// the device is already soliciting routers.
fn start_soliciting_routers(&mut self, device_id: Self::DeviceId);
/// 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).
fn stop_soliciting_routers(&mut self, device_id: Self::DeviceId);
/// Handle `device_id` becoming an advertising interface.
///
/// # Panics
///
/// Panics if `device_id` does not have an assigned (non-tentative) link-local address or if it is
/// not configured to be an advertising interface.
fn start_advertising_interface(&mut self, device_id: Self::DeviceId);
/// Handle `device_id` ceasing to be an advertising interface.
///
/// # Panics
///
/// Panics if `device_id` is not operating as an advertising interface.
fn stop_advertising_interface(&mut self, device_id: Self::DeviceId);
/// Look up the link layer address.
///
/// Begins the address resolution process if the link layer address
/// for `lookup_addr` is not already known.
fn lookup(&mut self, device_id: Self::DeviceId, lookup_addr: Ipv6Addr) -> Option<D::Address>;
/// Handles a timer firing.
fn handle_timer(&mut self, id: NdpTimerId<D, Self::DeviceId>);
/// 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)]
fn insert_static_neighbor(&mut self, device_id: Self::DeviceId, net: Ipv6Addr, hw: D::Address);
}
impl<D: LinkDevice, C: NdpContext<D>> NdpHandler<D> for C
where
D::Address: for<'a> From<&'a MulticastAddr<Ipv6Addr>>,
{
fn deinitialize(&mut self, device_id: Self::DeviceId) {
deinitialize(self, device_id)
}
fn set_configurations(&mut self, device_id: Self::DeviceId, configs: NdpConfigurations) {
set_ndp_configurations(self, device_id, configs)
}
fn get_configurations(&self, device_id: Self::DeviceId) -> &NdpConfigurations {
get_ndp_configurations(self, device_id)
}
fn start_duplicate_address_detection(
&mut self,
device_id: Self::DeviceId,
tentative_addr: Ipv6Addr,
) {
start_duplicate_address_detection(self, device_id, tentative_addr)
}
fn cancel_duplicate_address_detection(
&mut self,
device_id: Self::DeviceId,
tentative_addr: Ipv6Addr,
) {
cancel_duplicate_address_detection(self, device_id, tentative_addr)
}
fn start_soliciting_routers(&mut self, device_id: Self::DeviceId) {
start_soliciting_routers(self, device_id)
}
fn stop_soliciting_routers(&mut self, device_id: Self::DeviceId) {
stop_soliciting_routers(self, device_id)
}
fn start_advertising_interface(&mut self, device_id: Self::DeviceId) {
start_advertising_interface(self, device_id)
}
fn stop_advertising_interface(&mut self, device_id: Self::DeviceId) {
stop_advertising_interface(self, device_id)
}
fn lookup(&mut self, device_id: Self::DeviceId, lookup_addr: Ipv6Addr) -> Option<D::Address> {
lookup(self, device_id, lookup_addr)
}
fn handle_timer(&mut self, id: NdpTimerId<D, Self::DeviceId>) {
handle_timer(self, id)
}
#[cfg(test)]
fn insert_static_neighbor(&mut self, device_id: Self::DeviceId, net: Ipv6Addr, hw: D::Address) {
insert_neighbor(self, device_id, net, hw)
}
}
/// The execution context for an NDP device.
pub(crate) trait NdpContext<D: LinkDevice>:
Sized
+ DeviceIdContext<D>
+ RngContext
+ CounterContext
+ StateContext<
NdpState<D, <Self as InstantContext>::Instant>,
<Self as DeviceIdContext<D>>::DeviceId,
> + TimerContext<NdpTimerId<D, <Self as DeviceIdContext<D>>::DeviceId>>
{
/// Get the link layer address for a device.
fn get_link_layer_addr(&self, device_id: Self::DeviceId) -> D::Address;
/// Get the interface identifier for a device as defined by RFC 4291 section 2.5.1.
fn get_interface_identifier(&self, device_id: Self::DeviceId) -> [u8; 8];
/// Get a 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(
&self,
device_id: Self::DeviceId,
) -> Option<Tentative<LinkLocalAddr<Ipv6Addr>>>;
/// Get a non-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.
///
/// `get_ipv6_addr` may return a non-tentative link-local address if `device_id` deosn't have
/// any non-tentative global addresses.
fn get_ipv6_addr(&self, device_id: Self::DeviceId) -> Option<Ipv6Addr>;
/// The type of the iterator returned from `get_ipv6_addr_entries`.
type AddrEntriesIter: Iterator<Item = AddressEntry<Ipv6Addr, Self::Instant>>;
/// Get all global IPv6 addresses and associated data for this device.
///
/// `get_ipv6_addr_entries` MUST return all **unicast** IPv6 addresses associated with
/// `device_id`. This will include Tentative, Deprecated and Assigned addresses configured
/// manually or via SLAAC. Violating this rule may result in incorrect IP packets being sent.
fn get_ipv6_addr_entries(&self, device_id: Self::DeviceId) -> Self::AddrEntriesIter;
/// Returns the state of `address` on the device identified
/// by `device_id` if it exists.
///
/// `address` is guaranteed to be a valid unicast address.
fn ipv6_addr_state(
&self,
device_id: Self::DeviceId,
address: &Ipv6Addr,
) -> Option<AddressState>;
// TODO(joshlf): Use `FrameContext` instead.
/// 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<S: Serializer<Buffer = EmptyBuf>>(
&mut self,
device_id: Self::DeviceId,
next_hop: Ipv6Addr,
body: S,
) -> Result<(), S>;
/// 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(
&mut self,
device_id: Self::DeviceId,
address: &Ipv6Addr,
link_address: D::Address,
);
/// Notifies the device layer that the link-layer address resolution for
/// the neighbor in `address` failed.
fn address_resolution_failed(&mut self, device_id: Self::DeviceId, 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(&mut self, device_id: Self::DeviceId, 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(&mut self, device_id: Self::DeviceId, 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 IPv6 minimum
/// link MTU, [`Ipv6::MINIMUM_LINK_MTU`].
///
/// [RFC 4861 section 6.3.4]: https://tools.ietf.org/html/rfc4861#section-6.3.4
fn set_mtu(&mut self, device_id: Self::DeviceId, 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(&mut self, device_id: Self::DeviceId, hop_limit: NonZeroU8);
/// Add a new IPv6 Global Address configured via SLAAC.
fn add_slaac_addr_sub(
&mut self,
device_id: Self::DeviceId,
addr_sub: AddrSubnet<Ipv6Addr>,
valid_until: Self::Instant,
) -> Result<(), AddressError>;
/// Deprecate the use of an address previously configured via SLAAC.
///
/// If `addr` is currently tentative on `device_id`, the address should simply
/// be invalidated as new connections should not use a deprecated address,
/// and we should have no existing connections using a tentative address.
///
/// # Panics
///
/// May panic if `addr` is not an address configured via SLAAC on `device_id`.
fn deprecate_slaac_addr(&mut self, device_id: Self::DeviceId, addr: &Ipv6Addr);
/// Completely invalidate an address previously configured via SLAAC.
///
/// # Panics
///
/// May panic if `addr` is not an address configured via SLAAC on `device_id`.
fn invalidate_slaac_addr(&mut self, device_id: Self::DeviceId, addr: &Ipv6Addr);
/// Update the instant when an address configured via SLAAC is valid until.
///
/// # Panics
///
/// May panic if `addr` is not an address configured via SLAAC on `device_id`.
fn update_slaac_addr_valid_until(
&mut self,
device_id: Self::DeviceId,
addr: &Ipv6Addr,
valid_until: Self::Instant,
);
/// 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 routing enabled; if `is_router` returns false, either `device_id` or the netstack
/// (`ctx`) has routing disabled.
fn is_router(&self, device_id: Self::DeviceId) -> bool;
/// Is `device_id` an advertising interface?
fn is_advertising_interface(&self, device_id: Self::DeviceId) -> bool {
self.is_router(device_id)
&& self
.get_state_with(device_id)
.configs
.get_router_configurations()
.get_should_send_advertisements()
}
/// Handle the case when a link-local address is resolved.
///
/// `link_local_resolved` will start sending periodic router advertisements if `device_id` is
/// configured to be an advertising interface.
fn link_local_resolved(&mut self, device_id: Self::DeviceId) {
trace!("link_local_resolved: link-local address on device {:?} resolved", device_id);
if self.is_router(device_id) {
// If the device is operating as a router, and it is configured to be an advertising
// interface, start sending periodic router advertisements.
if self
.get_state_with(device_id)
.configs
.get_router_configurations()
.get_should_send_advertisements()
{
// At this point, we know that `devie_id` is an advertising interface and
// has an assigned link-local address. Given this, we know that
// `start_advertising_interface` will not panic.
start_advertising_interface(self, device_id);
}
}
}
/// 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.
///
/// If the address was the link-local address, periodic router advertisements
/// will be started if `device_id` is an advertising interface.
///
/// # Panics
///
/// Panics if `addr` is not tentative on the devide identified by `device_id`.
fn unique_address_determined_wrapper(&mut self, device_id: Self::DeviceId, addr: Ipv6Addr) {
// Let the device-layer know that `addr` is most likely not already used
// on the link.
self.unique_address_determined(device_id, addr);
if addr.is_linklocal() {
// Here know know that we just resolved a link-local address becuase
// we just checked that `addr` is link-local and we know that
// `unique_address_determined` would have paniced if `addr` was not
// tentative on `device_id`.
self.link_local_resolved(device_id);
}
}
}
fn deinitialize<D: LinkDevice, C: NdpContext<D>>(ctx: &mut C, device_id: C::DeviceId) {
// Remove all timers associated with the device
ctx.cancel_timers_with(|timer_id| timer_id.get_device_id() == device_id);
// TODO(rheacock): Send any immediate packets, and potentially flag the state as uninitialized?
}
/// 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
/// routing 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`.
///
/// `is_final_ra_batch` is used to let `new_router_advertisement` know that the new Router
/// Advertisement is part of a batch of final router advertisements when a device ceases to be
/// an advertising interface. In this case, the Router Lifetime field will be set to 0 to inform
/// hosts that the (ex-)router is no longer to be used as a default router.
fn new_router_advertisement(&self, is_final_ra_batch: bool) -> RouterAdvertisement {
let router_lifetime = if is_final_ra_batch {
0
} else {
self.get_advertised_default_lifetime().map_or(0, |x| x.get())
};
RouterAdvertisement::new(
self.get_advertised_current_hop_limit(),
self.get_advertised_managed_flag(),
self.get_advertised_other_config_flag(),
router_lifetime,
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: LinkDevice, Instant> {
//
// NDP operation data structures.
//
/// List of neighbors.
neighbors: NeighborTable<D::Address>,
/// 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,
/// Number of remaining final Router Advertisement messages to send.
///
/// The Router Lifetime field in these Router Advertisement message will be set to 0 to let
/// hosts know not to use the router as a default route.
final_router_advertisements_remaining: u8,
/// Number of Router Advertisements sent to the IPv6 all-nodes multicast address.
///
/// Note, this value does not include Router Advertisements sent directly to a host (response to
/// a Router Solicitation message).
all_nodes_transmited_router_advertisements: u64,
/// Instant when the last Router Advertisement to the all-nodes multicast address was sent.
last_router_advertisement_instant: Option<Instant>,
//
// 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: LinkDevice, Instant> NdpState<D, Instant> {
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,
final_router_advertisements_remaining: 0,
all_nodes_transmited_router_advertisements: 0,
last_router_advertisement_instant: None,
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(&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);
// If a neighbor entry exists for the router, unmark it as a router.
if let Some(state) = self.neighbors.get_neighbor_state_mut(&ip.get()) {
state.is_router = false;
}
}
/// 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.
#[cfg(test)]
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<D: LinkDevice, DeviceId> {
device_id: DeviceId,
inner: InnerNdpTimerId,
_marker: PhantomData<D>,
}
/// 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> },
/// Timer to send a Router Advertisement.
RouterAdvertisementTransmit,
/// Timer to deprecate an address configured via SLAAC.
DeprecateSlaacAddress { addr: Ipv6Addr },
/// Timer to invalidate an address configured via SLAAC.
InvalidateSlaacAddress { addr: 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<D: LinkDevice, DeviceId: Copy> NdpTimerId<D, DeviceId> {
fn new(device_id: DeviceId, inner: InnerNdpTimerId) -> NdpTimerId<D, DeviceId> {
NdpTimerId { device_id, inner, _marker: PhantomData }
}
/// Creates a new `NdpTimerId` wrapped inside a `TimerId` with the provided
/// `device_id` and `neighbor_addr`.
pub(crate) fn new_link_address_resolution(
device_id: DeviceId,
neighbor_addr: Ipv6Addr,
) -> NdpTimerId<D, DeviceId> {
NdpTimerId::new(device_id, InnerNdpTimerId::LinkAddressResolution { neighbor_addr })
}
pub(crate) fn new_dad_ns_transmission(
device_id: DeviceId,
tentative_addr: Ipv6Addr,
) -> NdpTimerId<D, DeviceId> {
NdpTimerId::new(device_id, InnerNdpTimerId::DadNsTransmit { addr: tentative_addr })
}
pub(crate) fn new_router_solicitation(device_id: DeviceId) -> NdpTimerId<D, DeviceId> {
NdpTimerId::new(device_id, InnerNdpTimerId::RouterSolicitationTransmit)
}
pub(crate) fn new_router_invalidation(
device_id: DeviceId,
ip: LinkLocalAddr<Ipv6Addr>,
) -> NdpTimerId<D, DeviceId> {
NdpTimerId::new(device_id, InnerNdpTimerId::RouterInvalidation { ip })
}
pub(crate) fn new_prefix_invalidation(
device_id: DeviceId,
addr_subnet: AddrSubnet<Ipv6Addr>,
) -> NdpTimerId<D, DeviceId> {
NdpTimerId::new(device_id, InnerNdpTimerId::PrefixInvalidation { addr_subnet })
}
pub(crate) fn new_router_advertisement_transmit(
device_id: DeviceId,
) -> NdpTimerId<D, DeviceId> {
NdpTimerId::new(device_id, InnerNdpTimerId::RouterAdvertisementTransmit)
}
pub(crate) fn new_deprecate_slaac_address(
device_id: DeviceId,
addr: Ipv6Addr,
) -> NdpTimerId<D, DeviceId> {
NdpTimerId::new(device_id, InnerNdpTimerId::DeprecateSlaacAddress { addr })
}
pub(crate) fn new_invalidate_slaac_address(
device_id: DeviceId,
addr: Ipv6Addr,
) -> NdpTimerId<D, DeviceId> {
NdpTimerId::new(device_id, InnerNdpTimerId::InvalidateSlaacAddress { addr })
}
pub(crate) fn get_device_id(&self) -> DeviceId {
self.device_id
}
}
fn handle_timer<D: LinkDevice, C: NdpContext<D>>(ctx: &mut C, id: NdpTimerId<D, C::DeviceId>) {
match id.inner {
InnerNdpTimerId::LinkAddressResolution { neighbor_addr } => {
let ndp_state = ctx.get_state_mut_with(id.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(ctx, id.device_id, neighbor_addr);
ctx.schedule_timer(
retrans_timer,
NdpTimerId::new_link_address_resolution(id.device_id, neighbor_addr).into(),
);
} 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);
ctx.increment_counter("ndp::neighbor_solicitation_timer");
ctx.address_resolution_failed(id.device_id, &neighbor_addr);
}
} else {
unreachable!("handle_timer: 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 = ctx.get_state_mut_with(id.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
// `id.device_id`. However, we can only reach here if `addr` was
// tentative on `id.device_id` and we are performing DAD so we
// know `unique_address_determined` will not panic.
ctx.unique_address_determined_wrapper(id.device_id, addr);
} else {
do_duplicate_address_detection(ctx, id.device_id, addr);
}
}
InnerNdpTimerId::RouterSolicitationTransmit => do_router_solicitation(ctx, id.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.
ctx.get_state_mut_with(id.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.
ctx.get_state_mut_with(id.device_id).invalidate_prefix(addr_subnet);
}
InnerNdpTimerId::RouterAdvertisementTransmit => {
// Send the router advertisement to the IPv6 all-nodes multicast address.
send_router_advertisement(
ctx,
id.device_id,
Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get(),
);
// Schedule the next Router Advertisement if `id.device_id` is an
// advertising interface or it is sending its final Router
// Advertisements, as per RFC 4861 section 6.2.5.
if ctx.is_advertising_interface(id.device_id)
|| ctx.get_state_with(id.device_id).final_router_advertisements_remaining != 0
{
schedule_next_router_advertisement(ctx, id.device_id);
}
}
InnerNdpTimerId::DeprecateSlaacAddress { addr } => {
ctx.deprecate_slaac_addr(id.device_id, &addr);
}
InnerNdpTimerId::InvalidateSlaacAddress { addr } => {
ctx.invalidate_slaac_addr(id.device_id, &addr);
}
}
}
fn set_ndp_configurations<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
configs: NdpConfigurations,
) {
let existing_configs = get_ndp_configurations(ctx, device_id).clone();
// Update the configurations.
ctx.get_state_mut_with(device_id).configs = configs;
// If the device was not a router before, then it won't be a router after any NDP configuration
// change so we only check router-specific configuration changes if the device is a router.
//
// We also check to make sure the device has a non-tentative link-local address because
// if we didn't have a link-local address, we would not have started any router advertisements
// to update.
if ctx.is_router(device_id)
&& ctx.get_link_local_addr(device_id).map(|a| !a.is_tentative()).unwrap_or(false)
{
let old_rc = existing_configs.get_router_configurations();
let new_rc = get_ndp_configurations(ctx, device_id).get_router_configurations();
//
// Check if device changes advertising interfacee status (should/should't send Router
// Advertisements).
//
if !old_rc.get_should_send_advertisements() && new_rc.get_should_send_advertisements() {
// If the device is now an advertising interface, start sending Router
// Advertisements.
start_advertising_interface(ctx, device_id);
} else if old_rc.get_should_send_advertisements()
&& !new_rc.get_should_send_advertisements()
{
// If the device is now not an advertising interface, stop sending Router
// Advertisements after the final batch of Router Advertisements with Router Lifetime
// set to 0 (to inform hosts to not use this node as a default router).
stop_advertising_interface(ctx, device_id);
}
//
// Check for a change in min/max interval between sending Router Advertisements.
//
// If the time from now to the instant when the next Router Advertisement is to be sent is
// greater than the new MaxRtrAdvInterval, schedule a new Router Advertisement.
//
// Note, we intentionally ignore the case when the next Router Advertisement to be sent is
// less than the new MinRtrAdvInterval to make sure we do not unintentionally wait too long
// before sending the next Router Advertisement.
//
let old_interval = old_rc.get_router_advertisements_interval();
let new_interval = get_ndp_configurations(ctx, device_id)
.get_router_configurations()
.get_router_advertisements_interval();
if old_interval != new_interval {
// If a Router Advertisement is scheduled, make sure the time it will fire is not
// more than MaxRtrAdvInterval from now.
if let Some(next_instant) =
ctx.scheduled_instant(NdpTimerId::new_router_advertisement_transmit(device_id))
{
let now = ctx.now();
let max_instant = now
.checked_add(Duration::from_secs((*new_interval.end()).into()))
.expect("Failed to calculate new max delay before NDP RA transmission");
if next_instant > max_instant {
// Next RA will be sent too far from now. Reschedule it.
schedule_next_router_advertisement(ctx, device_id);
}
}
}
}
}
fn get_ndp_configurations<D: LinkDevice, C: NdpContext<D>>(
ctx: &C,
device_id: C::DeviceId,
) -> &NdpConfigurations {
&ctx.get_state_with(device_id).configs
}
fn lookup<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
lookup_addr: Ipv6Addr,
) -> Option<D::Address>
where
D::Address: for<'a> From<&'a MulticastAddr<Ipv6Addr>>,
{
trace!("ndp::lookup: {:?}", lookup_addr);
// If `lookup_addr` is a multicast address, get the corresponding
// destination multicast mac address.
if let Some(multicast_addr) = MulticastAddr::new(lookup_addr) {
return Some(D::Address::from(&multicast_addr));
}
// TODO(brunodalbo): Figure out what to do if a frame can't be sent
let ndpstate = ctx.get_state_mut_with(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(ctx, device_id, lookup_addr);
// Also schedule a timer to retransmit in case we don't get
// neighbor advertisements back.
ctx.schedule_timer(
retrans_timer,
NdpTimerId::new_link_address_resolution(device_id, lookup_addr).into(),
);
// 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"),
}
}
#[cfg(test)]
fn insert_neighbor<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
net: Ipv6Addr,
hw: D::Address,
) {
// Neighbor `net` should be marked as reachable.
ctx.get_state_mut_with(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,
}
}
/// Is the neighbor incomplete (waiting for address resolution)?
fn is_incomplete(&self) -> bool {
if let NeighborEntryState::Incomplete { .. } = self.state {
true
} else {
false
}
}
/// Is the neighbor reachable?
fn is_reachable(&self) -> bool {
self.state == NeighborEntryState::Reachable
}
}
/// 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,
}
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() }
}
}
fn start_advertising_interface<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
) {
trace!(
"start_advertising_interface: making device {:?} an advertising advertising interface",
device_id
);
// Reset the number of final Router Advertisements to send before ending Router Advertisements.
ctx.get_state_mut_with(device_id).final_router_advertisements_remaining = 0;
// Start sending periodic router advertisements. May panic if `device_id` does not have an
// assigned (non-tentative) link-local address or if it is not configured to be an advertising
// interface.
start_periodic_router_advertisements(ctx, device_id);
}
fn stop_advertising_interface<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
) {
trace!(
"stop_advertising_interface: making device {:?} a non-advertising advertising interface",
device_id
);
// Not having a scheduled router advertisement implies `device_id` is already not an
// advertising interface.
assert!(ctx
.scheduled_instant(NdpTimerId::new_router_advertisement_transmit(device_id).into())
.is_some());
let ndp_state = ctx.get_state_mut_with(device_id);
// If `final_router_advertisements_remaining` is non-zero, that means that `device_id`
// already ceased to be an advertising interface, meaning it isn't an advertising interface.
assert_eq!(ndp_state.final_router_advertisements_remaining, 0);
if let Some(final_ras) = MAX_FINAL_RTR_ADVERTISEMENTS {
let final_ras = final_ras.get();
trace!(
"stop_advertising_interface: device {:?} is configured to send {:?} final Router Advertisements",
device_id,
final_ras,
);
// Set the number of final Router Advertisements. We do not cancel the periodic Router
// Advertisements yet since we will use the same timers to send the final Router
// Advertisement.
ndp_state.final_router_advertisements_remaining = final_ras;
} else {
trace!(
"stop_advertising_interface: device {:?} is not configured to send any final Router Advertisements, stopping periodic Router Advertisement transmissions",
device_id,
);
// Stop sending periodic Router Advertisements. Will panic if `device_id` is not currently
// sending periodic Router Advertisements, implying it is not an advertising interface.
stop_periodic_router_advertisements(ctx, device_id);
}
}
/// Start sending periodic router advertisements.
///
/// # Panics
///
/// Panics if `device_id` does not have an assigned (non-tentative) link-local address or if it is
/// not configured to be an advertising interface.
fn start_periodic_router_advertisements<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
) {
trace!(
"ndp::start_periodic_router_advertisements: start sending router advertisements from device: {:?}",
device_id
);
// Reset the number of Router Advertisements transmited to the all-nodes multicast address.
ctx.get_state_mut_with(device_id).all_nodes_transmited_router_advertisements = 0;
schedule_next_router_advertisement(ctx, device_id);
}
/// Stop sending periodic router advertisements.
///
/// # Panics
///
/// Panics if `device_id` is not currently sending periodic Router Advertisements.
fn stop_periodic_router_advertisements<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
) {
// Cancel the next periodic router advertisement timeout.
//
// May panic if we are not currently scheduled to send a periodic router advertisements.
ctx.cancel_timer(NdpTimerId::new_router_advertisement_transmit(device_id).into()).unwrap();
}
/// Schedule next unsolicited Router Advertisement message.
///
/// `schedule_next_router_advertisement` will overwrite any existing scheduled Router Advertisement
/// transmissions.
///
/// # Panics
///
/// Panics if `device_id` is not operating as a router, if it is not configured to send Router
/// Advertisements, or if it does not have an assigned (non-tentative) link-local address.
fn schedule_next_router_advertisement<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
) {
trace!(
"ndp::schedule_next_router_advertisement: scheduling the next router advertisement for device {:?}",
device_id
);
// Calculate a random time from the interface's MinRtrAdvInterval and MaxRtrAdvInterval, as per
// RFC 4861 section 6.2.4.
let tx_range = ctx
.get_state_with(device_id)
.configs
.get_router_configurations()
.get_router_advertisements_interval();
let mut delay =
Duration::from_secs(ctx.rng().gen_range(tx_range.start(), tx_range.end()).into());
// As per RFC 6.2.4, for the first few advertisements (up to `MAX_INITIAL_RTR_ADVERTISEMENTS`)
// sent from an interface when it becomes an advertising interface, if the randomly chosen
// interval is greater than `MAX_INITIAL_RTR_ADVERT_INTERVAL`, the timer SHOULD be set to
// `MAX_INITIAL_RTR_ADVERT_INTERVAL` instead. Using a smaller interval for the initial
// advertisements increases the likelihood of a router being discovered quickly when it first
// becomes available, in the presence of possible packet loss.
//
// TODO(ghanan): Make the choice to limit the delay of the first few advertisements configurable
let ndp_state = ctx.get_state_mut_with(device_id);
if (ndp_state.all_nodes_transmited_router_advertisements < MAX_INITIAL_RTR_ADVERTISEMENTS)
&& (delay > MAX_INITIAL_RTR_ADVERT_INTERVAL)
{
trace!("schedule_next_router_advertisement: still sending the initial batch of router advertisements so limiting delay to MAX_INITIAL_RTR_ADVERT_INTERVAL ({:?})", MAX_INITIAL_RTR_ADVERT_INTERVAL);
delay = MAX_INITIAL_RTR_ADVERT_INTERVAL;
}
// Schedule the timout to send the router advertisement.
let instant =
ctx.now().checked_add(delay).expect("Failed to calculate next NDP RA transmit instant");
schedule_next_router_advertisement_instant(ctx, device_id, instant);
}
/// Schedule next unsolicited Router Advertisement message at a specific instant in time.
///
/// `schedule_next_router_advertisement_instant` will overwrite any existing scheduled Router
/// Advertisement transmissions if `instant` is before the next scheduled Router Advertisement.
/// If the next scheduled Router Advertisement is before `instant`,
/// `schedule_next_router_advertisement_instant` will not update the timer.
///
/// # Panics
///
/// Panics if `device_id` does not have an assigned (non-tentative) link-local address or if it is
/// not either an advertising interface or sending the final Router Advertisements after ceasing to
/// be an advertising interface.
fn schedule_next_router_advertisement_instant<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
instant: C::Instant,
) {
// 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 method.
let _our_link_local_addr =
ctx.get_link_local_addr(device_id).unwrap().try_into_permanent().unwrap();
// Device MUST be in one of two scenarios if we are trying to schedule a Router Advertisement.
// 1) Is an advertising interface.
// 2) Sending the final Router Advertisements after ceasing to be an advertising interface.
let is_advertising_interface = ctx.is_advertising_interface(device_id);
let ndp_state = ctx.get_state_mut_with(device_id);
let is_final_ra_batch = ndp_state.final_router_advertisements_remaining != 0;
assert!(is_final_ra_batch || is_advertising_interface);
let timer_id = NdpTimerId::new_router_advertisement_transmit(device_id).into();
// If no existing Router Advertisement transmission is scheduled, scheduled the timer at
// `instant`. If we already have a scheduled Router Advertisement, overwrite the timer if
// `instant` is at a time before the existing timer, `next_instant`.
let next_instant = ctx.scheduled_instant(timer_id);
if next_instant.map_or(true, |i| i > instant) {
trace!(
"ndp::schedule_next_router_advertisement: scheduling the next router advertisement for device {:?} at {:?}, overwriting potentially existing timer that would have fired at {:?}",
device_id, instant, next_instant,
);
// Schedule the timeout to send the router advertisement.
ctx.schedule_timer_instant(instant, timer_id);
} else {
trace!(
"ndp::schedule_next_router_advertisement: the next router advertisement for device {:?} at {:?}, is before the new one at {:?}, so doing nothing",
device_id, next_instant, instant,
);
}
}
fn start_soliciting_routers<D: LinkDevice, C: NdpContext<D>>(ctx: &mut C, device_id: C::DeviceId) {
// MUST NOT be a router.
assert!(!ctx.is_router(device_id));
let ndp_state = ctx.get_state_mut_with(device_id);
// MUST NOT already be performing router solicitation.
assert_eq!(ndp_state.router_solicitations_remaining, 0);
if let Some(v) = ndp_state.configs.max_router_solicitations {
trace!(
"ndp::start_soliciting_routers: start soliciting routers for device: {:?}",
device_id
);
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.rng().gen_range(Duration::new(0, 0), MAX_RTR_SOLICITATION_DELAY);
// MUST NOT already be performing router solicitation.
assert!(ctx
.schedule_timer(delay, NdpTimerId::new_router_solicitation(device_id).into())
.is_none());
} else {
trace!("ndp::start_soliciting_routers: device {:?} not configured to send any router solicitations", device_id);
}
}
fn stop_soliciting_routers<D: LinkDevice, C: NdpContext<D>>(ctx: &mut C, device_id: C::DeviceId) {
trace!("ndp::stop_soliciting_routers: stop soliciting routers for device: {:?}", device_id);
assert!(!ctx.is_router(device_id));
ctx.cancel_timer(NdpTimerId::new_router_solicitation(device_id).into());
// No more router solicitations remaining since we are cancelling.
ctx.get_state_mut_with(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: LinkDevice, C: NdpContext<D>>(ctx: &mut C, device_id: C::DeviceId) {
assert!(!ctx.is_router(device_id));
let ndp_state = ctx.get_state_mut_with(device_id);
let remaining = &mut ndp_state.router_solicitations_remaining;
assert!(*remaining > 0);
*remaining -= 1;
let remaining = *remaining;
let src_ip = ctx.get_ipv6_addr(device_id);
trace!(
"do_router_solicitation: soliciting routers for device {:?} using src_ip {:?}",
device_id,
src_ip
);
send_router_solicitation(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.schedule_timer(
RTR_SOLICITATION_INTERVAL,
NdpTimerId::new_router_solicitation(device_id).into(),
);
}
}
/// Send a router solicitation packet.
///
/// # Panics
///
/// Panics if we attempt to send a router solicitation as a router.
fn send_router_solicitation<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
src_ip: Option<Ipv6Addr>,
) {
assert!(!ctx.is_router(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_specified() {
// Must not include the source link layer address if the source address
// is unspecified as per RFC 4861 section 4.1.
// TODO(rheacock): Do something if this returns an error?
let _ = send_ndp_packet::<_, _, &[u8], _>(
ctx,
device_id,
src_ip,
Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS.get(),
RouterSolicitation::default(),
&[],
);
} else {
let src_ll = ctx.get_link_layer_addr(device_id);
// TODO(rheacock): Do something if this returns an error?
let _ = send_ndp_packet::<_, _, &[u8], _>(
ctx,
device_id,
src_ip,
Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS.get(),
RouterSolicitation::default(),
&[NdpOption::SourceLinkLayerAddress(src_ll.bytes())],
);
}
}
fn start_duplicate_address_detection<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
tentative_addr: Ipv6Addr,
) {
let ndp_state = ctx.get_state_mut_with(device_id);
let transmits = ndp_state.configs.dup_addr_detect_transmits;
if let Some(transmits) = transmits {
// Must not already be performing DAD on the device.
assert!(ndp_state
.dad_transmits_remaining
.insert(tentative_addr, transmits.get())
.is_none());
trace!("ndp::start_duplicate_address_detection: starting duplicate address detection for address {:?} on device {:?}", tentative_addr, device_id);
do_duplicate_address_detection(ctx, device_id, tentative_addr);
} else {
// Must not already be performing DAD on the device.
assert!(!ndp_state.dad_transmits_remaining.contains_key(&tentative_addr));
// 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, device_id);
ctx.unique_address_determined_wrapper(device_id, tentative_addr);
}
}
fn cancel_duplicate_address_detection<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
tentative_addr: Ipv6Addr,
) {
trace!("ndp::cancel_duplicate_address_detection: cancelling duplicate address detection for address {:?} on device {:?}", tentative_addr, device_id);
ctx.cancel_timer(NdpTimerId::new_dad_ns_transmission(device_id, tentative_addr).into());
// `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.
ctx.get_state_mut_with(device_id).dad_transmits_remaining.remove(&tentative_addr).unwrap();
}
/// Send another DAD message (Neighbor Solicitation).
///
/// # Panics
///
/// Panics if the DAD process has not been started for `tentative_addr` on `device_id`.
fn do_duplicate_address_detection<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
tentative_addr: Ipv6Addr,
) {
trace!("do_duplicate_address_detection: tentative_addr {:?}", tentative_addr);
let ndp_state = ctx.get_state_mut_with(device_id);
// We MUST have already started the DAD process if we reach this point.
let remaining = ndp_state.dad_transmits_remaining.get_mut(&tentative_addr).unwrap();
assert!(*remaining > 0);
*remaining -= 1;
// Uses same RETRANS_TIMER definition per RFC 4862 section-5.1
let retrans_timer = ndp_state.retrans_timer;
let src_ll = ctx.get_link_layer_addr(device_id);
// TODO(rheacock): Do something if this returns an error?
let _ = send_ndp_packet::<_, _, &[u8], _>(
ctx,
device_id,
Ipv6::UNSPECIFIED_ADDRESS,
tentative_addr.to_solicited_node_address().get(),
NeighborSolicitation::new(tentative_addr),
&[NdpOption::SourceLinkLayerAddress(src_ll.bytes())],
);
ctx.schedule_timer(
retrans_timer,
NdpTimerId::new_dad_ns_transmission(device_id, tentative_addr).into(),
);
}
fn send_neighbor_solicitation<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
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(src_ip) = ctx.get_ipv6_addr(device_id) {
assert!(src_ip.is_valid_unicast());
let src_ll = ctx.get_link_layer_addr(device_id);
let dst_ip = lookup_addr.to_solicited_node_address().get();
// TODO(rheacock): Do something if this returns an error?
let _ = send_ndp_packet::<_, _, &[u8], _>(
ctx,
device_id,
src_ip,
dst_ip,
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: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
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()));
// We must call into the higher level send_ndp_packet 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 = ctx.get_link_layer_addr(device_id);
let options = [NdpOption::TargetLinkLayerAddress(src_ll.bytes())];
// TODO(rheacock): Do something if this returns an error?
let _ = send_ndp_packet::<_, _, &[u8], _>(
ctx,
device_id,
device_addr,
dst_ip,
NeighborAdvertisement::new(ctx.is_router(device_id), solicited, false, device_addr),
&options[..],
);
}
/// 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 or if it is
/// not either an advertising interface or sending the final Router Advertisements after ceasing to
/// be an advertising interface.
fn send_router_advertisement<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
dst_ip: Ipv6Addr,
) {
// 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 = ctx
.get_link_local_addr(device_id)
.expect("cannot send router advertisement without a link-local address")
.try_into_permanent()
.expect("cannot send router advertisement with temporary link-local address");
let is_final_ra_batch =
ctx.get_state_with(device_id).final_router_advertisements_remaining != 0;
// Device MUST be in one of two scenarios if we reach this point:
// 1) Is an advertising interface.
// 2) Sending the final Router Advertisements after ceasing to be an advertising interface.
assert!(is_final_ra_batch || ctx.is_advertising_interface(device_id));
let ra_type = if is_final_ra_batch { "final batch" } else { "normal" };
trace!(
"send_router_advertisement: sending {:?} router advertisement from {:?} (dev = {:?}) to {:?}",
ra_type,
src_ip,
device_id,
dst_ip
);
let src_ll = ctx.get_link_layer_addr(device_id);
let mut options = alloc::vec![NdpOption::SourceLinkLayerAddress(src_ll.bytes())];
let ndp_state = ctx.get_state_mut_with(device_id);
let router_configurations = ndp_state.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(is_final_ra_batch);
// If `dst_ip` is the IPv6 all-nodes multicast address, increment the counter for number of
// Router Advertisements sent to the IPv6 all-nodes multicast address.
if dst_ip == Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get() {
ndp_state.all_nodes_transmited_router_advertisements += 1;
}
// Attempt to send the router advertisement message.
if send_ndp_packet::<_, _, &[u8], _>(
ctx,
device_id,
src_ip.get(),
dst_ip,
message,
&options[..],
)
.is_ok()
{
if dst_ip == Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get() {
let now = ctx.now();
let ndp_state = ctx.get_state_mut_with(device_id);
// Sent the frame successfully so update NDP state's `last_router_advertisement_instant`.
ndp_state.last_router_advertisement_instant = Some(now);
if is_final_ra_batch {
ndp_state.final_router_advertisements_remaining -= 1;
}
}
} else {
error!("send_router_advertisement: failed to send router advertisement")
};
}
/// Helper function to send ndp packet over an NdpDevice to `dst_ip`.
fn send_ndp_packet<D: LinkDevice, C: NdpContext<D>, B: ByteSlice, M>(
ctx: &mut C,
device_id: C::DeviceId,
src_ip: Ipv6Addr,
dst_ip: Ipv6Addr,
message: M,
options: &[NdpOption],
) -> Result<(), ()>
where
M: IcmpMessage<Ipv6, B, Code = IcmpUnusedCode>,
{
trace!("send_ndp_packet: src_ip={:?} dst_ip={:?}", src_ip, dst_ip);
ctx.send_ipv6_frame(
device_id,
dst_ip,
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,
REQUIRED_NDP_IP_PACKET_HOP_LIMIT,
IpProto::Icmpv6,
)),
)
.map_err(|_| ())
}
/// A handler for incoming NDP packets.
///
/// An implementation of `NdpPacketHandler` is provided by the device layer (see
/// the `crate::device` module) to the IP layer so that it can pass incoming NDP
/// packets. It can also be mocked for use in testing.
pub(crate) trait NdpPacketHandler<DeviceId> {
/// Receive an NDP packet.
fn receive_ndp_packet<B: ByteSlice>(
&mut self,
device: DeviceId,
src_ip: Ipv6Addr,
dst_ip: SpecifiedAddr<Ipv6Addr>,
packet: NdpPacket<B>,
);
}
fn duplicate_address_detected<D: LinkDevice, C: NdpContext<D>>(
ctx: &mut C,
device_id: C::DeviceId,
addr: Ipv6Addr,
) {
cancel_duplicate_address_detection(ctx, device_id, addr);
// let's notify our device
ctx.duplicate_address_detected(device_id, addr);
}
pub(crate) fn receive_ndp_packet<D: LinkDevice, C: NdpContext<D>, B>(
ctx: &mut C,
device_id: C::DeviceId,
src_ip: Ipv6Addr,
_dst_ip: SpecifiedAddr<Ipv6Addr>,
packet: NdpPacket<B>,
) where
B: ByteSlice,
{
// TODO(ghanan): Make sure the IP packet's hop limit was set to 255 as per RFC 4861 sections
// 4.1, 4.2, 4.3, 4.4, and 4.5 (each type of NDP packet).
match packet {
NdpPacket::RouterSolicitation(p) => {
trace!("receive_ndp_packet_inner: Received NDP RS");
if !ctx.is_router(device_id) {
// Hosts MUST silently discard Router Solicitation messages
// as per RFC 4861 section 6.1.1.
trace!(
"receive_ndp_packet_inner: device {:?} is not a router, discarding NDP RS",
device_id
);
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(p.body());
if !src_ip.is_specified() && 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;
}
ctx.increment_counter("ndp::rx_router_solicitation");
let ndp_state = ctx.get_state_mut_with(device_id);
if !ndp_state.configs.get_router_configurations().get_should_send_advertisements() {
// If a router's AdvSendAdvertisements flag is set to false, it doesn't send
// Router Advertisements - even in response to Router Solicitations. See
// AdvSendAdvertisements in RFC 4861 section 6.2.1 for more details.
trace!("receive_ndp_packet_inner: device {:?} is not configured to send Router Advertisements, ignoring NDP RS", device_id);
return;
}
if let Some(link_addr) = source_link_layer_option {
// Set the link address and mark the neighbor entry as stale if we either create it,
// or updated an existing one, as per RFC 4861 section 6.2.6.
ndp_state.neighbors.set_link_address(src_ip, link_addr, false);
}
if let Some(state) = ndp_state.neighbors.get_neighbor_state_mut(&src_ip) {
// Set the neighbor's IsRouter flag to false, as per RFC 4861 section 6.2.6.
//
// This is because only hosts should solicit routers.
state.is_router = false;
}
//
// As per RFC 4861 section 6.2.6:
// A router might process Router Solicitations as follows:
//
// - Upon receipt of a Router Solicitation, compute a random delay
// within the range 0 through `MAX_RA_DELAY_TIME`. If the computed
// value corresponds to a time later than the time the next multicast
// Router Advertisement is scheduled to be sent, ignore the random
// delay and send the advertisement at the already-scheduled time.
//
// - If the router sent a multicast Router Advertisement (solicited or
// unsolicited) within the last `MIN_DELAY_BETWEEN_RAS` seconds,
// schedule the advertisement to be sent at a time corresponding to
// `MIN_DELAY_BETWEEN_RAS` plus the random value after the previous
// advertisement was sent. This ensures that the multicast Router
// Advertisements are rate limited.
//
// - Otherwise, schedule the sending of a Router Advertisement at the
// time given by the random value.
//
// Routers have the option of responding to Router Solicitations by sending a Router
// Advertisement directly to the soliciting host (the source address is not the
// unspecified address), or by a Router Advertisement to the all nodes multicast
// address. For now, we will only reply to Router Solicitations by sending the
// Router Advertisement to the IPv6 all nodes multicast addres.
//
// As per RFC 4861 section 6.2.6, consecutive Router Advertisements
// sent to the all-nodes multicast address MUST be rate limited to no
// more than one advertisement every `MIN_DELAY_BETWEEN_RAS` seconds.
//
// TODO(ghanan): Support sending Router Advertisements directly to the soliciting host.
// The choice to send Router Advertisements directly to the host or the
// all nodes multicast address should be configurable at run-time.
// Calculate random delay between `MIN_RA_DELAY_TIME` and `MAX_RA_DELAY_TIME`.
let now = ctx.now();
let delay = ctx.rng().gen_range(MIN_RA_DELAY_TIME, MAX_RA_DELAY_TIME);
let send_instant = now
.checked_add(delay)
.expect("Failed to calculate instant to reply to Router Solicitation");
if ctx
.scheduled_instant(NdpTimerId::new_router_advertisement_transmit(device_id).into())
.map_or(false, |i| i < send_instant)
{
// Next scheduled Router Advertisement will be sent before `delay` time from
// `now` so let the scheduled Router Advertisement be the reply to this
// Router Solicitation message.
trace!("receive_ndp_packet_inner: next scheduled RA will be the response this NDP RS from {:?} on device {:?}", src_ip, device_id);
return;
}
let ndp_state = ctx.get_state_mut_with(device_id);
if let Some(last_instant) = ndp_state.last_router_advertisement_instant {
if now.duration_since(last_instant) <= MIN_DELAY_BETWEEN_RAS {
// We already sent a Router Advertisement to the all-nodes multicast address
// within `MIN_DELAY_BETWEEN_RAS` time, so schedule a new Router Advertisement
// to be sent at `delay` and `MIN_DELAY_BETWEEN_RAS` time after `last_instant`.
let next_instant = last_instant
.checked_add(delay)
.expect("Failed to calculate instant to send the next Router Advertisement")
.checked_add(MIN_DELAY_BETWEEN_RAS)
.expect(
"Failed to calculate instant to send the next Router Advertisement",
);
trace!("receive_ndp_packet_inner: already sent an RA within MIN_DELAY_BETWEEN_RAS ({:?}), scheduling RA reply for RS from {:?} to be sent at {:?} from device {:?}", MIN_DELAY_BETWEEN_RAS, src_ip, next_instant, device_id);
schedule_next_router_advertisement_instant(ctx, device_id, next_instant);
return;
}
}
// If we haven't scheduled the RA response yet, or an already scheduled RA transmission
// is too far from now, schedule an RA transmission for after `delay` time.
trace!("receive_ndp_packet_inner: scheduling an RA reply for RS from {:?} to be sent at {:?} from device {:?}", src_ip, send_instant, device_id);
schedule_next_router_advertisement_instant(ctx, device_id, send_instant);
}
NdpPacket::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.
ctx.increment_counter("ndp::rx_router_advertisement");
if ctx.is_router(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 = ctx.get_state_mut_with(device_id);
let ra = p.message();
let timer_id = NdpTimerId::new_router_invalidation(device_id, src_ip).into();
if let Some(router_lifetime) = ra.router_lifetime() {
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);
};
// Reset invalidation timeout.
trace!("receive_ndp_packet_inner: NDP RA: updating invalidation timer to {:?} for router: {:?}", router_lifetime, src_ip);
ctx.schedule_timer(router_lifetime.get(), timer_id);
} else {
if ndp_state.has_default_router(&src_ip) {
trace!("receive_ndp_packet_inner: NDP RA has zero-valued router lifetime, invaliding router: {:?}", src_ip);
// `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);
// As per RFC 4861 section 6.3.4, immediately timeout the
// entry as specified in RFC 4861 section 6.3.5.
assert!(ctx.cancel_timer(timer_id).is_some());
} 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.
}
// Borrow again so that a) we shadow the original `ndp_state` and
// thus, b) the original is dropped before `ctx` is used mutably in
// various code above (namely, to schedule timers). Now that all of
// that mutation has happened, we can borrow `ctx` mutably again and
// not run afoul of the borrow checker.
let ndp_state = ctx.get_state_mut_with(device_id);
// As per RFC 4861 section 6.3.4:
// If the received Reachable Time value is specified, 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 let Some(base_reachable_time) = ra.reachable_time() {
trace!("receive_ndp_packet_inner: NDP RA: updating base_reachable_time to {:?} for router: {:?}", base_reachable_time, src_ip);
ndp_state.set_base_reachable_time(base_reachable_time.get());
}
// As per RFC 4861 section 6.3.4:
// The RetransTimer variable SHOULD be copied from the Retrans Timer
// field, if it is specified.
//
// 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(retransmit_timer) = ra.retransmit_timer() {
trace!("receive_ndp_packet_inner: NDP RA: updating retrans_timer to {:?} for router: {:?}", retransmit_timer, src_ip);
ndp_state.set_retrans_timer(retransmit_timer.get());
}
// As per RFC 4861 section 6.3.4:
// If the received Cur Hop Limit value is specified, 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) = 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);
ctx.set_hop_limit(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 = ctx.get_state_mut_with(device_id);
let link_addr = D::Address::from_bytes(&a[..D::Address::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 >= Ipv6::MINIMUM_LINK_MTU.into() {
// `set_mtu` may panic if `mtu` is less than
// `MINIMUM_LINK_MTU` but we just checked to make
// sure that `mtu` is at least `MINIMUM_LINK_MTU` so
// we know `set_mtu` will not panic.
ctx.set_mtu(device_id, mtu);
} else {
trace!("receive_ndp_packet_inner: NDP RA: not setting link MTU (from {:?}) to {:?} as it is less than Ipv6::MINIMUM_LINK_MTU", src_ip, mtu);
}
}
NdpOption::PrefixInformation(prefix_info) => {
let ndp_state = ctx.get_state_mut_with(device_id);
trace!("receive_ndp_packet_inner: prefix information option with prefix = {:?}", prefix_info);
let addr_sub = match prefix_info.addr_subnet() {
Ok(a) => a,
Err(err) => {
trace!("receive_ndp_packet_inner: malformed prefix information ({:?}), so ignoring", err);
continue;
}
};
if prefix_info.prefix().is_linklocal() {
// As per RFC 4861 section 6.3.4 (on-link prefix determination)
// and RFC 4862 section 5.5.3 (SLAAC), ignore options with the
// the link-local prefix.
trace!("receive_ndp_packet_inner: prefix is a link local, so ignoring");
continue;
}
if prefix_info.on_link_flag() {
// Timer ID for this prefix's invalidation.
let timer_id =
NdpTimerId::new_prefix_invalidation(device_id, addr_sub).into();
if let Some(valid_lifetime) = prefix_info.valid_lifetime() {
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 valid_lifetime == INFINITE_LIFETIME {
// We do not need a timer to mark the prefix as invalid when it
// has an infinite lifetime.
ctx.cancel_timer(timer_id);
} else {
ctx.schedule_timer(valid_lifetime.get(), timer_id);
}
} else if ndp_state.has_prefix(&addr_sub) {
trace!("receive_ndp_packet_inner: on-link 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.cancel_timer(timer_id);
let ndp_state = ctx.get_state_mut_with(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: on-link prefix is unknown and is has valid lifetime = 0, so ignoring");
}
}
if prefix_info.autonomous_address_configuration_flag() {
if prefix_info.preferred_lifetime() > prefix_info.valid_lifetime() {
// If the preferred lifetime is greater than the valid lifetime,
// silently ignore the Prefix Information option, as per RFC 4862
// section 5.5.3.
trace!("receive_ndp_packet_inner: autonomous prefix's preferred lifetime is greater than valid lifetime, ignoring");
continue;
}
let subnet = match Subnet::new(
*prefix_info.prefix(),
prefix_info.prefix_length(),
) {
Ok(subnet) => subnet,
Err(err) => {
trace!("receive_ndp_packet_inner: autonomous prefix {:?} with length {:?} is not valid: {:?}", prefix_info.prefix(), prefix_info.prefix_length(), err);
continue;
}
};
let now = ctx.now();
let preferred_until = prefix_info
.preferred_lifetime()
.map(|l| now.checked_add(l.get()).unwrap());
let valid_for = prefix_info
.valid_lifetime()
.map(|l| l.get())
.unwrap_or(Duration::from_secs(0));
let valid_until = now.checked_add(valid_for).unwrap();
// Before configuring a SLAAC address, check to see if we already have
// a SLAAC address for the given prefix.
if let Some(entry) = ctx.get_ipv6_addr_entries(device_id).find(|a| {
a.addr_sub().subnet() == subnet
&& a.configuration_type() == AddressConfigurationType::Slaac
}) {
let addr_sub = entry.addr_sub();
let addr = addr_sub.addr();
trace!("receive_ndp_packet_inner: autonomous prefix is for an already configured SLAAC address {:?} on device {:?}", addr_sub, device_id);
// We know the call to `unwrap` will not panic because `entry` will
// be an `AddressEntry` configured via SLAAC. All SLAAC addresses
// MUST expire after some time as per the discovered autonomous
// prefix's valid lifetime.
let entry_valid_until = entry.valid_until().unwrap();
let remaining_lifetime = if entry_valid_until < now {
None
} else {
Some(entry_valid_until.duration_since(now))
};
// As per RFC 4862 section 5.5.3.e, if the advertised prefix is
// equal to the prefix of an address configured by stateless
// autoconfiguration in the list, the preferred lifetime of the
// address is reset to the Preferred Lifetime in the received
// advertisement.
trace!("receive_ndp_packet_inner: updating preferred lifetime to {:?} for SLAAC address {:?} on device {:?}", preferred_until, addr, device_id);
// Update the preferred lifetime for this address.
//
// Must not have reached this point if the address was not already
// assigned to a device.
if let Some(preferred_until_duration) = preferred_until {
if entry.state().is_deprecated() {
ctx.unique_address_determined(device_id, addr.get());
}
ctx.schedule_timer_instant(
preferred_until_duration,
NdpTimerId::new_deprecate_slaac_address(
device_id,
addr.get(),
)
.into(),
);
} else if !entry.state().is_deprecated() {
ctx.deprecate_slaac_addr(device_id, &addr.get());
ctx.cancel_timer(NdpTimerId::new_deprecate_slaac_address(
device_id,
addr.get(),
));
}
// As per RFC 4862 section 5.5.3.e, the specific action to perform
// for the valid lifetime of the address depends on the Valid
// Lifetime in the received advertisement and the remaining time to
// the valid lifetime expiration of the previously autoconfigured
// address:
if (valid_for > MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE)
|| remaining_lifetime.map_or(true, |r| r < valid_for)
{
// If the received Valid Lifetime is greater than 2 hours or
// greater than RemainingLifetime, set the valid lifetime of the
// corresponding address to the advertised Valid Lifetime.
trace!("receive_ndp_packet_inner: updating valid lifetime to {:?} for SLAAC address {:?} on device {:?}", valid_until, addr, device_id);
// Set the valid lifetime for this address.
ctx.update_slaac_addr_valid_until(
device_id,
&addr,
valid_until,
);
// Must not have reached this point if the address was already
// assigned to a device.
assert!(ctx
.schedule_timer_instant(
valid_until,
NdpTimerId::new_invalidate_slaac_address(
device_id,
addr.get()
)
.into(),
)
.is_some());
} else if remaining_lifetime
.map_or(true, |r| r <= MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE)
{
// If RemainingLifetime is less than or equal to 2 hours, ignore
// the Prefix Information option with regards to the valid
// lifetime, unless the Router Advertisement from which this
// option was obtained has been authenticated (e.g., via Secure
// Neighbor Discovery [RFC3971]). If the Router Advertisement
// was authenticated, the valid lifetime of the corresponding
// address should be set to the Valid Lifetime in the received
// option.
//
// TODO(ghanan): If the NDP packet this prefix option is in was
// authenticated, update the valid lifetime of
// the address to the valid lifetime in the
// received option, as per RFC 4862 section
// 5.5.3.e.
trace!("receive_ndp_packet_inner: not updating valid lifetime for SLAAC address {:?} on device {:?} as remaining lifetime is less than 2 hours and new valid lifetime ({:?}) is less than remaining lifetime", addr, device_id, valid_for);
} else {
// Otherwise, reset the valid lifetime of the corresponding
// address to 2 hours.
trace!("receive_ndp_packet_inner: resetting valid lifetime to 2 hrs for SLAAC address {:?} on device {:?}",addr, device_id);
// Update the valid lifetime for this address.
let valid_until = now
.checked_add(MIN_PREFIX_VALID_LIFETIME_FOR_UPDATE)
.unwrap();
ctx.update_slaac_addr_valid_until(
device_id,
&addr,
valid_until,
);
// Must not have reached this point if the address was not
// already assigned to a device.
assert!(ctx
.schedule_timer_instant(
valid_until,
NdpTimerId::new_invalidate_slaac_address(
device_id,
addr.get()
)
.into(),
)
.is_some());
}
} else {
// As per RFC 4862 section 5.5.3.e, if the prefix advertised is not
// equal to the prefix of an address configured by stateless
// autoconfiguration already in the list of addresses associated
// with the interface, and if the Valid Lifetime is not 0, form an
// address (and add it to the list) by combining the advertised
// prefix with an interface identifier of the link as follows:
//
// | 128 - N bits | N bits |
// +--------------------------------------+------------------------+
// | link prefix | interface identifier |
// +---------------------------------------------------------------+
if valid_for == ZERO_DURATION {
trace!("receive_ndp_packet_inner: autonomous prefix has valid lfietime = 0, ignoring");
continue;
}
if subnet.prefix() != REQUIRED_PREFIX_BITS {
// If the sum of the prefix length and interface identifier
// length does not equal 128 bits, the Prefix Information option
// MUST be ignored, as per RFC 4862 section 5.5.3.
error!("receive_ndp_packet_inner: autonomous prefix length {:?} and interface identifier length {:?} cannot form valid IPv6 address, ignoring", subnet.prefix(), REQUIRED_PREFIX_BITS);
continue;
}
// Generate the global address as defined by RFC 4862 section
// 5.5.3.d.
let address = generate_global_address(
&subnet,
&ctx.get_interface_identifier(device_id)[..],
);
// TODO(ghanan): Should bindings be the one to actually assign the
// address to maintain a "single source of truth"?
// Attempt to add the address to the device.
if let Err(err) =
ctx.add_slaac_addr_sub(device_id, address, valid_until)
{
error!("receive_ndp_packet_inner: Failed configure new IPv6 address {:?} on device {:?} via SLAAC with error {:?}", address, device_id, err);
} else {
trace!("receive_ndp_packet_inner: Successfully configured new IPv6 address {:?} on device {:?} via SLAAC", address, device_id);
// Set the valid lifetime for this address.
//
// Must not have reached this point if the address was already
// assigned to a device.
assert!(ctx
.schedule_timer_instant(
valid_until,
NdpTimerId::new_invalidate_slaac_address(
device_id,
address.addr().get()
)
.into(),
)
.is_none());
let timer_id = NdpTimerId::new_deprecate_slaac_address(
device_id,
address.addr().get(),
);
// Set the preferred lifetime for this address.
//
// Must not have reached this point if the address was already
// assigned to a device.
match preferred_until {
Some(preferred_until_duration) => assert!(ctx
.schedule_timer_instant(
preferred_until_duration,
timer_id.into()
)
.is_none()),
None => {
ctx.deprecate_slaac_addr(
device_id,
&address.addr().get(),
);
assert!(ctx.cancel_timer(timer_id.into()).is_none());
}
};
}
}
}
}
_ => {}
}
}
// 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 = ctx.get_state_mut_with(device_id);
if let Some(state) = ndp_state.neighbors.get_neighbor_state_mut(&src_ip) {
state.is_router = true;
}
}
NdpPacket::NeighborSolicitation(p) => {
trace!("receive_ndp_packet_inner: Received NDP NS");
let target_address = p.message().target_address();
// Is `target_address` a valid unicast address, and if so, is it associated with
// our device? If not, drop the packet.
if !target_address.is_valid_unicast()
|| ctx.ipv6_addr_state(device_id, target_address).is_none()
{
// 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;
}
// We know the call to `unwrap` will not panic because we just checked to make sure
// that `target_address` is associated with `device_id`.
let state = ctx.ipv6_addr_state(device_id, target_address).unwrap();
if state.is_tentative() {
if !src_ip.is_specified() {
// 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.
trace!(
"receive_ndp_packet_inner: Received NDP NS: duplicate address {:?} detected on device {:?}", target_address, device_id
);
duplicate_address_detected(ctx, device_id, *target_address);
}
// `target_address` is tentative on `device_id` so we do not continue processing
// the NDP NS.
return;
}
//
// At this point, we gurantee the following is true because of the earlier checks:
//
// 1) The target address is a valid unicast address.
// 2) The target address is an address that is on our device, `device_id`.
// 3) The target address is not tentative.
//
ctx.increment_counter("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_specified() {
// 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(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 create
// the neighbor entry, or updated an existing one, as per RFC 4861
// section 7.2.3.
ctx.get_state_mut_with(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(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(
ctx,
device_id,
false,
*target_address,
Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get(),
)
}
}
NdpPacket::NeighborAdvertisement(p) => {
trace!("receive_ndp_packet_inner: Received NDP NA");
let message = p.message();
let target_address = message.target_address();
match ctx.ipv6_addr_state(device_id, target_address) {
Some(AddressState::Tentative) => {
trace!("receive_ndp_packet_inner: NDP NA has a target address {:?} that is tentative on device {:?}", target_address, device_id);
duplicate_address_detected(ctx, device_id, *target_address);
return;
}
Some(AddressState::Assigned) | Some(AddressState::Deprecated) => {
// RFC 4862 says this situation is out of the scope, so we
// just log out the situation for now.
//
// TODO(ghanan): Signal to bindings that a duplicate address is detected?
error!("receive_ndp_packet_inner: NDP NA: A duplicated address {:?} found on device {:?} when we are not in DAD process!", target_address, device_id);
return;
}
// Do nothing.
None => {}
}
ctx.increment_counter("ndp::rx_neighbor_advertisement");
let ndp_state = ctx.get_state_mut_with(device_id);
let neighbor_state = if let Some(state) =
ndp_state.neighbors.get_neighbor_state_mut(&src_ip)
{
state
} else {
// If the neighbor is not in the cache, we just ignore the advertisement, as
// we're not yet interested in communicating with it, as per RFC 4861 section
// 7.2.5.
trace!("receive_ndp_packet_inner: Ignoring NDP NA from {:?} does not already exist in our list of neighbors, so discarding", src_ip);
return;
};
let target_ll = get_target_link_layer_option(p.body());
if neighbor_state.is_incomplete() {
// If we are in the Incomplete state, we should not have ever learned about a
// link-layer address.
assert!(neighbor_state.link_address.is_none());
if let Some(address) = target_ll {
// Set the IsRouter flag as per RFC 4861 section 7.2.5.
trace!(
"receive_ndp_packet_inner: NDP RS from {:?} indicicates it {:?} a router",
src_ip,
if message.router_flag() { "is" } else { "isn't" }
);
neighbor_state.is_router = message.router_flag();
// Record the link-layer 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.
//
// Note, since the neighbor's link address was `None` before, we will definitely
// update the address, so the state will be set to STALE if the solicited flag
// is unset.
trace!(
"receive_ndp_packet_inner: Resolving link address of {:?} to {:?}",
src_ip,
address
);
ndp_state.neighbors.set_link_address(src_ip, address, message.solicited_flag());
// Cancel the resolution timeout.
ctx.cancel_timer(
NdpTimerId::new_link_address_resolution(device_id, src_ip).into(),
);
// Send any packets queued for the neighbor awaiting address resolution.
ctx.address_resolved(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);
return;
}
return;
}
// If we are not in the Incomplete state, we should have (at some point) learned about a
// link-layer address.
assert!(neighbor_state.link_address.is_some());
if !message.override_flag() {
// As per RFC 4861 section 7.2.5:
//
// If the Override flag is clear and the supplied link-layer address differs from
// that in the cache, then one of two actions takes places:
//
// a) If the state of the entry is REACHABLE, set it to STALE, but do not update the
// entry in any other way.
//
// b) Otherwise, the received advertisement should be ignored and MUST NOT update
// cache.
if target_ll.map_or(false, |x| neighbor_state.link_address != Some(x)) {
if neighbor_state.is_reachable() {
trace!("receive_ndp_packet_inner: NDP RS from known reachable neighbor {:?} does not have override set, but supplied link addr is different, setting state to stale", src_ip);
neighbor_state.state = NeighborEntryState::Stale;
} else {
trace!("receive_ndp_packet_inner: NDP RS from known neighbor {:?} (with reachability unknown) does not have override set, but supplied link addr is different, ignoring", src_ip);
}
}
}
// Ignore this unless `target_ll` is `Some`.
let mut is_same = false;
// If override is set, the link-layer address MUST be inserted into the cache (if one is
// supplied and differs from the alreadt recoded address).
if let Some(address) = target_ll {
let address = Some(address);
is_same = neighbor_state.link_address == address;
if !is_same && message.override_flag() {
neighbor_state.link_address = address;
}
}
// If the override flag is set, or the supplied link-layer address is the same as that
// in the cache, or no Target Link-Layer Address option was supplied:
if message.override_flag() || target_ll.is_none() || is_same {
// - If the solicited flag is set, the state of the entry MUST be set to REACHABLE.
// - Else, if it was unset, and the link address was updated, the state MUST be set
// to STALE.
// - Otherwise, the state remains the same.
if message.solicited_flag() {
trace!("receive_ndp_packet_inner: NDP RS from {:?} is solicited and either has override set, link address isn't provided, or the provided address is not different, updating state to Reachable", src_ip);
neighbor_state.state = NeighborEntryState::Reachable;
} else if message.override_flag() && target_ll.is_some() && !is_same {
trace!("receive_ndp_packet_inner: NDP RS from {:?} is unsolicited and the link address was updated, updating state to Stale", src_ip);
neighbor_state.state = NeighborEntryState::Stale;
} else {
trace!("receive_ndp_packet_inner: NDP RS from {:?} is unsolicited and the link address was not updated, doing nothing", src_ip);
}
// Check if the neighbor transitioned from a router -> host.
if neighbor_state.is_router && !message.router_flag() {
trace!("receive_ndp_packet_inner: NDP RS from {:?} informed us that it is no longer a router, updating is_router flag", src_ip);
neighbor_state.is_router = false;
if let Some(router_ll) = LinkLocalAddr::new(src_ip) {
// Invalidate the router as a default router if it is one of our default
// routers.
if ndp_state.has_default_router(&router_ll) {
trace!("receive_ndp_packet_inner: NDP RS from {:?} (known as a default router) informed us that it is no longer a router, invaliding the default router", src_ip);
ndp_state.invalidate_default_router(&router_ll);
}
}
} else {
neighbor_state.is_router = message.router_flag();
}
}
}
NdpPacket::Redirect(_) => log_unimplemented!((), "NDP Redirect not implemented"),
}
}
fn get_source_link_layer_option<L: LinkAddress, B>(options: &Options<B>) -> Option<L>
where
B: ByteSlice,
{
options.iter().find_map(|o| match o {
NdpOption::SourceLinkLayerAddress(a) => {
if a.len() >= L::BYTES_LENGTH {
Some(L::from_bytes(&a[..L::BYTES_LENGTH]))
} else {
None
}
}
_ => None,
})
}
fn get_target_link_layer_option<L: LinkAddress, B>(options: &Options<B>) -> Option<L>
where
B: ByteSlice,
{
options.iter().find_map(|o| match o {
NdpOption::TargetLinkLayerAddress(a) => {
if a.len() >= L::BYTES_LENGTH {
Some(L::from_bytes(&a[..L::BYTES_LENGTH]))
} else {
None
}
}
_ => None,
})
}
/// Generate an IPv6 Global Address as defined by RFC 4862 section 5.5.3.d.
///
/// The generated address will be of the format:
///
/// | 128 - N bits | N bits |
/// +---------------------------------------+------------------------+
/// | link prefix | interface identifier |
/// +----------------------------------------------------------------+
///
/// # Panics
///
/// Panics if a valid IPv6 address cannot be formed with the provided prefix and interface
/// identifier, or if the prefix length is not a multiple of 8 bits.
fn generate_global_address(prefix: &Subnet<Ipv6Addr>, iid: &[u8]) -> AddrSubnet<Ipv6Addr> {
if prefix.prefix() % 8 != 0 {
unimplemented!("generate_global_address: not implemented for when prefix length is not a multiple of 8 bits");
}
let prefix_len = usize::from(prefix.prefix() / 8);
assert_eq!(usize::from(Ipv6Addr::BYTES) - prefix_len, iid.len());
let mut address = prefix.network().ipv6_bytes();
address[prefix_len..].copy_from_slice(&iid);
let address = AddrSubnet::new(Ipv6Addr::new(address), prefix.prefix()).unwrap();
assert_eq!(address.subnet(), *prefix);
address
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryFrom;
use net_types::ethernet::Mac;
use net_types::ip::AddrSubnet;
use packet::{Buf, GrowBuffer, ParseBuffer};
use packet_formats::icmp::ndp::{
options::PrefixInformation, OptionsSerializer, RouterAdvertisement, RouterSolicitation,
};
use packet_formats::icmp::{IcmpEchoRequest, IcmpParseArgs, Icmpv6Packet};
use packet_formats::testutil::{
parse_ethernet_frame, parse_icmp_packet_in_ip_packet_in_ethernet_frame,
};
use crate::device::{
add_ip_addr_subnet, del_ip_addr,
ethernet::{EthernetLinkDevice, EthernetTimerId},
get_assigned_ip_addr_subnets, get_ip_addr_state, get_ipv6_hop_limit, get_mtu,
is_in_ip_multicast, is_routing_enabled, set_routing_enabled, DeviceId, DeviceLayerTimerId,
DeviceLayerTimerIdInner, EthernetDeviceId,
};
use crate::testutil::{
self, get_counter_val, run_for, set_logger_for_test, trigger_next_timer,
DummyEventDispatcher, DummyEventDispatcherBuilder, DummyInstant, DummyNetwork, TestIpExt,
DUMMY_CONFIG_V6,
};
use crate::{Context, Instant, StackStateBuilder, TimerId, TimerIdInner};
// We assume Ethernet since that's what all of our tests use.
impl From<NdpTimerId<EthernetLinkDevice, EthernetDeviceId>> for TimerId {
fn from(id: NdpTimerId<EthernetLinkDevice, EthernetDeviceId>) -> Self {
TimerId(TimerIdInner::DeviceLayer(DeviceLayerTimerId(
DeviceLayerTimerIdInner::Ethernet(EthernetTimerId::Ndp(id)),
)))
}
}
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() -> SpecifiedAddr<Ipv6Addr> {
DUMMY_CONFIG_V6.local_ip
}
fn remote_ip() -> SpecifiedAddr<Ipv6Addr> {
DUMMY_CONFIG_V6.remote_ip
}
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 neighbor_advertisement_message(
src_ip: Ipv6Addr,
dst_ip: Ipv6Addr,
router_flag: bool,
solicited_flag: bool,
override_flag: bool,
mac: Option<Mac>,
) -> Buf<Vec<u8>> {
let mac = mac.map(|x| x.bytes());
let mut options = Vec::new();
if let Some(ref mac) = mac {
options.push(NdpOption::TargetLinkLayerAddress(mac));
}
OptionsSerializer::new(options.iter())
.into_serializer()
.encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new(
src_ip,
dst_ip,
IcmpUnusedCode,
NeighborAdvertisement::new(router_flag, solicited_flag, override_flag, src_ip),
))
.serialize_vec_outer()
.unwrap()
.unwrap_b()
}
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);
}
/// Validate the initial Router Advertisements sent by `ctx` after making a device
/// an advertising interface.
///
/// By the time this method returns, 3 packets will be sent.
fn validate_initial_ras_after_enable(
ctx: &mut Context<DummyEventDispatcher>,
device: DeviceId,
ndp_configs: &NdpConfigurations,
offset: usize,
) {
// The first `MAX_INITIAL_RTR_ADVERTISEMENTS` messages will have a delay of at max
// `MAX_INITIAL_RTR_ADVERT_INTERVAL`
for i in 0..MAX_INITIAL_RTR_ADVERTISEMENTS {
assert_eq!(ctx.dispatcher().frames_sent().len(), offset + usize::try_from(i).unwrap());
let now = ctx.now();
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0 <= (now + MAX_INITIAL_RTR_ADVERT_INTERVAL))
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
assert_eq!(
trigger_next_timer(ctx).unwrap(),
NdpTimerId::new_router_advertisement_transmit(device.id().into()).into()
);
assert_eq!(
ctx.dispatcher().frames_sent().len(),
offset + usize::try_from(i).unwrap() + 1
);
validate_simple_ra(
&ctx.dispatcher().frames_sent()[offset + usize::try_from(i).unwrap()].1,
ndp_configs
.get_router_configurations()
.get_advertised_default_lifetime()
.map_or(0, |x| x.get()),
);
}
// Should still have the timer set, but now the time must be between the valid interval
let interval = ndp_configs.get_router_configurations().get_router_advertisements_interval();
let now = ctx.now();
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0 >= (now + Duration::from_secs((*interval.start()).into())))
&& (*x.0 <= (now + Duration::from_secs((*interval.end()).into())))
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
}
/// Validate the final Router Advertisements sent by `ctx` after making a device
/// an advertising interface.
///
/// By the time this method returns, `MAX_FINAL_RTR_ADVERTISEMENTS` packets will be sent.
fn validate_final_ras(
ctx: &mut Context<DummyEventDispatcher>,
device: DeviceId,
offset: usize,
) {
let count = if let Some(x) = MAX_FINAL_RTR_ADVERTISEMENTS {
x.get()
} else {
return;
};
for i in 0..usize::from(count) {
assert_eq!(
trigger_next_timer(ctx).unwrap(),
NdpTimerId::new_router_advertisement_transmit(device.id().into()).into()
);
assert_eq!(ctx.dispatcher().frames_sent().len(), offset + i + 1);
validate_simple_ra(&ctx.dispatcher().frames_sent()[offset + i].1, 0);
}
// Should have no more router advertisement timers.
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| *x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into()).into())
.count(),
0
);
}
/// Validate a simple Router Advertisement message (using default NDP configurations).
fn validate_simple_ra(buf: &[u8], lifetime: u16) {
let (_, _, src_ip, dst_ip, _, message, code) =
parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv6, _, RouterAdvertisement, _>(
buf,
|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().addr().get());
assert_eq!(dst_ip, Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get());
assert_eq!(code, IcmpUnusedCode);
assert_eq!(message, RouterAdvertisement::new(64, false, false, lifetime, 0, 0));
}
#[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::<DummyEventDispatcher>();
let dev_id = ctx
.state_mut()
.device
.add_ethernet_device(TEST_LOCAL_MAC, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, dev_id);
// Now we have to manually assign the ip addresses, see `EthernetLinkDevice::get_ipv6_addr`
add_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(local_ip().get(), 128).unwrap())
.unwrap();
lookup::<EthernetLinkDevice, _>(&mut ctx, dev_id.id().into(), remote_ip().get());
// 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);
assert_eq!(
testutil::trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_link_address_resolution(dev_id.id().into(), remote_ip().get())
.into()
);
}
// check that we hit the timeout after MAX_MULTICAST_SOLICIT
assert_eq!(
*ctx.state().test_counters.get("ndp::neighbor_solicitation_timer"),
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, _| {
if *ctx == "local" {
vec![("remote", device_id, None)]
} else {
vec![("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
add_ip_addr_subnet(
net.context("local"),
device_id,
AddrSubnet::new(local_ip().get(), 128).unwrap(),
)
.unwrap();
add_ip_addr_subnet(
net.context("remote"),
device_id,
AddrSubnet::new(remote_ip().get(), 128).unwrap(),
)
.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().get(),
remote_ip().get(),
remote_ip(),
IpProto::Icmpv6,
body,
None,
)
.unwrap();
// 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!(
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
net.context("local"),
device_id.id().into()
)
.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 =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
net.context("local"),
device_id.id().into(),
)
.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 =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
net.context("remote"),
device_id.id().into(),
)
.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("<IcmpIpTransportContext as BufferIpTransportContext<Ipv6>>::receive_ip_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::<DummyEventDispatcher>();
let dev_id = ctx
.state_mut()
.device
.add_ethernet_device(TEST_LOCAL_MAC, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, dev_id);
// Now we have to manually assign the IP addresses, see `EthernetLinkDevice::get_ipv6_addr`
add_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(local_ip().get(), 128).unwrap())
.unwrap();
lookup::<EthernetLinkDevice, _>(&mut ctx, dev_id.id().into(), remote_ip().get());
// 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).into());
assert_eq!(ctx.dispatcher.timer_events().count(), 1);
// Deinitializing the correct ID should cancel the timer.
deinitialize(&mut ctx, dev_id.id.into());
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 multicast_addr = mac.to_ipv6_link_local().addr().get().to_solicited_node_address();
let local = DummyEventDispatcherBuilder::default();
let remote = DummyEventDispatcherBuilder::default();
let device_id = DeviceId::new_ethernet(0);
let mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = 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, _| {
if *ctx == "local" {
vec![("remote", device_id, None)]
} else {
vec![("local", device_id, None)]
}
},
);
// Create the devices (will start DAD at the same time).
assert_eq!(
net.context("local")
.state_mut()
.add_ethernet_device(mac, Ipv6::MINIMUM_LINK_MTU.into()),
device_id
);
crate::device::initialize_device(net.context("local"), device_id);
assert_eq!(
net.context("remote")
.state_mut()
.add_ethernet_device(mac, Ipv6::MINIMUM_LINK_MTU.into()),
device_id
);
crate::device::initialize_device(net.context("remote"), device_id);
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_eq!(
get_assigned_ip_addr_subnets::<_, Ipv6Addr>(net.context("local"), device_id).count(),
0
);
assert_eq!(
get_assigned_ip_addr_subnets::<_, Ipv6Addr>(net.context("remote"), device_id).count(),
0
);
// 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 net = DummyNetwork::new(
vec![
("local", local.build::<DummyEventDispatcher>()),
("remote", remote.build::<DummyEventDispatcher>()),
]
.into_iter(),
|ctx, _| {
if *ctx == "local" {
vec![("remote", device_id, None)]
} else {
vec![("local", device_id, None)]
}
},
);
// Enable DAD.
let mut ndp_configs = NdpConfigurations::default();
ndp_configs.set_max_router_solicitations(None);
crate::device::set_ndp_configurations(net.context("local"), device_id, ndp_configs.clone());
crate::device::set_ndp_configurations(net.context("remote"), device_id, ndp_configs);
println!("Setting new IP on local");
let addr = AddrSubnet::new(local_ip().get(), 128).unwrap();
let multicast_addr = local_ip().to_solicited_node_address();
add_ip_addr_subnet(net.context("local"), device_id, addr).unwrap();
// 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_eq!(
testutil::trigger_next_timer(net.context("local")).unwrap(),
NdpTimerId::new_dad_ns_transmission(device_id.id().into(), local_ip().get()).into()
);
assert!(NdpContext::<EthernetLinkDevice>::ipv6_addr_state(
net.context("local"),
device_id.id().into(),
&local_ip()
)
.unwrap()
.is_assigned());
println!("Set new IP on remote");
add_ip_addr_subnet(net.context("remote"), device_id, addr).unwrap();
// 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();
assert_eq!(
get_assigned_ip_addr_subnets::<_, Ipv6Addr>(net.context("remote"), device_id).count(),
1
);
// let's make sure that our local node still can use that address
assert!(NdpContext::<EthernetLinkDevice>::ipv6_addr_state(
net.context("local"),
device_id.id().into(),
&local_ip()
)
.unwrap()
.is_assigned());
// 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::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, dev_id);
let addr = local_ip();
add_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(addr.get(), 128).unwrap()).unwrap();
assert_eq!(
NdpContext::<EthernetLinkDevice>::ipv6_addr_state(&ctx, dev_id.id().into(), &addr)
.unwrap(),
AddressState::Tentative,
);
let addr = remote_ip();
assert!(NdpContext::<EthernetLinkDevice>::ipv6_addr_state(&ctx, dev_id.id().into(), &addr)
.is_none(),);
add_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(addr.get(), 128).unwrap()).unwrap();
assert_eq!(
NdpContext::<EthernetLinkDevice>::ipv6_addr_state(&ctx, dev_id.id().into(), &addr)
.unwrap(),
AddressState::Tentative,
);
}
#[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);
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());
let dev_id =
ctx.state_mut().add_ethernet_device(TEST_LOCAL_MAC, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, dev_id);
// Enable DAD.
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
dev_id.id().into(),
)
.set_dad_transmits(NonZeroU8::new(3));
add_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(local_ip().get(), 128).unwrap())
.unwrap();
for _ in 0..3 {
assert_eq!(
testutil::trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_dad_ns_transmission(dev_id.id().into(), local_ip().get()).into()
);
}
assert!(NdpContext::<EthernetLinkDevice>::ipv6_addr_state(
&ctx,
dev_id.id().into(),
&local_ip()
)
.unwrap()
.is_assigned());
}
#[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, _| {
if *ctx == "local" {
vec![("remote", device_id, None)]
} else {
vec![("local", device_id, None)]
}
},
);
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
net.context("local"),
device_id.id().into(),
)
.set_dad_transmits(NonZeroU8::new(3));
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
net.context("remote"),
device_id.id().into(),
)
.set_dad_transmits(NonZeroU8::new(3));
add_ip_addr_subnet(
net.context("local"),
device_id,
AddrSubnet::new(local_ip().get(), 128).unwrap(),
)
.unwrap();
let expected_timer_id =
NdpTimerId::new_dad_ns_transmission(device_id.id().into(), local_ip().get()).into();
// during the first and second period, the remote host is still down.
assert_eq!(testutil::trigger_next_timer(net.context("local")).unwrap(), expected_timer_id);
assert_eq!(testutil::trigger_next_timer(net.context("local")).unwrap(), expected_timer_id);
add_ip_addr_subnet(
net.context("remote"),
device_id,
AddrSubnet::new(local_ip().get(), 128).unwrap(),
)
.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_eq!(
get_assigned_ip_addr_subnets::<_, Ipv6Addr>(net.context("local"), device_id).count(),
1
);
assert_eq!(
get_assigned_ip_addr_subnets::<_, Ipv6Addr>(net.context("remote"), device_id).count(),
1
);
}
#[test]
fn test_dad_multiple_ips_simultaneously() {
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let dev_id =
ctx.state_mut().add_ethernet_device(TEST_LOCAL_MAC, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, dev_id);
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
let mut ndp_configs = NdpConfigurations::default();
ndp_configs.set_dup_addr_detect_transmits(NonZeroU8::new(3));
ndp_configs.set_max_router_solicitations(None);
crate::device::set_ndp_configurations(&mut ctx, dev_id, ndp_configs);
// Add an IP.
add_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(local_ip().get(), 128).unwrap())
.unwrap();
assert!(get_ip_addr_state(&ctx, dev_id, &local_ip()).unwrap().is_tentative());
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
// Send another NS.
run_for(&mut ctx, Duration::from_secs(1));
assert_eq!(ctx.dispatcher().frames_sent().len(), 2);
// Add another IP
add_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(remote_ip().get(), 128).unwrap())
.unwrap();
assert!(get_ip_addr_state(&ctx, dev_id, &local_ip()).unwrap().is_tentative());
assert!(get_ip_addr_state(&ctx, dev_id, &remote_ip()).unwrap().is_tentative());
assert_eq!(ctx.dispatcher().frames_sent().len(), 3);
// Run to the end for DAD for local ip
run_for(&mut ctx, Duration::from_secs(2));
assert!(get_ip_addr_state(&ctx, dev_id, &local_ip()).unwrap().is_assigned());
assert!(get_ip_addr_state(&ctx, dev_id, &remote_ip()).unwrap().is_tentative());
assert_eq!(ctx.dispatcher().frames_sent().len(), 6);
// Run to the end for DAD for local ip
run_for(&mut ctx, Duration::from_secs(1));
assert!(get_ip_addr_state(&ctx, dev_id, &local_ip()).unwrap().is_assigned());
assert!(get_ip_addr_state(&ctx, dev_id, &remote_ip()).unwrap().is_assigned());
assert_eq!(ctx.dispatcher().frames_sent().len(), 6);
// No more timers.
assert!(trigger_next_timer(&mut ctx).is_none());
}
#[test]
fn test_dad_cancel_when_ip_removed() {
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let dev_id =
ctx.state_mut().add_ethernet_device(TEST_LOCAL_MAC, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, dev_id);
// Enable DAD.
let mut ndp_configs = NdpConfigurations::default();
ndp_configs.set_dup_addr_detect_transmits(NonZeroU8::new(3));
ndp_configs.set_max_router_solicitations(None);
crate::device::set_ndp_configurations(&mut ctx, dev_id, ndp_configs);
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
// Add an IP.
add_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(local_ip().get(), 128).unwrap())
.unwrap();
assert!(get_ip_addr_state(&ctx, dev_id, &local_ip()).unwrap().is_tentative());
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
// Send another NS.
run_for(&mut ctx, Duration::from_secs(1));
assert_eq!(ctx.dispatcher().frames_sent().len(), 2);
// Add another IP
add_ip_addr_subnet(&mut ctx, dev_id, AddrSubnet::new(remote_ip().get(), 128).unwrap())
.unwrap();
assert!(get_ip_addr_state(&ctx, dev_id, &local_ip()).unwrap().is_tentative());
assert!(get_ip_addr_state(&ctx, dev_id, &remote_ip()).unwrap().is_tentative());
assert_eq!(ctx.dispatcher().frames_sent().len(), 3);
// Run 1s
run_for(&mut ctx, Duration::from_secs(1));
assert!(get_ip_addr_state(&ctx, dev_id, &local_ip()).unwrap().is_tentative());
assert!(get_ip_addr_state(&ctx, dev_id, &remote_ip()).unwrap().is_tentative());
assert_eq!(ctx.dispatcher().frames_sent().len(), 5);
// Remove local ip
del_ip_addr(&mut ctx, dev_id, &local_ip()).unwrap();
assert!(get_ip_addr_state(&ctx, dev_id, &local_ip()).is_none());
assert!(get_ip_addr_state(&ctx, dev_id, &remote_ip()).unwrap().is_tentative());
assert_eq!(ctx.dispatcher().frames_sent().len(), 5);
// Run to the end for DAD for local ip
run_for(&mut ctx, Duration::from_secs(2));
assert!(get_ip_addr_state(&ctx, dev_id, &local_ip()).is_none());
assert!(get_ip_addr_state(&ctx, dev_id, &remote_ip()).unwrap().is_assigned());
assert_eq!(ctx.dispatcher().frames_sent().len(), 6);
// No more timers.
assert!(trigger_next_timer(&mut ctx).is_none());
}
trait UnwrapNdp<B: ByteSlice> {
fn unwrap_ndp(self) -> NdpPacket<B>;
}
impl<B: ByteSlice> UnwrapNdp<B> for Icmpv6Packet<B> {
fn unwrap_ndp(self) -> NdpPacket<B> {
match self {
Icmpv6Packet::Ndp(ndp) => ndp,
_ => unreachable!(),
}
}
}
#[test]
fn test_receiving_router_solicitation_validity_check() {
let config = Ipv6::DUMMY_CONFIG;
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_id = 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_id, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
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.ipv6_builder().forward(true);
let mut ctx = DummyEventDispatcherBuilder::from_config(config.clone())
.build_with(state_builder, DummyEventDispatcher::default());
set_routing_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_id, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
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();
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(
unspecified_source,
config.local_ip,
))
.unwrap();
ctx.receive_ndp_packet(
device_id,
unspecified_source,
config.local_ip,
icmpv6_packet.unwrap_ndp(),
);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_solicitation"), 1);
}
#[test]
fn test_receiving_router_advertisement_validity_check() {
let config = Ipv6::DUMMY_CONFIG;
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_id = 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.get(), 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_id, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
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().addr().get();
let mut icmpv6_packet_buf =
router_advertisement_message(src_ip, config.local_ip.get(), 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_id, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 1);
}
#[test]
fn test_receiving_router_advertisement_fixed_message() {
let config = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::from_config(config.clone())
.build::<DummyEventDispatcher>();
let device_id = DeviceId::new_ethernet(0);
let src_ip = config.remote_mac.to_ipv6_link_local().addr();
//
// 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.get(),
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!(
!StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device_id.id().into(),
)
.has_default_router(&src_ip)
);
ctx.receive_ndp_packet(
device_id,
src_ip.get(),
config.local_ip,
icmpv6_packet.unwrap_ndp(),
);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 1);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device_id.id().into(),
);
// 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_id).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.get(),
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_id,
src_ip.get(),
config.local_ip,
icmpv6_packet.unwrap_ndp(),
);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 2);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device_id.id().into(),
);
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_id).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.get(),
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_id,
src_ip.get(),
config.local_ip,
icmpv6_packet.unwrap_ndp(),
);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 3);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device_id.id().into(),
);
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_id).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.get(),
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_id,
src_ip.get(),
config.local_ip,
icmpv6_packet.unwrap_ndp(),
);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 4);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device_id.id().into(),
);
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_id).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.get(),
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_id,
src_ip.get(),
config.local_ip,
icmpv6_packet.unwrap_ndp(),
);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 5);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device_id.id().into(),
);
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_id).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.get(),
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_id,
src_ip.get(),
config.local_ip,
icmpv6_packet.unwrap_ndp(),
);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 6);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device_id.id().into(),
);
// 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;
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_id).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).is_none());
//
// 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.get(),
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_id,
src_ip.get(),
config.local_ip,
icmpv6_packet.unwrap_ndp(),
);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 7);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device_id.id().into(),
);
// 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;
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_id).get(), 37);
// Invaldate the router by triggering the timeout.
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_router_invalidation(device_id.id().into(), src_ip).into()
);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device_id.id().into(),
);
assert!(!ndp_state.has_default_router(&src_ip));
// No more timers.
assert!(trigger_next_timer(&mut ctx).is_none());
}
#[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 = Ipv6::DUMMY_CONFIG;
let device_id = DeviceId::new_ethernet(0);
let src_ip = config.remote_mac.to_ipv6_link_local().addr();
let mut icmpv6_packet_buf = router_advertisement_message(
src_ip.get(),
config.local_ip.get(),
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!(
!StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
ctx,
device_id.id().into()
)
.has_default_router(&src_ip)
);
ctx.receive_ndp_packet(
device_id,
src_ip.get(),
config.local_ip,
icmpv6_packet.unwrap_ndp(),
);
assert_eq!(get_ipv6_hop_limit(&ctx, device_id).get(), hop_limit);
crate::ip::send_ip_packet_from_device(
ctx,
device_id,
config.local_ip.get(),
config.remote_ip.get(),
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(Ipv6::DUMMY_CONFIG)
.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 = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::from_config(config.clone())
.build::<DummyEventDispatcher>();
let device_id = DeviceId::new_ethernet(0);
let src_mac = Mac::new([10, 11, 12, 13, 14, 15]);
let src_ip = src_mac.to_ipv6_link_local().addr();
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.get(),
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 =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device_id.id().into(),
);
assert!(!ndp_state.has_default_router(&src_ip));
assert!(ndp_state.neighbors.get_neighbor_state(&src_ip).is_none());
ctx.receive_ndp_packet(
device_id,
src_ip.get(),
config.local_ip,
icmpv6_packet.unwrap_ndp(),
);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 1);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device_id.id().into(),
);
// 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_id,
src_ip.get(),
config.local_ip,
icmpv6_packet.unwrap_ndp(),
);
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 2);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device_id.id().into(),
);
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);
// Trigger router invalidation.
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_router_invalidation(device_id.id().into(), src_ip).into()
);
// Neighbor entry shouldn't change except for `is_router` which should now be `false`.
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device_id.id().into(),
);
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);
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 = Ipv6::DUMMY_CONFIG;
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().into();
let src_mac = Mac::new([10, 11, 12, 13, 14, 15]);
let src_ip = src_mac.to_ipv6_link_local().addr();
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.get(), 5781);
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.unwrap_ndp());
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 1);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx, device_id,
);
assert!(ndp_state.has_default_router(&src_ip));
assert_eq!(get_mtu(&ctx, 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.get(), u32::from(Ipv6::MINIMUM_LINK_MTU) - 1);
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.unwrap_ndp());
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 2);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx, device_id,
);
assert!(ndp_state.has_default_router(&src_ip));
assert_eq!(get_mtu(&ctx, 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.get(), Ipv6::MINIMUM_LINK_MTU.into());
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.unwrap_ndp());
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 3);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx, device_id,
);
assert!(ndp_state.has_default_router(&src_ip));
assert_eq!(get_mtu(&ctx, device), Ipv6::MINIMUM_LINK_MTU.into());
}
#[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 = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::from_config(config.clone())
.build::<DummyEventDispatcher>();
let device = DeviceId::new_ethernet(0);
let device_id = device.id().into();
let src_mac = Mac::new([10, 11, 12, 13, 14, 15]);
let src_ip = src_mac.to_ipv6_link_local().addr().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.get(), 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(device, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 1);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx, 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.get(), 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(device, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 2);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx, 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.get(), 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(device, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 3);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx, 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.get(), 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(device, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
assert_eq!(get_counter_val(&mut ctx, "ndp::rx_router_advertisement"), 4);
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx, 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_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_prefix_invalidation(device_id.into(), addr_subnet).into()
);
// Prefix should no longer be in our list.
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx, device_id,
);
assert!(!ndp_state.has_prefix(&addr_subnet));
// No more timers.
assert!(trigger_next_timer(&mut ctx).is_none());
}
#[test]
fn test_host_send_router_solicitations() {
fn validate_params(
src_mac: Mac,
src_ip: Ipv6Addr,
message: RouterSolicitation,
code: IcmpUnusedCode,
) {
let dummy_config = Ipv6::DUMMY_CONFIG;
assert_eq!(src_mac, dummy_config.local_mac);
assert_eq!(src_ip, dummy_config.local_mac.to_ipv6_link_local().addr().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 = Ipv6::DUMMY_CONFIG;
let mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = 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::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device_id);
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
let time = ctx.now();
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_router_solicitation(device_id.id().into()).into()
);
// Initial router solicitation should be a random delay between 0 and
// `MAX_RTR_SOLICITATION_DELAY`.
assert!(ctx.now().duration_since(time) < MAX_RTR_SOLICITATION_DELAY);
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
let (src_mac, _, src_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.now();
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_router_solicitation(device_id.id().into()).into()
);
assert_eq!(ctx.now().duration_since(time), RTR_SOLICITATION_INTERVAL);
let (src_mac, _, src_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.
add_ip_addr_subnet(
&mut ctx,
device_id,
AddrSubnet::new(dummy_config.local_ip.get(), 128).unwrap(),
)
.unwrap();
let time = ctx.now();
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_router_solicitation(device_id.id().into()).into()
);
assert_eq!(ctx.now().duration_since(time), RTR_SOLICITATION_INTERVAL);
let (src_mac, _, src_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::<Mac, _>(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.get());
assert_eq!(message, RouterSolicitation::default());
assert_eq!(code, IcmpUnusedCode);
// No more timers.
assert!(trigger_next_timer(&mut ctx).is_none());
// 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 = 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::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device_id);
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
let time = ctx.now();
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_router_solicitation(device_id.id().into()).into()
);
// Initial router solicitation should be a random delay between 0 and
// `MAX_RTR_SOLICITATION_DELAY`.
assert!(ctx.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.now();
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_router_solicitation(device_id.id().into()).into()
);
assert_eq!(ctx.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, _, src_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).is_none());
}
#[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 = Ipv6::DUMMY_CONFIG;
//
// 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.ipv6_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::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let timer_id = NdpTimerId::new_router_solicitation(device.id().into()).into();
// 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_eq!(trigger_next_timer(&mut ctx).unwrap(), timer_id);
// Should have sent a router solicitation and still have the timer setup.
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
let (_, _dst_mac, _, _, _, _, _) =
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 routing on device.
set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
assert!(is_routing_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 routing capable.
//
let mut state_builder = StackStateBuilder::default();
state_builder.ipv6_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::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let timer_id = NdpTimerId::new_router_solicitation(device.id().into()).into();
// 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_eq!(trigger_next_timer(&mut ctx).unwrap(), timer_id);
// Should have sent a frame and have a router solicitation timer setup.
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
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 routing on the device.
set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
assert!(is_routing_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_routing_enabled::<_, Ipv6>(&mut ctx, device, false);
assert!(!is_routing_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_eq!(trigger_next_timer(&mut ctx).unwrap(), timer_id);
// Should have sent a router solicitation.
assert_eq!(ctx.dispatcher().frames_sent().len(), 2);
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 = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device = ctx
.state_mut()
.add_ethernet_device(dummy_config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
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`.
add_ip_addr_subnet(
&mut ctx,
device,
AddrSubnet::new(dummy_config.local_ip.get(), 128).unwrap(),
)
.unwrap();
assert_eq!(
NdpContext::<EthernetLinkDevice>::ipv6_addr_state(
&ctx,
device.id().into(),
&dummy_config.local_ip
)
.unwrap(),
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.
add_ip_addr_subnet(
&mut ctx,
device,
AddrSubnet::new(dummy_config.remote_ip.get(), 128).unwrap(),
)
.unwrap();
assert_eq!(
NdpContext::<EthernetLinkDevice>::ipv6_addr_state(
&ctx,
device.id().into(),
&dummy_config.local_ip
)
.unwrap(),
AddressState::Assigned
);
assert_eq!(
NdpContext::<EthernetLinkDevice>::ipv6_addr_state(
&ctx,
device.id().into(),
&dummy_config.remote_ip
)
.unwrap(),
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());
let expected_timer_id =
NdpTimerId::new_dad_ns_transmission(device.id().into(), dummy_config.remote_ip.get())
.into();
// Allow aleady started DAD to complete (2 more more NS, 3 more timers).
assert_eq!(trigger_next_timer(&mut ctx).unwrap(), expected_timer_id);
assert_eq!(ctx.dispatcher().frames_sent().len(), 2);
assert_eq!(trigger_next_timer(&mut ctx).unwrap(), expected_timer_id);
assert_eq!(ctx.dispatcher().frames_sent().len(), 3);
assert_eq!(trigger_next_timer(&mut ctx).unwrap(), expected_timer_id);
assert_eq!(ctx.dispatcher().frames_sent().len(), 3);
assert_eq!(
NdpContext::<EthernetLinkDevice>::ipv6_addr_state(
&ctx,
device.id().into(),
&dummy_config.remote_ip
)
.unwrap(),
AddressState::Assigned
);
// Updating the IP should resolve immediately since DAD has just been turned off.
let new_ip = Ipv6::get_other_ip_address(3);
add_ip_addr_subnet(&mut ctx, device, AddrSubnet::new(new_ip.get(), 128).unwrap()).unwrap();
assert_eq!(
NdpContext::<EthernetLinkDevice>::ipv6_addr_state(
&ctx,
device.id().into(),
&dummy_config.local_ip
)
.unwrap(),
AddressState::Assigned
);
assert_eq!(
NdpContext::<EthernetLinkDevice>::ipv6_addr_state(
&ctx,
device.id().into(),
&dummy_config.remote_ip
)
.unwrap(),
AddressState::Assigned
);
assert_eq!(
NdpContext::<EthernetLinkDevice>::ipv6_addr_state(&ctx, device.id().into(), &new_ip)
.unwrap(),
AddressState::Assigned
);
}
#[test]
#[should_panic(
expected = "assertion failed: is_final_ra_batch || ctx.is_advertising_interface(device_id)"
)]
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 mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = 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.ipv6_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::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
// Enable sending router advertisements (`device`) is still not a router though.
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
)
.configs
.router_configurations
.set_should_send_advertisements(true);
// Should panic since `device` is not a router.
send_router_advertisement::<EthernetLinkDevice, _>(
&mut ctx,
device.id().into(),
Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get(),
);
}
#[test]
#[should_panic(expected = "cannot send router advertisement with temporary link-local address")]
fn test_send_router_advertisement_without_linklocal() {
//
// Attempting to send a router advertisement when a device does not yet have a link-local
// address should panic.
//
let mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = NdpConfigurations::default();
ndp_configs.set_max_router_solicitations(None);
stack_builder.device_builder().set_default_ndp_configs(ndp_configs);
stack_builder.ipv6_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::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
// Make `device` a router (netstack is configured to forward packets already).
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
// Enable sending router advertisements (`device`) is still not a router though.
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
)
.configs
.router_configurations
.set_should_send_advertisements(true);
// Should panic since `device` is not a router.
send_router_advertisement::<EthernetLinkDevice, _>(
&mut ctx,
device.id().into(),
Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get(),
);
}
#[test]
#[should_panic(
expected = "assertion failed: is_final_ra_batch || ctx.is_advertising_interface(device_id)"
)]
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 mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = 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.ipv6_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::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
// Make `device` a router (netstack is configured to forward packets already).
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
// Should panic since sending router advertisements is disabled.
send_router_advertisement::<EthernetLinkDevice, _>(
&mut ctx,
device.id().into(),
Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get(),
);
}
#[test]
fn test_send_router_advertisement_after_dad() {
//
// Should send router advertisements after DAD (3 transmits) is completed for the
// link-local address.
//
let mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = NdpConfigurations::default();
ndp_configs.set_max_router_solicitations(None);
ndp_configs.set_dup_addr_detect_transmits(NonZeroU8::new(3));
stack_builder.device_builder().set_default_ndp_configs(ndp_configs.clone());
stack_builder.ipv6_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::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
// Make `device` a router (netstack is configured to forward packets already).
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
// Enable sending router advertisements (`device`) is still not a router though.
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
)
.configs
.router_configurations
.set_should_send_advertisements(true);
// Finish up DAD.
assert_eq!(
NdpContext::<EthernetLinkDevice>::ipv6_addr_state(
&ctx,
device.id().into(),
&TEST_LOCAL_MAC.to_ipv6_link_local().addr().get()
)
.unwrap(),
AddressState::Tentative
);
let expected_timer_id = NdpTimerId::new_dad_ns_transmission(
device.id().into(),
TEST_LOCAL_MAC.to_ipv6_link_local().addr().get(),
)
.into();
assert_eq!(
run_for(&mut ctx, Duration::from_secs(3)),
&[expected_timer_id, expected_timer_id, expected_timer_id]
);
assert_eq!(
NdpContext::<EthernetLinkDevice>::ipv6_addr_state(
&ctx,
device.id().into(),
&TEST_LOCAL_MAC.to_ipv6_link_local().addr().get()
)
.unwrap(),
AddressState::Assigned
);
// Send initial RAs
//
// 3 packets have already been sent because of DAD.
validate_initial_ras_after_enable(&mut ctx, device, &ndp_configs, 3);
}
#[test]
fn test_sending_router_advertisement() {
//
// Test that valid router advertisements are sent based on the device's
// NDP router configurations (`NdpRouterConfigurations`).
//
let mut stack_builder = StackStateBuilder::default();
let mut ndp_configs = 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.ipv6_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::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
// Make `device` a router (netstack is configured to forward packets already).
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
// Enable and send router advertisements.
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
)
.configs
.router_configurations
.set_should_send_advertisements(true);
send_router_advertisement::<EthernetLinkDevice, _>(
&mut ctx,
device.id().into(),
Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get(),
);
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
let (_, _, 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().addr().get());
assert_eq!(dst_ip, Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get());
assert_eq!(code, IcmpUnusedCode);
assert_eq!(message, RouterAdvertisement::new(64, false, false, 1800, 0, 0));
// Set new values for a new router advertisement.
let router_configs =
&mut StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
)
.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::<EthernetLinkDevice, _>(
&mut ctx,
device.id().into(),
Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get(),
);
assert_eq!(ctx.dispatcher().frames_sent().len(), 2);
let (_, _, 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().addr().get());
assert_eq!(dst_ip, Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get());
assert_eq!(code, IcmpUnusedCode);
assert_eq!(message, RouterAdvertisement::new(75, true, false, 2000, 50, 200));
}
#[test]
fn test_sending_unsolicited_router_advertisements() {
let mut ndp_configs = NdpConfigurations::default();
let mut ndp_rc_configs = NdpRouterConfigurations::default();
ndp_rc_configs.set_should_send_advertisements(true);
ndp_configs.set_router_configurations(ndp_rc_configs);
ndp_configs.set_max_router_solicitations(None);
ndp_configs.set_dup_addr_detect_transmits(None);
//
// When netstack is not configured to forward IP packets, should not send router
// advertisements.
//
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(TEST_LOCAL_MAC, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
// Setting a non-router device to be an advertising interface should do nothing.
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs.clone());
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
fn confirm_dad_frame_timer(ctx: &mut Context<DummyEventDispatcher>, device: DeviceId) {
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
assert_eq!(ctx.dispatcher().timer_events().count(), 1);
assert_eq!(
*ctx.dispatcher().timer_events().last().unwrap().1,
NdpTimerId::new_dad_ns_transmission(
device.id().into(),
TEST_LOCAL_MAC.to_ipv6_link_local().addr().get()
)
.into()
);
}
//
// Test sending router advertisements (after dad).
//
let mut state_builder = StackStateBuilder::default();
state_builder.ipv6_builder().forward(true);
let mut default_ndp_configs = NdpConfigurations::default();
default_ndp_configs.set_max_router_solicitations(None);
state_builder.device_builder().set_default_ndp_configs(default_ndp_configs.clone());
let mut ctx = DummyEventDispatcherBuilder::default()
.build_with(state_builder, DummyEventDispatcher::default());
let device =
ctx.state_mut().add_ethernet_device(TEST_LOCAL_MAC, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
assert_eq!(ctx.dispatcher().timer_events().count(), 1);
//
// Update configurations that affect advertising interface status
// before DAD has completed (RAs should not be sent or started).
//
// Setting a non-router device to be an advertising interface should do nothing.
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs.clone());
confirm_dad_frame_timer(&mut ctx, device);
// Setting a non-advertising interface to a router should do nothing.
crate::device::set_ndp_configurations(&mut ctx, device, NdpConfigurations::default());
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
confirm_dad_frame_timer(&mut ctx, device);
// Set to be an advertising interface.
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs.clone());
confirm_dad_frame_timer(&mut ctx, device);
// Setting to be a non-router should end router advertisements (but should start router solicitations)
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, false);
confirm_dad_frame_timer(&mut ctx, device);
// Set back to a router.
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
confirm_dad_frame_timer(&mut ctx, device);
// Setting to be a non-advertising interface should end router advertisements.
crate::device::set_ndp_configurations(&mut ctx, device, NdpConfigurations::default());
confirm_dad_frame_timer(&mut ctx, device);
// Set back to being an advertising interface.
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs.clone());
confirm_dad_frame_timer(&mut ctx, device);
// Reset device routing and advertising interface status.
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, false);
crate::device::set_ndp_configurations(&mut ctx, device, default_ndp_configs);
confirm_dad_frame_timer(&mut ctx, device);
// Complete DAD.
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_dad_ns_transmission(
device.id().into(),
TEST_LOCAL_MAC.to_ipv6_link_local().addr().get()
)
.into()
);
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
//
// Update configurations that affect advertising interface status
// after DAD has completed (RAs may now be sent).
//
// Setting a non-router device to be an advertising interface should do nothing.
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs.clone());
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
// Setting a non-advertising interface to a router should do nothing.
crate::device::set_ndp_configurations(&mut ctx, device, NdpConfigurations::default());
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
// Set to be an advertising interface.
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs.clone());
validate_initial_ras_after_enable(&mut ctx, device, &ndp_configs, 1);
// Setting to be a non-router should end router advertisements.
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, false);
validate_final_ras(&mut ctx, device, 4);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
// Set back to a router.
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
validate_initial_ras_after_enable(&mut ctx, device, &ndp_configs, 7);
// Setting to be a non-advertising interface should end router advertisements.
crate::device::set_ndp_configurations(&mut ctx, device, NdpConfigurations::default());
validate_final_ras(&mut ctx, device, 10);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
// Set back to being an advertising interface.
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs.clone());
validate_initial_ras_after_enable(&mut ctx, device, &ndp_configs, 13);
}
#[test]
fn test_updating_ndp_router_config_router_advertisement_interval() {
let mut ndp_configs = NdpConfigurations::default();
let mut ndp_rc_configs = NdpRouterConfigurations::default();
ndp_rc_configs.set_should_send_advertisements(true);
ndp_rc_configs.set_router_advertisements_interval(200..=300);
ndp_configs.set_router_configurations(ndp_rc_configs.clone());
ndp_configs.set_dup_addr_detect_transmits(None);
ndp_configs.set_max_router_solicitations(None);
let mut state_builder = StackStateBuilder::default();
state_builder.ipv6_builder().forward(true);
state_builder.device_builder().set_default_ndp_configs(ndp_configs.clone());
let mut ctx = DummyEventDispatcherBuilder::default()
.build_with(state_builder, DummyEventDispatcher::default());
let device =
ctx.state_mut().add_ethernet_device(TEST_LOCAL_MAC, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
// Make device a router so it will start sending router advertisements
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
validate_initial_ras_after_enable(&mut ctx, device, &ndp_configs, 0);
let now = ctx.now();
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0 >= (now + Duration::from_secs(200)))
&& (*x.0 <= (now + Duration::from_secs(300)))
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
// Update the routing interval to some value that will make the timer get rescheduled
// (new max delay is less than the time to the next Router Advertisement transmission).
ndp_rc_configs.set_router_advertisements_interval(50..=100);
ndp_configs.set_router_configurations(ndp_rc_configs.clone());
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs.clone());
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0 >= (now + Duration::from_secs(50)))
&& (*x.0 <= (now + Duration::from_secs(100)))
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
// Update the routing interval to some value that will not make the timer get rescheduled
// (new max delay is greater than the time to the next Router Advertisement transmission).
ndp_rc_configs.set_router_advertisements_interval(25..=300);
ndp_configs.set_router_configurations(ndp_rc_configs.clone());
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs.clone());
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0 >= (now + Duration::from_secs(50)))
&& (*x.0 <= (now + Duration::from_secs(100)))
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
// Update the routing interval to some value that will not make the timer get rescheduled
// (new min delay is greater than the time to the next Router Advertisement transmission).
ndp_rc_configs.set_router_advertisements_interval(500..=1000);
ndp_configs.set_router_configurations(ndp_rc_configs.clone());
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs.clone());
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0 >= (now + Duration::from_secs(50)))
&& (*x.0 <= (now + Duration::from_secs(150)))
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
// Resetting the advertising interface status should use the new updated interval.
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, false);
validate_final_ras(&mut ctx, device, 3);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
validate_initial_ras_after_enable(&mut ctx, device, &ndp_configs, 6);
let now = ctx.now();
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0 >= (now + Duration::from_secs(500)))
&& (*x.0 <= (now + Duration::from_secs(1000)))
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
}
#[test]
fn test_router_response_to_router_solicitation_from_unspecified_source() {
/// Get a Router Solicitation ICMPv6 packet buffer.
fn rs_msg(src_ip: Ipv6Addr, dst_ip: Ipv6Addr) -> Buf<Vec<u8>> {
Buf::new(Vec::new(), ..)
.encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new(
src_ip,
dst_ip,
IcmpUnusedCode,
RouterSolicitation::default(),
))
.serialize_vec_outer()
.unwrap()
.into_inner()
}
let mut ndp_configs = NdpConfigurations::default();
let mut ndp_rc_configs = NdpRouterConfigurations::default();
ndp_rc_configs.set_should_send_advertisements(true);
ndp_rc_configs.set_router_advertisements_interval(200..=300);
ndp_configs.set_router_configurations(ndp_rc_configs.clone());
ndp_configs.set_dup_addr_detect_transmits(None);
ndp_configs.set_max_router_solicitations(None);
let dummy_config = Ipv6::DUMMY_CONFIG;
let mut state_builder = StackStateBuilder::default();
state_builder.ipv6_builder().forward(true);
state_builder.device_builder().set_default_ndp_configs(ndp_configs.clone());
let mut ctx = DummyEventDispatcherBuilder::default()
.build_with(state_builder, DummyEventDispatcher::default());
let device =
ctx.state_mut().add_ethernet_device(TEST_LOCAL_MAC, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
// Assign an address to the device (should be assigned immediately since DAD is disabled).
add_ip_addr_subnet(
&mut ctx,
device,
AddrSubnet::new(dummy_config.local_ip.get(), 128).unwrap(),
)
.unwrap();
// Make device a router so it will start sending router advertisements
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
// Should not have sent anything yet.
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
let now = ctx.now();
// First `MAX_INITIAL_RTR_ADVERTISEMENTS` will have an interval of
// `MAX_INITIAL_RTR_ADVERT_INTERVAL` because (3 left).
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0 == now.checked_add(MAX_INITIAL_RTR_ADVERT_INTERVAL).unwrap())
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
//
// Receiving a Router Solicitation when the next scheduled Router Advertisement transmission
// is too far away will update the Router Advertisement timer to be at max
// `MAX_RA_DELAY_TIME`.
//
let src_ip = Ipv6Addr::default();
let mut rs_buf = rs_msg(src_ip, dummy_config.local_ip.get());
let rs = rs_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, dummy_config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip, dummy_config.local_ip, rs.unwrap_ndp());
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0 <= now.checked_add(MAX_RA_DELAY_TIME).unwrap())
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
// Send the Router Advertisement.
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_router_advertisement_transmit(device.id().into()).into()
);
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
validate_simple_ra(&ctx.dispatcher().frames_sent()[0].1, 1800);
//
// Receiving a router solicitation close to the next scheduled router advertisement
// transmission should not reschedule the router advertisement.
//
let now = ctx.now();
// First `MAX_INITIAL_RTR_ADVERTISEMENTS` will have an interval of
// `MAX_INITIAL_RTR_ADVERT_INTERVAL` because (2 left).
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0 == now.checked_add(MAX_INITIAL_RTR_ADVERT_INTERVAL).unwrap())
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
// Skip time to right before the next Router Advertisement transmission.
run_for(&mut ctx, MAX_INITIAL_RTR_ADVERT_INTERVAL - MIN_RA_DELAY_TIME);
let now = ctx.now();
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (x.0.duration_since(now) == MIN_RA_DELAY_TIME)
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
// Receiving a Router Solicitation.
let mut rs_buf = rs_msg(src_ip, dummy_config.local_ip.get());
let rs = rs_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, dummy_config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip, dummy_config.local_ip, rs.unwrap_ndp());
// Timer should not have been updated.
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (x.0.duration_since(now) == MIN_RA_DELAY_TIME)
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
// Send the Router Advertisement.
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_router_advertisement_transmit(device.id().into()).into()
);
assert_eq!(ctx.dispatcher().frames_sent().len(), 2);
validate_simple_ra(&ctx.dispatcher().frames_sent()[1].1, 1800);
//
// Receiving a router solicitation within `MIN_DELAY_BETWEEN_RAS` of sending a Router
// Advertisement to the all-nodes multicast address will update the transmission time
// of the next Router Advertisement message to be at max `MIN_DELAY_BETWEEN_RAS` +
// `MAX_RA_DELAY_TIME` from the time the last Router Advertisement was sent.
//
// Time the last router advertisement was sent.
let last_instant = ctx.now();
// Skip time by 1s (less than `MIN_DELAY_BETWEEN_RAS`).
run_for(&mut ctx, MIN_DELAY_BETWEEN_RAS - Duration::from_secs(1));
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== last_instant.checked_add(MAX_INITIAL_RTR_ADVERT_INTERVAL).unwrap())
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
// Receiving a Router Solicitation.
let mut rs_buf = rs_msg(src_ip, dummy_config.local_ip.get());
let rs = rs_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, dummy_config.local_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip, dummy_config.local_ip, rs.unwrap_ndp());
// Timer should be updated to be at max `MIN_DELAY_BETWEEN_RAS` + `MAX_RA_DELAY_TIME` from
// the time the last Router Advertisement was sent.
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (x.0.duration_since(last_instant)
<= (MIN_DELAY_BETWEEN_RAS + MAX_RA_DELAY_TIME))
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
// Send the Router Advertisement.
assert_eq!(ctx.dispatcher().frames_sent().len(), 2);
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_router_advertisement_transmit(device.id().into()).into()
);
assert_eq!(ctx.dispatcher().frames_sent().len(), 3);
validate_simple_ra(&ctx.dispatcher().frames_sent()[2].1, 1800);
}
#[test]
fn test_router_response_to_router_solicitation_from_specified_source() {
/// Get a Router Solicitation ICMPv6 packet buffer.
fn rs_msg(src_ip: Ipv6Addr, src_mac: Mac, dst_ip: Ipv6Addr) -> Buf<Vec<u8>> {
let mac_bytes = src_mac.bytes();
let options = &[NdpOption::SourceLinkLayerAddress(&mac_bytes)];
OptionsSerializer::<_>::new(options.iter())
.into_serializer()
.encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new(
src_ip,
dst_ip,
IcmpUnusedCode,
RouterSolicitation::default(),
))
.serialize_vec_outer()
.unwrap()
.unwrap_b()
}
let mut ndp_configs = NdpConfigurations::default();
let mut ndp_rc_configs = NdpRouterConfigurations::default();
ndp_rc_configs.set_should_send_advertisements(true);
ndp_rc_configs.set_router_advertisements_interval(200..=300);
ndp_configs.set_router_configurations(ndp_rc_configs.clone());
ndp_configs.set_dup_addr_detect_transmits(None);
ndp_configs.set_max_router_solicitations(None);
let dummy_config = Ipv6::DUMMY_CONFIG;
let mut state_builder = StackStateBuilder::default();
state_builder.ipv6_builder().forward(true);
state_builder.device_builder().set_default_ndp_configs(ndp_configs.clone());
let mut ctx = DummyEventDispatcherBuilder::default()
.build_with(state_builder, DummyEventDispatcher::default());
let device =
ctx.state_mut().add_ethernet_device(TEST_LOCAL_MAC, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
// Assign an address to the device (should be assigned immediately since DAD is disabled).
add_ip_addr_subnet(
&mut ctx,
device,
AddrSubnet::new(dummy_config.local_ip.get(), 128).unwrap(),
)
.unwrap();
// Make device a router so it will start sending router advertisements
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
// Should not have sent anything yet.
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
let now = ctx.now();
// First `MAX_INITIAL_RTR_ADVERTISEMENTS` will have an interval of
// `MAX_INITIAL_RTR_ADVERT_INTERVAL` because (3 left).
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0 == now.checked_add(MAX_INITIAL_RTR_ADVERT_INTERVAL).unwrap())
&& (*x.1
== NdpTimerId::new_router_advertisement_transmit(device.id().into())
.into()))
.count(),
1
);
//
// Receiving a Router Solicitation from a specified source with a source link address
// should update our neighbor cache.
//
let mut rs_buf = rs_msg(
dummy_config.remote_ip.get(),
dummy_config.remote_mac,
dummy_config.local_ip.get(),
);
let rs = rs_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(
dummy_config.remote_ip,
dummy_config.local_ip,
))
.unwrap();
ctx.receive_ndp_packet(
device,
dummy_config.remote_ip.get(),
dummy_config.local_ip,
rs.unwrap_ndp(),
);
// Send the Router Advertisement.
assert_eq!(ctx.dispatcher().frames_sent().len(), 0);
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_router_advertisement_transmit(device.id().into()).into()
);
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
validate_simple_ra(&ctx.dispatcher().frames_sent()[0].1, 1800);
// source should be in our neighbor cache.
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
);
let neighbor = ndp_state.neighbors.get_neighbor_state_mut(&dummy_config.remote_ip).unwrap();
assert_eq!(neighbor.link_address.unwrap(), dummy_config.remote_mac);
assert_eq!(neighbor.state, NeighborEntryState::Stale);
assert_eq!(neighbor.is_router, false);
//
// Receiving a Router Solicitation from a specified source with a source link address
// should update our neighbor cache, even if the neighbor already exists.
//
// Update neighbor entries to other values to check an update.
neighbor.link_address = Some(Mac::new([99, 98, 97, 96, 95, 94]));
neighbor.state = NeighborEntryState::Reachable;
neighbor.is_router = true;
let mut rs_buf = rs_msg(
dummy_config.remote_ip.get(),
dummy_config.remote_mac,
dummy_config.local_ip.get(),
);
let rs = rs_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(
dummy_config.remote_ip,
dummy_config.local_ip,
))
.unwrap();
ctx.receive_ndp_packet(
device,
dummy_config.remote_ip.get(),
dummy_config.local_ip,
rs.unwrap_ndp(),
);
// Send the Router Advertisement.
assert_eq!(ctx.dispatcher().frames_sent().len(), 1);
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_router_advertisement_transmit(device.id().into()).into()
);
assert_eq!(ctx.dispatcher().frames_sent().len(), 2);
validate_simple_ra(&ctx.dispatcher().frames_sent()[1].1, 1800);
// Source's neighbor entry should be updated.
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
);
let neighbor = ndp_state.neighbors.get_neighbor_state(&dummy_config.remote_ip).unwrap();
assert_eq!(neighbor.link_address.unwrap(), dummy_config.remote_mac);
assert_eq!(neighbor.state, NeighborEntryState::Stale);
assert_eq!(neighbor.is_router, false);
}
#[test]
fn test_router_discovery() {
let dummy_config = Ipv6::DUMMY_CONFIG;
let mut state_builder = StackStateBuilder::default();
let mut ndp_configs = NdpConfigurations::default();
ndp_configs.set_dup_addr_detect_transmits(None);
state_builder.device_builder().set_default_ndp_configs(ndp_configs.clone());
let host = DummyEventDispatcherBuilder::default()
.build_with(state_builder, DummyEventDispatcher::default());
let mut state_builder = StackStateBuilder::default();
state_builder.ipv6_builder().forward(true);
state_builder.device_builder().set_default_ndp_configs(ndp_configs);
let router = DummyEventDispatcherBuilder::default()
.build_with(state_builder, DummyEventDispatcher::default());
let device = DeviceId::new_ethernet(0);
let mut net =
DummyNetwork::new(vec![("host", host), ("router", router)].into_iter(), |ctx, _dev| {
if *ctx == "host" {
vec![("router", device, None)]
} else {
vec![("host", device, None)]
}
});
// Create an interface that is configured to be an advertising interface.
assert_eq!(
device,
net.context("router")
.state_mut()
.add_ethernet_device(dummy_config.remote_mac, Ipv6::MINIMUM_LINK_MTU.into())
);
crate::device::initialize_device(net.context("router"), device);
set_routing_enabled::<_, Ipv6>(net.context("router"), device, true);
let mut ndp_configs = NdpConfigurations::default();
let mut ndp_rc_configs = NdpRouterConfigurations::default();
ndp_rc_configs.set_should_send_advertisements(true);
ndp_configs.set_router_configurations(ndp_rc_configs);
ndp_configs.set_max_router_solicitations(None);
ndp_configs.set_dup_addr_detect_transmits(None);
crate::device::set_ndp_configurations(net.context("router"), device, ndp_configs);
// Create an interface to be the host.
assert_eq!(
device,
net.context("host")
.state_mut()
.add_ethernet_device(dummy_config.local_mac, Ipv6::MINIMUM_LINK_MTU.into())
);
crate::device::initialize_device(net.context("host"), device);
// Host should not know about the router yet.
let router_ll = dummy_config.remote_mac.to_ipv6_link_local().addr();
assert!(!StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_with(
net.context("host"),
device.id().into()
)
.has_default_router(&router_ll));
// Run the network for `MAX_RA_DELAY_TIME`.
net.run_for(MAX_RA_DELAY_TIME);
// Host should now know about the router
assert!(StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_with(
net.context("host"),
device.id().into()
)
.has_default_router(&router_ll));
// Making the router a non-advertising interface should make host remove it from its default
// router list.
set_routing_enabled::<_, Ipv6>(net.context("router"), device, false);
// Only need to run for `MAX_INITIAL_RTR_ADVERT_INTERVAL` time since router is still
// currently sending the first `MAX_INITIAL_RTR_ADVERTISEMENTS` RAs so the interval between
// RAs will be set to at max `MAX_INITIAL_RTR_ADVERT_INTERVAL`. We know the interval will
// definitely be set to `MAX_INITIAL_RTR_ADVERT_INTERVAL` because the normally generated
// value would be at minimum 200s (see `ROUTER_ADVERTISEMENTS_INTERVAL_DEFAULT`).
net.run_for(MAX_INITIAL_RTR_ADVERT_INTERVAL);
// Host should not know about the router anymore.
let router_ll = dummy_config.remote_mac.to_ipv6_link_local().addr();
assert!(!StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_with(
net.context("host"),
device.id().into()
)
.has_default_router(&router_ll));
}
#[test]
fn test_receiving_neighbor_advertisements() {
fn test_receiving_na_from_known_neighbor(
ctx: &mut Context<DummyEventDispatcher>,
src_ip: Ipv6Addr,
dst_ip: SpecifiedAddr<Ipv6Addr>,
device: DeviceId,
router_flag: bool,
solicited_flag: bool,
override_flag: bool,
mac: Option<Mac>,
expected_state: NeighborEntryState,
expected_router: bool,
expected_link_addr: Option<Mac>,
) {
let mut buf = neighbor_advertisement_message(
src_ip,
dst_ip.get(),
router_flag,
solicited_flag,
override_flag,
mac,
);
let packet =
buf.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, dst_ip)).unwrap();
ctx.receive_ndp_packet(device, src_ip, dst_ip, packet.unwrap_ndp());
let neighbor_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
ctx,
device.id().into(),
)
.neighbors
.get_neighbor_state(&src_ip)
.unwrap();
assert_eq!(neighbor_state.state, expected_state);
assert_eq!(neighbor_state.is_router, expected_router);
assert_eq!(neighbor_state.link_address, expected_link_addr);
}
let config = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let neighbor_mac = config.remote_mac;
let neighbor_ip = neighbor_mac.to_ipv6_link_local().addr().get();
let all_nodes_addr = Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.into_specified();
// Should not know about the neighbor yet.
assert!(StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into()
)
.neighbors
.get_neighbor_state(&neighbor_ip)
.is_none());
//
// Receiving unsolicited NA from a neighbor we don't care about yet should do nothing.
//
// Receive the NA.
let mut buf = neighbor_advertisement_message(
neighbor_ip,
all_nodes_addr.get(),
false,
false,
false,
None,
);
let packet = buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(neighbor_ip, all_nodes_addr))
.unwrap();
ctx.receive_ndp_packet(device, neighbor_ip, all_nodes_addr, packet.unwrap_ndp());
// We still do not know about the neighbor since the NA was unsolicited and we never were
// interested in the neighbor yet.
assert!(StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into()
)
.neighbors
.get_neighbor_state(&neighbor_ip)
.is_none());
//
// Receiving solicited NA from a neighbor we don't care about yet should do nothing (should
// never happen).
//
// Receive the NA.
let mut buf = neighbor_advertisement_message(
neighbor_ip,
all_nodes_addr.get(),
false,
true,
false,
None,
);
let packet = buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(neighbor_ip, all_nodes_addr))
.unwrap();
ctx.receive_ndp_packet(device, neighbor_ip, all_nodes_addr, packet.unwrap_ndp());
// We still do not know about the neighbor since the NA was unsolicited and we never were
// interested in the neighbor yet.
assert!(StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into()
)
.neighbors
.get_neighbor_state(&neighbor_ip)
.is_none());
//
// Receiving solicited NA from a neighbor we are trying to resolve, but no target link addr.
//
// Should do nothing (still INCOMPLETE).
//
// Create incomplete neighbor entry.
let neighbors =
&mut StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
)
.neighbors;
neighbors.add_incomplete_neighbor_state(neighbor_ip);
test_receiving_na_from_known_neighbor(
&mut ctx,
neighbor_ip,
config.local_ip,
device,
false,
true,
false,
None,
NeighborEntryState::Incomplete { transmit_counter: 1 },
false,
None,
);
//
// Receiving solicited NA from a neighbor we are resolving, but with target link addr.
//
// Should update link layer address and set state to REACHABLE.
//
test_receiving_na_from_known_neighbor(
&mut ctx,
neighbor_ip,
config.local_ip,
device,
false,
true,
false,
Some(neighbor_mac),
NeighborEntryState::Reachable,
false,
Some(neighbor_mac),
);
//
// Receive unsolicited NA from a neighbor with router flag updated (no target link addr).
//
// Should update is_router to true.
//
test_receiving_na_from_known_neighbor(
&mut ctx,
neighbor_ip,
config.local_ip,
device,
true,
false,
false,
None,
NeighborEntryState::Reachable,
true,
Some(neighbor_mac),
);
//
// Receive unsolicited NA from a neighbor without router flag set and same target link addr.
//
// Should update is_router, state should be unchanged.
//
test_receiving_na_from_known_neighbor(
&mut ctx,
neighbor_ip,
config.local_ip,
device,
false,
false,
false,
Some(neighbor_mac),
NeighborEntryState::Reachable,
false,
Some(neighbor_mac),
);
//
// Receive unsolicted NA from a neighbor with new target link addr.
//
// Should NOT update link layer addr, but set state to STALE.
//
let new_mac = Mac::new([99, 98, 97, 96, 95, 94]);
test_receiving_na_from_known_neighbor(
&mut ctx,
neighbor_ip,
config.local_ip,
device,
false,
false,
false,
Some(new_mac),
NeighborEntryState::Stale,
false,
Some(neighbor_mac),
);
//
// Receive unsolicted NA from a neighbor with new target link addr and override set.
//
// Should update link layer addr and set state to STALE.
//
test_receiving_na_from_known_neighbor(
&mut ctx,
neighbor_ip,
config.local_ip,
device,
false,
false,
true,
Some(new_mac),
NeighborEntryState::Stale,
false,
Some(new_mac),
);
//
// Receive solicted NA from a neighbor with the same link layer addr.
//
// Should not update link layer addr, but set state to REACHABLE.
//
test_receiving_na_from_known_neighbor(
&mut ctx,
neighbor_ip,
config.local_ip,
device,
false,
true,
false,
Some(new_mac),
NeighborEntryState::Reachable,
false,
Some(new_mac),
);
//
// Receive unsolicted NA from a neighbor with new target link addr and override set.
//
// Should update link layer addr, and set state to Stale.
//
test_receiving_na_from_known_neighbor(
&mut ctx,
neighbor_ip,
config.local_ip,
device,
false,
false,
true,
Some(neighbor_mac),
NeighborEntryState::Stale,
false,
Some(neighbor_mac),
);
//
// Receive solicted NA from a neighbor with new target link addr and override set.
//
// Should set state to Reachable.
//
test_receiving_na_from_known_neighbor(
&mut ctx,
neighbor_ip,
config.local_ip,
device,
false,
true,
true,
Some(neighbor_mac),
NeighborEntryState::Reachable,
false,
Some(neighbor_mac),
);
//
// Receive unsolicted NA from a neighbor with no target link addr and overide set.
//
// Should do nothing..
//
test_receiving_na_from_known_neighbor(
&mut ctx,
neighbor_ip,
config.local_ip,
device,
false,
false,
true,
None,
NeighborEntryState::Reachable,
false,
Some(neighbor_mac),
);
}
fn slaac_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(0, false, false, 0, 0, 0),
))
.serialize_vec_outer()
.unwrap()
.unwrap_b()
}
#[test]
fn test_router_stateless_address_autoconfiguration() {
//
// Routers should not perform SLAAC for global addresses.
//
let config = Ipv6::DUMMY_CONFIG;
let mut state_builder = StackStateBuilder::default();
let mut ndp_configs = NdpConfigurations::default();
ndp_configs.set_dup_addr_detect_transmits(None);
ndp_configs.set_max_router_solicitations(None);
state_builder.device_builder().set_default_ndp_configs(ndp_configs);
state_builder.ipv6_builder().forward(true);
let mut ctx = DummyEventDispatcherBuilder::default()
.build_with(state_builder, DummyEventDispatcher::default());
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
crate::device::set_routing_enabled::<_, Ipv6>(&mut ctx, device, true);
let src_mac = config.remote_mac;
let src_ip = src_mac.to_ipv6_link_local().addr().get();
let prefix = Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0]);
let prefix_length = 64;
let mut expected_addr = [1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0];
expected_addr[8..].copy_from_slice(&src_mac.to_eui64()[..]);
//
// Receive a new RA with new prefix (autonomous).
//
// Should not get a new IP.
//
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
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(device, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
0
);
// No timers.
assert!(trigger_next_timer(&mut ctx).is_none());
}
#[test]
fn test_host_stateless_address_autoconfiguration() {
let config = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let src_mac = config.remote_mac;
let src_ip = src_mac.to_ipv6_link_local().addr().get();
let prefix = Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0]);
let prefix_length = 64;
let addr_subnet = AddrSubnet::new(prefix, prefix_length).unwrap();
let mut expected_addr = [1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0];
expected_addr[8..].copy_from_slice(&config.local_mac.to_eui64()[..]);
let expected_addr = Ipv6Addr::new(expected_addr);
let expected_addr_sub = AddrSubnet::new(expected_addr, prefix_length).unwrap();
// Enable DAD for future IPs.
let mut ndp_configs = NdpConfigurations::default();
ndp_configs.set_max_router_solicitations(None);
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs);
//
// Receive a new RA with new prefix (not autonomous)
//
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
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(device, src_ip, config.local_ip, icmpv6_packet.unwrap_ndp());
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
);
// Prefix should be in our list now.
assert!(ndp_state.has_prefix(&addr_subnet));
// No new address should be formed.
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
0
);
//
// Receive a new RA with new prefix (autonomous).
//
// Should get a new IP.
//
let valid_lifetime = 10000;
let preferred_lifetime = 9000;
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
prefix,
prefix_length,
true,
true,
valid_lifetime,
preferred_lifetime,
);
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.unwrap_ndp());
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
);
assert!(ndp_state.has_prefix(&addr_subnet));
// Should have gotten a new ip.
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
1
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(*entry.addr_sub(), expected_addr_sub);
assert_eq!(entry.state(), AddressState::Tentative);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
// Make sure deprecate and invalidation timers are set.
let now = ctx.now();
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now.checked_add(Duration::from_secs(preferred_lifetime.into())).unwrap())
&& (*x.1
== NdpTimerId::new_deprecate_slaac_address(
device.id().into(),
expected_addr
)
.into()))
.count(),
1
);
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now.checked_add(Duration::from_secs(valid_lifetime.into())).unwrap())
&& (*x.1
== NdpTimerId::new_invalidate_slaac_address(
device.id().into(),
expected_addr
)
.into()))
.count(),
1
);
// Complete DAD
assert_eq!(
run_for(&mut ctx, Duration::from_secs(1)),
vec!(NdpTimerId::new_dad_ns_transmission(device.id().into(), expected_addr).into())
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(*entry.addr_sub(), expected_addr_sub);
assert_eq!(entry.state(), AddressState::Assigned);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
//
// Receive the same RA.
//
// Should not get a new IP, but keep the one generated before.
//
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
prefix,
prefix_length,
true,
true,
valid_lifetime,
preferred_lifetime,
);
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.unwrap_ndp());
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
);
assert!(ndp_state.has_prefix(&addr_subnet));
// Should not have changed.
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
1
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(*entry.addr_sub(), expected_addr_sub);
assert_eq!(entry.state(), AddressState::Assigned);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
// Timers should have been reset.
let now = ctx.now();
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now.checked_add(Duration::from_secs(preferred_lifetime.into())).unwrap())
&& (*x.1
== NdpTimerId::new_deprecate_slaac_address(
device.id().into(),
expected_addr
)
.into()))
.count(),
1
);
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now.checked_add(Duration::from_secs(valid_lifetime.into())).unwrap())
&& (*x.1
== NdpTimerId::new_invalidate_slaac_address(
device.id().into(),
expected_addr
)
.into()))
.count(),
1
);
//
// Preferred lifetime expiration.
//
// Should be marked as deprecated.
//
assert_eq!(
run_for(&mut ctx, Duration::from_secs(preferred_lifetime.into())),
vec!(NdpTimerId::new_deprecate_slaac_address(device.id().into(), expected_addr).into())
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(entry.state(), AddressState::Deprecated);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
//
// Valid lifetime expiration.
//
// Should be deleted.
//
assert_eq!(
run_for(&mut ctx, Duration::from_secs((valid_lifetime - preferred_lifetime).into())),
vec!(
NdpTimerId::new_prefix_invalidation(device.id().into(), addr_subnet).into(),
NdpTimerId::new_invalidate_slaac_address(device.id().into(), expected_addr).into()
)
);
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
0
);
// No more timers.
assert!(trigger_next_timer(&mut ctx).is_none());
}
#[test]
fn test_host_stateless_address_autoconfiguration_new_ra_with_preferred_lifetime_zero() {
let config = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let src_mac = config.remote_mac;
let src_ip = src_mac.to_ipv6_link_local().addr().get();
let prefix = Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0]);
let prefix_length = 64;
let addr_subnet = AddrSubnet::new(prefix, prefix_length).unwrap();
let mut expected_addr = [1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0];
expected_addr[8..].copy_from_slice(&config.local_mac.to_eui64()[..]);
let expected_addr = Ipv6Addr::new(expected_addr);
// Enable DAD for future IPs.
let mut ndp_configs = NdpConfigurations::default();
ndp_configs.set_max_router_solicitations(None);
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs);
//
// Receive a new RA with new prefix (autonomous).
//
// Should get a new IP.
//
let valid_lifetime = 10000;
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
prefix,
prefix_length,
true,
true,
valid_lifetime,
0,
);
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.unwrap_ndp());
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
);
assert!(ndp_state.has_prefix(&addr_subnet));
// Should NOT have gotten a new ip.
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
0
);
// Make sure deprecate and invalidation timers are set.
let now = ctx.now();
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.1
== NdpTimerId::new_deprecate_slaac_address(device.id().into(), expected_addr)
.into()))
.count(),
0
);
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now.checked_add(Duration::from_secs(valid_lifetime.into())).unwrap())
&& (*x.1
== NdpTimerId::new_invalidate_slaac_address(
device.id().into(),
expected_addr
)
.into()))
.count(),
0
);
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.1
== NdpTimerId::new_dad_ns_transmission(device.id().into(), expected_addr)
.into()))
.count(),
0
);
assert_eq!(run_for(&mut ctx, Duration::from_secs(1)).len(), 0);
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
0
);
}
#[test]
fn test_host_stateless_address_autoconfiguration_updated_ra_with_preferred_lifetime_zero() {
let config = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let src_mac = config.remote_mac;
let src_ip = src_mac.to_ipv6_link_local().addr().get();
let prefix = Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0]);
let prefix_length = 64;
let addr_subnet = AddrSubnet::new(prefix, prefix_length).unwrap();
let mut expected_addr = [1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0];
expected_addr[8..].copy_from_slice(&config.local_mac.to_eui64()[..]);
let expected_addr = Ipv6Addr::new(expected_addr);
let expected_addr_sub = AddrSubnet::new(expected_addr, prefix_length).unwrap();
// Enable DAD for future IPs.
let mut ndp_configs = NdpConfigurations::default();
ndp_configs.set_max_router_solicitations(None);
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs);
//
// Receive a new RA with new prefix (autonomous).
//
// Should get a new IP.
//
let valid_lifetime = 10000;
let preferred_lifetime = 9000;
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
prefix,
prefix_length,
true,
true,
valid_lifetime,
preferred_lifetime,
);
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.unwrap_ndp());
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
);
assert!(ndp_state.has_prefix(&addr_subnet));
// Should have gotten a new ip.
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
1
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(*entry.addr_sub(), expected_addr_sub);
assert_eq!(entry.state(), AddressState::Tentative);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
// Make sure deprecate and invalidation timers are set.
let now = ctx.now();
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now.checked_add(Duration::from_secs(preferred_lifetime.into())).unwrap())
&& (*x.1
== NdpTimerId::new_deprecate_slaac_address(
device.id().into(),
expected_addr
)
.into()))
.count(),
1
);
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now.checked_add(Duration::from_secs(valid_lifetime.into())).unwrap())
&& (*x.1
== NdpTimerId::new_invalidate_slaac_address(
device.id().into(),
expected_addr
)
.into()))
.count(),
1
);
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.1
== NdpTimerId::new_dad_ns_transmission(device.id().into(), expected_addr)
.into()))
.count(),
1
);
assert_eq!(
run_for(&mut ctx, Duration::from_secs(1)),
vec!(NdpTimerId::new_dad_ns_transmission(device.id().into(), expected_addr).into())
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(*entry.addr_sub(), expected_addr_sub);
assert_eq!(entry.state(), AddressState::Assigned);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
//
// Receive the same RA, now with preferred_lifetime = 0
//
// Should not get a new IP, but keep the one generated before.
//
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
prefix,
prefix_length,
true,
true,
valid_lifetime,
0,
);
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.unwrap_ndp());
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
);
assert!(ndp_state.has_prefix(&addr_subnet));
// Should not have changed.
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
1
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(*entry.addr_sub(), expected_addr_sub);
assert_eq!(entry.state(), AddressState::Deprecated);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
// Timers should have been reset.
let now = ctx.now();
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.1
== NdpTimerId::new_deprecate_slaac_address(device.id().into(), expected_addr)
.into()))
.count(),
0
);
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now.checked_add(Duration::from_secs(valid_lifetime.into())).unwrap())
&& (*x.1
== NdpTimerId::new_invalidate_slaac_address(
device.id().into(),
expected_addr
)
.into()))
.count(),
1
);
//
// Receive the same RA (again), still with preferred_lifetime = 0
//
// Should not get a new IP, but keep the one generated before.
//
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
prefix,
prefix_length,
true,
true,
valid_lifetime,
0,
);
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.unwrap_ndp());
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
);
assert!(ndp_state.has_prefix(&addr_subnet));
// Should not have changed.
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
1
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(*entry.addr_sub(), expected_addr_sub);
assert_eq!(entry.state(), AddressState::Deprecated);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
// Timers should have been reset.
let now = ctx.now();
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.1
== NdpTimerId::new_deprecate_slaac_address(device.id().into(), expected_addr)
.into()))
.count(),
0
);
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now.checked_add(Duration::from_secs(valid_lifetime.into())).unwrap())
&& (*x.1
== NdpTimerId::new_invalidate_slaac_address(
device.id().into(),
expected_addr
)
.into()))
.count(),
1
);
//
// Receive the same RA, now with preferred_lifetime > 0
//
// Should not get a new IP, but keep the one generated before.
//
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
prefix,
prefix_length,
true,
true,
valid_lifetime,
preferred_lifetime,
);
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.unwrap_ndp());
let ndp_state =
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
);
assert!(ndp_state.has_prefix(&addr_subnet));
// Should not have changed.
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
1
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(*entry.addr_sub(), expected_addr_sub);
assert_eq!(entry.state(), AddressState::Assigned);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
// Make sure deprecate and invalidation timers are set.
let now = ctx.now();
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now.checked_add(Duration::from_secs(preferred_lifetime.into())).unwrap())
&& (*x.1
== NdpTimerId::new_deprecate_slaac_address(
device.id().into(),
expected_addr
)
.into()))
.count(),
1
);
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now.checked_add(Duration::from_secs(valid_lifetime.into())).unwrap())
&& (*x.1
== NdpTimerId::new_invalidate_slaac_address(
device.id().into(),
expected_addr
)
.into()))
.count(),
1
);
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.1
== NdpTimerId::new_dad_ns_transmission(device.id().into(), expected_addr)
.into()))
.count(),
0
);
assert_eq!(run_for(&mut ctx, Duration::from_secs(1)), vec!());
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(*entry.addr_sub(), expected_addr_sub);
assert_eq!(entry.state(), AddressState::Assigned);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
//
// Preferred lifetime expiration.
//
// Should be marked as deprecated.
//
assert_eq!(
run_for(&mut ctx, Duration::from_secs(preferred_lifetime.into())),
vec!(NdpTimerId::new_deprecate_slaac_address(device.id().into(), expected_addr).into())
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(entry.state(), AddressState::Deprecated);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
//
// Valid lifetime expiration.
//
// Should be deleted.
//
assert_eq!(
run_for(&mut ctx, Duration::from_secs((valid_lifetime - preferred_lifetime).into())),
vec!(
NdpTimerId::new_prefix_invalidation(device.id().into(), addr_subnet).into(),
NdpTimerId::new_invalidate_slaac_address(device.id().into(), expected_addr).into()
)
);
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
0
);
// No more timers.
assert!(trigger_next_timer(&mut ctx).is_none());
}
#[test]
fn test_host_slaac_and_manual_address_and_prefix_conflict() {
//
// SLAAC should not overwrite any manually added addresses that may conflict with the
// generated SLAAC address.
//
let config = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let src_mac = config.remote_mac;
let src_ip = src_mac.to_ipv6_link_local().addr().get();
let prefix = Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0]);
let prefix_length = 64;
let mut expected_addr = [1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0];
expected_addr[8..].copy_from_slice(&config.local_mac.to_eui64()[..]);
let expected_addr = Ipv6Addr::new(expected_addr);
let expected_addr_sub = AddrSubnet::new(expected_addr, prefix_length).unwrap();
// Manually give the device the expected address/subnet
add_ip_addr_subnet(&mut ctx, device, expected_addr_sub).unwrap();
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
1
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(entry.state(), AddressState::Assigned);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Manual);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
//
// Receive a new RA with new prefix (autonomous).
//
// Should not get a new IP.
//
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
prefix,
prefix_length,
false,
true,
10000,
9000,
);
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.unwrap_ndp());
// Address state and configuration type should not have changed.
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
1
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(*entry.addr_sub(), expected_addr_sub);
assert_eq!(entry.state(), AddressState::Assigned);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Manual);
// No new timers were added.
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
}
#[test]
fn test_host_slaac_and_manual_address_conflict() {
//
// SLAAC should not overwrite any manually added addresses that may conflict with the
// generated SLAAC address, even if the manually added address has a different prefix.
//
let config = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let src_mac = config.remote_mac;
let src_ip = src_mac.to_ipv6_link_local().addr().get();
let prefix = Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0]);
let prefix_length = 64;
let mut manual_addr_sub = [1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0];
manual_addr_sub[8..].copy_from_slice(&config.local_mac.to_eui64()[..]);
let manual_addr_sub = Ipv6Addr::new(manual_addr_sub);
let manual_addr_sub = AddrSubnet::new(manual_addr_sub, prefix_length + 10).unwrap();
// Manually give the device the expected address but with a different prefix.
add_ip_addr_subnet(&mut ctx, device, manual_addr_sub).unwrap();
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
1
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(*entry.addr_sub(), manual_addr_sub);
assert_eq!(entry.state(), AddressState::Assigned);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Manual);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
//
// Receive a new RA with new prefix (autonomous).
//
// Should not get a new IP.
//
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
prefix,
prefix_length,
false,
true,
10000,
9000,
);
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.unwrap_ndp());
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
1
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
// Address state and configuration type should not have changed.
assert_eq!(*entry.addr_sub(), manual_addr_sub);
assert_eq!(entry.state(), AddressState::Assigned);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Manual);
// No new timers were added.
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
}
#[test]
fn test_host_slaac_and_manual_address_prefix_conflict() {
//
// SLAAC should not overwrite any manually added addresses that use the same prefix
// as a SLAAC generated one..
//
let config = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let src_mac = config.remote_mac;
let src_ip = src_mac.to_ipv6_link_local().addr().get();
let prefix = Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0]);
let prefix_length = 64;
let manual_addr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let manual_addr = Ipv6Addr::new(manual_addr);
let manual_addr_sub = AddrSubnet::new(manual_addr, prefix_length).unwrap();
let mut slaac_addr = [1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0];
slaac_addr[8..].copy_from_slice(&config.local_mac.to_eui64());
let slaac_addr = Ipv6Addr::new(slaac_addr);
let slaac_addr_sub = AddrSubnet::new(slaac_addr, prefix_length).unwrap();
// Manually give the device the expected address with the same prefix.
add_ip_addr_subnet(&mut ctx, device, manual_addr_sub).unwrap();
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
1
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(entry.state(), AddressState::Assigned);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Manual);
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
//
// Receive a new RA with new prefix (autonomous).
//
// Should not get a new IP.
//
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
prefix,
prefix_length,
false,
true,
10000,
9000,
);
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.unwrap_ndp());
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
2
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.nth(0)
.unwrap();
// Address state and configuration type should not have changed.
assert_eq!(*entry.addr_sub(), manual_addr_sub);
assert_eq!(entry.state(), AddressState::Assigned);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Manual);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
// Address state and configuration type should not have changed.
assert_eq!(*entry.addr_sub(), slaac_addr_sub);
assert_eq!(entry.state(), AddressState::Assigned);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
// Address invalidation timers were added.
assert_eq!(ctx.dispatcher().timer_events().count(), 2);
}
#[test]
fn test_host_slaac_invalid_prefix_information() {
let config = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let src_mac = config.remote_mac;
let src_ip = src_mac.to_ipv6_link_local().addr().get();
let prefix = Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0]);
let prefix_length = 64;
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
0
);
//
// Receive a new RA with new prefix (autonomous), but preferred lifetime is greater than
// valid.
//
// Should not get a new IP.
//
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
prefix,
prefix_length,
false,
true,
9000,
10000,
);
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.unwrap_ndp());
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
0
);
// Address invalidation timers were added.
assert_eq!(ctx.dispatcher().timer_events().count(), 0);
}
#[test]
fn test_host_slaac_address_deprecate_while_tentative() {
//
// Invalidate an address right away if we attempt to deprecate a tentative address.
//
let config = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let src_mac = config.remote_mac;
let src_ip = src_mac.to_ipv6_link_local().addr().get();
let prefix = Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0]);
let prefix_length = 64;
let mut expected_addr = [1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0];
expected_addr[8..].copy_from_slice(&config.local_mac.to_eui64()[..]);
let expected_addr = Ipv6Addr::new(expected_addr);
let expected_addr_sub = AddrSubnet::new(expected_addr, prefix_length).unwrap();
// Have no addresses yet.
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
0
);
// Enable DAD for future IPs.
let mut ndp_configs = NdpConfigurations::default();
ndp_configs.set_max_router_solicitations(None);
crate::device::set_ndp_configurations(&mut ctx, device, ndp_configs);
// Set the retransmit timer between neighbor solicitations to be greater than the preferred
// lifetime of the prefix.
StateContext::<NdpState<EthernetLinkDevice, DummyInstant>, _>::get_state_mut_with(
&mut ctx,
device.id().into(),
)
.set_retrans_timer(Duration::from_secs(10));
//
// Receive a new RA with new prefix (autonomous).
//
// Should get a new IP and set prefered lifetime to 1s.
//
let valid_lifetime = 2;
let preferred_lifetime = 1;
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
config.local_ip.get(),
prefix,
prefix_length,
false,
true,
valid_lifetime,
preferred_lifetime,
);
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.unwrap_ndp());
// Should have gotten a new ip.
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
1
);
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.last()
.unwrap();
assert_eq!(*entry.addr_sub(), expected_addr_sub);
assert_eq!(entry.state(), AddressState::Tentative);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
// Make sure deprecate and invalidation timers are set.
let now = ctx.now();
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now.checked_add(Duration::from_secs(preferred_lifetime.into())).unwrap())
&& (*x.1
== NdpTimerId::new_deprecate_slaac_address(
device.id().into(),
expected_addr
)
.into()))
.count(),
1
);
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now.checked_add(Duration::from_secs(valid_lifetime.into())).unwrap())
&& (*x.1
== NdpTimerId::new_invalidate_slaac_address(
device.id().into(),
expected_addr
)
.into()))
.count(),
1
);
// Trigger the deprecation timer.
assert_eq!(
trigger_next_timer(&mut ctx).unwrap(),
NdpTimerId::new_deprecate_slaac_address(device.id().into(), expected_addr).into()
);
// At this point, the address (that was tentative) should just be invalidated (removed)
// since we should not have any existing connections using the tentative address.
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
0
);
// No more timers.
assert!(trigger_next_timer(&mut ctx).is_none());
}
#[test]
fn test_host_slaac_valid_lifetime_updates() {
//
// Make sure we update the valid lifetime only in certain scenarios
// to prevent denial-of-service attacks as outlined in RFC 4862 section
// 5.5.3.e. Note, the preferred lifetime should always be updated.
//
fn inner_test(
ctx: &mut Context<DummyEventDispatcher>,
device: DeviceId,
src_ip: Ipv6Addr,
dst_ip: SpecifiedAddr<Ipv6Addr>,
subnet: Subnet<Ipv6Addr>,
addr_sub: AddrSubnet<Ipv6Addr>,
preferred_lifetime: u32,
valid_lifetime: u32,
expected_valid_lifetime: u32,
) {
let prefix = subnet.network();
let prefix_length = subnet.prefix();
let mut icmpv6_packet_buf = slaac_packet_buf(
src_ip,
dst_ip.get(),
prefix,
prefix_length,
false,
true,
valid_lifetime,
preferred_lifetime,
);
let icmpv6_packet = icmpv6_packet_buf
.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, dst_ip))
.unwrap();
ctx.receive_ndp_packet(device, src_ip, dst_ip, icmpv6_packet.unwrap_ndp());
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(ctx, device.id().into())
.count(),
1
);
let now = ctx.now();
let valid_until =
now.checked_add(Duration::from_secs(expected_valid_lifetime.into())).unwrap();
let entry =
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(ctx, device.id().into())
.last()
.unwrap();
assert_eq!(*entry.addr_sub(), addr_sub);
assert_eq!(entry.state(), AddressState::Assigned);
assert_eq!(entry.configuration_type(), AddressConfigurationType::Slaac);
assert_eq!(entry.valid_until().unwrap(), valid_until);
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0
== now
.checked_add(Duration::from_secs(preferred_lifetime.into()))
.unwrap())
&& (*x.1
== NdpTimerId::new_deprecate_slaac_address(
device.id().into(),
addr_sub.addr().get()
)
.into()))
.count(),
1
);
assert_eq!(
ctx.dispatcher()
.timer_events()
.filter(|x| (*x.0 == valid_until)
&& (*x.1
== NdpTimerId::new_invalidate_slaac_address(
device.id().into(),
addr_sub.addr().get()
)
.into()))
.count(),
1
);
}
let config = Ipv6::DUMMY_CONFIG;
let mut ctx = DummyEventDispatcherBuilder::default().build::<DummyEventDispatcher>();
let device =
ctx.state_mut().add_ethernet_device(config.local_mac, Ipv6::MINIMUM_LINK_MTU.into());
crate::device::initialize_device(&mut ctx, device);
let src_mac = config.remote_mac;
let src_ip = src_mac.to_ipv6_link_local().addr().get();
let dst_ip = config.local_ip;
let prefix = Ipv6Addr::new([1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0]);
let prefix_length = 64;
let subnet = Subnet::new(prefix, prefix_length).unwrap();
let mut expected_addr = [1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0];
expected_addr[8..].copy_from_slice(&config.local_mac.to_eui64()[..]);
let expected_addr = Ipv6Addr::new(expected_addr);
let expected_addr_sub = AddrSubnet::new(expected_addr, prefix_length).unwrap();
// Have no addresses yet.
assert_eq!(
NdpContext::<EthernetLinkDevice>::get_ipv6_addr_entries(&ctx, device.id().into())
.count(),
0
);
//
// Receive a new RA with new prefix (autonomous).
//
// Should get a new IP and set prefered lifetime to 1s.
//
// Make sure deprecate and invalidation timers are set.
inner_test(&mut ctx, device, src_ip, dst_ip, subnet, expected_addr_sub, 30, 60, 60);
// If the valid lifetime is greater than the remaining lifetime, update the valid
// lifetime.
inner_test(&mut ctx, device, src_ip, dst_ip, subnet, expected_addr_sub, 70, 70, 70);
// If the valid lifetime is greater than 2 hrs, update the valid lifetime.
inner_test(&mut ctx, device, src_ip, dst_ip, subnet, expected_addr_sub, 1001, 7201, 7201);
// Make remaining lifetime < 2 hrs.
assert_eq!(run_for(&mut ctx, Duration::from_secs(1000)).len(), 0);
// If the remaining lifetime is <= 2 hrs & valid lifetime is less than that, don't update
// valid lifetime
inner_test(&mut ctx, device, src_ip, dst_ip, subnet, expected_addr_sub, 1000, 2000, 6201);
// Make the remaining lifetime > 2 hours.
inner_test(&mut ctx, device, src_ip, dst_ip, subnet, expected_addr_sub, 1000, 10800, 10800);
// If the remaining lifetime is > 2 hours, and new valid lifetime is < 2 hours, set the
// valid lifetime to 2 hours.
inner_test(&mut ctx, device, src_ip, dst_ip, subnet, expected_addr_sub, 1000, 1000, 7200);
// If the remaining lifetime is <= 2 hrs & valid lifetime is less than that, don't update
// valid lifetime
inner_test(&mut ctx, device, src_ip, dst_ip, subnet, expected_addr_sub, 1000, 2000, 7200);
// Increase valid lifetime twice while it is greater than 2 hours.
inner_test(&mut ctx, device, src_ip, dst_ip, subnet, expected_addr_sub, 1001, 7201, 7201);
inner_test(&mut ctx, device, src_ip, dst_ip, subnet, expected_addr_sub, 1001, 7202, 7202);
// Make remaining lifetime < 2 hrs.
assert_eq!(run_for(&mut ctx, Duration::from_secs(1000)).len(), 0);
// If the remaining lifetime is <= 2 hrs & valid lifetime is less than that, don't update
// valid lifetime.
inner_test(&mut ctx, device, src_ip, dst_ip, subnet, expected_addr_sub, 1001, 6202, 6202);
// Increase valid lifetime twice while it is less than 2 hours.
inner_test(&mut ctx, device, src_ip, dst_ip, subnet, expected_addr_sub, 1001, 6203, 6203);
inner_test(&mut ctx, device, src_ip, dst_ip, subnet, expected_addr_sub, 1001, 6204, 6204);
}
}