blob: f86595ba85616fc3d7094a175c3babe13df75f79 [file] [log] [blame]
// Copyright 2022 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.
//! Duplicate Address Detection.
use core::num::NonZeroU16;
use net_types::{
ip::{Ipv4, Ipv6, Ipv6Addr},
MulticastAddr, UnicastAddr, Witness as _,
};
use packet_formats::{icmp::ndp::NeighborSolicitation, utils::NonZeroDuration};
use tracing::debug;
use crate::{
context::{
CoreEventContext, CoreTimerContext, EventContext, HandleableTimer, TimerBindingsTypes,
TimerContext,
},
device::{self, AnyDevice, DeviceIdContext, StrongId as _, WeakId as _},
ip::device::{
state::Ipv6DadState, IpAddressId as _, IpAddressState, IpDeviceAddressIdContext,
IpDeviceIpExt, WeakIpAddressId,
},
};
/// A timer ID for duplicate address detection.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub struct DadTimerId<D: device::WeakId, A: WeakIpAddressId<Ipv6Addr>> {
pub(crate) device_id: D,
pub(crate) addr: A,
}
impl<D: device::WeakId, A: WeakIpAddressId<Ipv6Addr>> DadTimerId<D, A> {
pub(super) fn device_id(&self) -> &D {
let Self { device_id, addr: _ } = self;
device_id
}
}
pub(super) struct DadAddressStateRef<'a, CC, BT: DadBindingsTypes> {
/// A mutable reference to an address' state.
///
/// `None` if the address is not recognized.
pub(super) dad_state: &'a mut Ipv6DadState<BT>,
/// The execution context available with the address's DAD state.
pub(super) core_ctx: &'a mut CC,
}
/// Holds references to state associated with duplicate address detection.
pub(super) struct DadStateRef<'a, CC, BT: DadBindingsTypes> {
pub(super) state: DadAddressStateRef<'a, CC, BT>,
/// The time between DAD message retransmissions.
pub(super) retrans_timer: &'a NonZeroDuration,
/// The maximum number of DAD messages to send.
pub(super) max_dad_transmits: &'a Option<NonZeroU16>,
}
/// The execution context while performing DAD.
pub(super) trait DadAddressContext<BC>: IpDeviceAddressIdContext<Ipv6> {
/// Calls the function with a mutable reference to the address's assigned
/// flag.
fn with_address_assigned<O, F: FnOnce(&mut bool) -> O>(
&mut self,
device_id: &Self::DeviceId,
addr: &Self::AddressId,
cb: F,
) -> O;
/// Joins the multicast group on the device.
fn join_multicast_group(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
multicast_addr: MulticastAddr<Ipv6Addr>,
);
/// Leaves the multicast group on the device.
fn leave_multicast_group(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
multicast_addr: MulticastAddr<Ipv6Addr>,
);
}
/// The execution context for DAD.
pub(super) trait DadContext<BC: DadBindingsTypes>:
IpDeviceAddressIdContext<Ipv6>
+ DeviceIdContext<AnyDevice>
+ CoreTimerContext<DadTimerId<Self::WeakDeviceId, Self::WeakAddressId>, BC>
+ CoreEventContext<DadEvent<Self::DeviceId>>
{
type DadAddressCtx<'a>: DadAddressContext<
BC,
DeviceId = Self::DeviceId,
AddressId = Self::AddressId,
>;
/// Calls the function with the DAD state associated with the address.
fn with_dad_state<O, F: FnOnce(DadStateRef<'_, Self::DadAddressCtx<'_>, BC>) -> O>(
&mut self,
device_id: &Self::DeviceId,
addr: &Self::AddressId,
cb: F,
) -> O;
/// Sends an NDP Neighbor Solicitation message for DAD to the local-link.
///
/// The message will be sent with the unspecified (all-zeroes) source
/// address.
fn send_dad_packet(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
dst_ip: MulticastAddr<Ipv6Addr>,
message: NeighborSolicitation,
) -> Result<(), ()>;
}
#[derive(Debug, Eq, Hash, PartialEq)]
/// Events generated by duplicate address detection.
pub enum DadEvent<DeviceId> {
/// Duplicate address detection completed and the address is assigned.
AddressAssigned {
/// Device the address belongs to.
device: DeviceId,
/// The address that moved to the assigned state.
addr: UnicastAddr<Ipv6Addr>,
},
}
/// The bindings types for DAD.
pub trait DadBindingsTypes: TimerBindingsTypes {}
impl<BT> DadBindingsTypes for BT where BT: TimerBindingsTypes {}
/// The bindings execution context for DAD.
///
/// The type parameter `E` is tied by [`DadContext`] so that [`DadEvent`] can be
/// transformed into an event that is more meaningful to bindings.
pub trait DadBindingsContext<E>: DadBindingsTypes + TimerContext + EventContext<E> {}
impl<E, BC> DadBindingsContext<E> for BC where BC: DadBindingsTypes + TimerContext + EventContext<E> {}
/// An implementation for Duplicate Address Detection.
pub trait DadHandler<I: IpDeviceIpExt, BC>:
DeviceIdContext<AnyDevice> + IpDeviceAddressIdContext<I>
{
// TODO(https://fxbug.dev/42077260): This can probably be removed when we
// can do DAD over IPv4.
const INITIAL_ADDRESS_STATE: IpAddressState;
/// Starts duplicate address detection.
///
/// # Panics
///
/// Panics if tentative state for the address is not found.
fn start_duplicate_address_detection(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
addr: &Self::AddressId,
);
/// Stops duplicate address detection.
///
/// Does nothing if DAD is not being performed on the address.
fn stop_duplicate_address_detection(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
addr: &Self::AddressId,
);
}
enum DoDadVariation {
Start,
Continue,
}
fn do_duplicate_address_detection<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<BC>>(
core_ctx: &mut CC,
bindings_ctx: &mut BC,
device_id: &CC::DeviceId,
addr: &CC::AddressId,
variation: DoDadVariation,
) {
let send_msg = core_ctx.with_dad_state(
device_id,
addr,
|DadStateRef { state, retrans_timer, max_dad_transmits }| {
let DadAddressStateRef { dad_state, core_ctx } = state;
match variation {
DoDadVariation::Start => {
// Mark the address as tentative/unassigned before joining
// the group so that the address is not used as the source
// for any outgoing MLD message.
*dad_state = Ipv6DadState::Tentative {
dad_transmits_remaining: *max_dad_transmits,
timer: CC::new_timer(
bindings_ctx,
DadTimerId { device_id: device_id.downgrade(), addr: addr.downgrade() },
),
};
core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = false);
// As per RFC 4861 section 5.6.2,
//
// Before sending a Neighbor Solicitation, an interface MUST join
// the all-nodes multicast address and the solicited-node
// multicast address of the tentative address.
//
// Note that we join the all-nodes multicast address on interface
// enable.
core_ctx.join_multicast_group(
bindings_ctx,
device_id,
addr.addr().addr().to_solicited_node_address(),
);
}
DoDadVariation::Continue => {}
}
let (remaining, timer) = match dad_state {
Ipv6DadState::Tentative { dad_transmits_remaining, timer } => {
(dad_transmits_remaining, timer)
}
Ipv6DadState::Uninitialized | Ipv6DadState::Assigned => {
panic!("expected address to be tentative; addr={addr:?}")
}
};
match remaining {
None => {
*dad_state = Ipv6DadState::Assigned;
core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = true);
CC::on_event(
bindings_ctx,
DadEvent::AddressAssigned {
device: device_id.clone(),
addr: addr.addr_sub().addr().get(),
},
);
false
}
Some(non_zero_remaining) => {
*remaining = NonZeroU16::new(non_zero_remaining.get() - 1);
// Per RFC 4862 section 5.1,
//
// DupAddrDetectTransmits ...
// Autoconfiguration also assumes the presence of the variable
// RetransTimer as defined in [RFC4861]. For autoconfiguration
// purposes, RetransTimer specifies the delay between
// consecutive Neighbor Solicitation transmissions performed
// during Duplicate Address Detection (if
// DupAddrDetectTransmits is greater than 1), as well as the
// time a node waits after sending the last Neighbor
// Solicitation before ending the Duplicate Address Detection
// process.
assert_eq!(
bindings_ctx.schedule_timer(retrans_timer.get(), timer),
None,
"Unexpected DAD timer; addr={}, device_id={:?}",
addr.addr(),
device_id
);
debug!(
"performing DAD for {}; {} tries left",
addr.addr(),
remaining.map_or(0, NonZeroU16::get)
);
true
}
}
},
);
if !send_msg {
return;
}
// Do not include the source link-layer option when the NS
// message as DAD messages are sent with the unspecified source
// address which must not hold a source link-layer option.
//
// As per RFC 4861 section 4.3,
//
// Possible options:
//
// Source link-layer address
// The link-layer address for the sender. MUST NOT be
// included when the source IP address is the
// unspecified address. Otherwise, on link layers
// that have addresses this option MUST be included in
// multicast solicitations and SHOULD be included in
// unicast solicitations.
//
// TODO(https://fxbug.dev/42165912): Either panic or guarantee that this error
// can't happen statically.
let dst_ip = addr.addr().addr().to_solicited_node_address();
let _: Result<(), _> = core_ctx.send_dad_packet(
bindings_ctx,
device_id,
dst_ip,
NeighborSolicitation::new(addr.addr().addr()),
);
}
// TODO(https://fxbug.dev/42077260): Actually support DAD for IPv4.
impl<BC, CC> DadHandler<Ipv4, BC> for CC
where
CC: IpDeviceAddressIdContext<Ipv4> + DeviceIdContext<AnyDevice>,
{
const INITIAL_ADDRESS_STATE: IpAddressState = IpAddressState::Assigned;
fn start_duplicate_address_detection(
&mut self,
_bindings_ctx: &mut BC,
_device_id: &Self::DeviceId,
_addr: &Self::AddressId,
) {
}
fn stop_duplicate_address_detection(
&mut self,
_bindings_ctx: &mut BC,
_device_id: &Self::DeviceId,
_addr: &Self::AddressId,
) {
}
}
impl<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<BC>> DadHandler<Ipv6, BC> for CC {
const INITIAL_ADDRESS_STATE: IpAddressState = IpAddressState::Tentative;
fn start_duplicate_address_detection(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
addr: &Self::AddressId,
) {
do_duplicate_address_detection(self, bindings_ctx, device_id, addr, DoDadVariation::Start)
}
fn stop_duplicate_address_detection(
&mut self,
bindings_ctx: &mut BC,
device_id: &Self::DeviceId,
addr: &Self::AddressId,
) {
self.with_dad_state(
device_id,
addr,
|DadStateRef { state, retrans_timer: _, max_dad_transmits: _ }| {
let DadAddressStateRef { dad_state, core_ctx } = state;
let leave_group = match dad_state {
Ipv6DadState::Assigned => true,
Ipv6DadState::Tentative { dad_transmits_remaining: _, timer } => {
assert_ne!(bindings_ctx.cancel_timer(timer), None);
true
}
Ipv6DadState::Uninitialized => false,
};
// Undo the work we did when starting/performing DAD by putting
// the address back into a tentative/unassigned state and
// leaving the solicited node multicast group. We mark the
// address as tentative/unassigned before leaving the group so
// that the address is not used as the source for any outgoing
// MLD message.
*dad_state = Ipv6DadState::Uninitialized;
core_ctx.with_address_assigned(device_id, addr, |assigned| *assigned = false);
if leave_group {
core_ctx.leave_multicast_group(
bindings_ctx,
device_id,
addr.addr().addr().to_solicited_node_address(),
);
}
},
)
}
}
impl<BC: DadBindingsContext<CC::OuterEvent>, CC: DadContext<BC>> HandleableTimer<CC, BC>
for DadTimerId<CC::WeakDeviceId, CC::WeakAddressId>
{
fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC) {
let Self { device_id, addr } = self;
let Some(device_id) = device_id.upgrade() else {
return;
};
let Some(addr_id) = addr.upgrade() else {
return;
};
do_duplicate_address_detection(
core_ctx,
bindings_ctx,
&device_id,
&addr_id,
DoDadVariation::Continue,
)
}
}
#[cfg(test)]
mod tests {
use alloc::collections::hash_map::{Entry, HashMap};
use core::time::Duration;
use assert_matches::assert_matches;
use net_types::{
ip::{AddrSubnet, IpAddress as _},
Witness as _,
};
use packet::EmptyBuf;
use packet_formats::icmp::ndp::Options;
use super::*;
use crate::{
context::{
testutil::{FakeBindingsCtx, FakeCoreCtx, FakeCtx, FakeTimerCtxExt as _},
InstantContext as _, SendFrameContext as _, TimerHandler,
},
device::testutil::{FakeDeviceId, FakeWeakDeviceId},
ip::{
device::{testutil::FakeWeakAddressId, Ipv6DeviceAddr},
testutil::FakeIpDeviceIdCtx,
},
};
struct FakeDadAddressContext {
addr: UnicastAddr<Ipv6Addr>,
assigned: bool,
groups: HashMap<MulticastAddr<Ipv6Addr>, usize>,
ip_device_id_ctx: FakeIpDeviceIdCtx<FakeDeviceId>,
}
type FakeAddressCtxImpl = FakeCoreCtx<FakeDadAddressContext, (), FakeDeviceId>;
impl AsRef<FakeIpDeviceIdCtx<FakeDeviceId>> for FakeDadAddressContext {
fn as_ref(&self) -> &FakeIpDeviceIdCtx<FakeDeviceId> {
&self.ip_device_id_ctx
}
}
impl IpDeviceAddressIdContext<Ipv6> for FakeAddressCtxImpl {
type AddressId = AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>;
type WeakAddressId = FakeWeakAddressId<AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>>;
}
impl DadAddressContext<FakeBindingsCtxImpl> for FakeAddressCtxImpl {
fn with_address_assigned<O, F: FnOnce(&mut bool) -> O>(
&mut self,
&FakeDeviceId: &Self::DeviceId,
request_addr: &Self::AddressId,
cb: F,
) -> O {
let FakeDadAddressContext { addr, assigned, groups: _, ip_device_id_ctx: _ } =
self.get_mut();
assert_eq!(*request_addr.addr(), *addr);
cb(assigned)
}
fn join_multicast_group(
&mut self,
_bindings_ctx: &mut FakeBindingsCtxImpl,
&FakeDeviceId: &Self::DeviceId,
multicast_addr: MulticastAddr<Ipv6Addr>,
) {
let FakeDadAddressContext { addr: _, assigned: _, groups, ip_device_id_ctx: _ } =
self.get_mut();
*groups.entry(multicast_addr).or_default() += 1;
}
fn leave_multicast_group(
&mut self,
_bindings_ctx: &mut FakeBindingsCtxImpl,
&FakeDeviceId: &Self::DeviceId,
multicast_addr: MulticastAddr<Ipv6Addr>,
) {
let FakeDadAddressContext { addr: _, assigned: _, groups, ip_device_id_ctx: _ } =
self.get_mut();
match groups.entry(multicast_addr) {
Entry::Vacant(_) => {}
Entry::Occupied(mut e) => {
let v = e.get_mut();
const COUNT_BEFORE_REMOVE: usize = 1;
if *v == COUNT_BEFORE_REMOVE {
assert_eq!(e.remove(), COUNT_BEFORE_REMOVE);
} else {
*v -= 1
}
}
}
}
}
struct FakeDadContext {
state: Ipv6DadState<FakeBindingsCtxImpl>,
retrans_timer: NonZeroDuration,
max_dad_transmits: Option<NonZeroU16>,
address_ctx: FakeAddressCtxImpl,
}
#[derive(Debug)]
struct DadMessageMeta {
dst_ip: MulticastAddr<Ipv6Addr>,
message: NeighborSolicitation,
}
impl AsRef<FakeIpDeviceIdCtx<FakeDeviceId>> for FakeDadContext {
fn as_ref(&self) -> &FakeIpDeviceIdCtx<FakeDeviceId> {
self.address_ctx.get_ref().as_ref()
}
}
type TestDadTimerId = DadTimerId<
FakeWeakDeviceId<FakeDeviceId>,
FakeWeakAddressId<AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>>,
>;
type FakeBindingsCtxImpl = FakeBindingsCtx<TestDadTimerId, DadEvent<FakeDeviceId>, (), ()>;
type FakeCoreCtxImpl = FakeCoreCtx<FakeDadContext, DadMessageMeta, FakeDeviceId>;
fn get_address_id(addr: Ipv6Addr) -> AddrSubnet<Ipv6Addr, Ipv6DeviceAddr> {
AddrSubnet::new(addr, Ipv6Addr::BYTES * 8).unwrap()
}
impl IpDeviceAddressIdContext<Ipv6> for FakeCoreCtxImpl {
type AddressId = AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>;
type WeakAddressId = FakeWeakAddressId<AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>>;
}
impl CoreTimerContext<TestDadTimerId, FakeBindingsCtxImpl> for FakeCoreCtxImpl {
fn convert_timer(dispatch_id: TestDadTimerId) -> TestDadTimerId {
dispatch_id
}
}
impl CoreEventContext<DadEvent<FakeDeviceId>> for FakeCoreCtxImpl {
type OuterEvent = DadEvent<FakeDeviceId>;
fn convert_event(event: DadEvent<FakeDeviceId>) -> DadEvent<FakeDeviceId> {
event
}
}
impl DadContext<FakeBindingsCtxImpl> for FakeCoreCtxImpl {
type DadAddressCtx<'a> = FakeAddressCtxImpl;
fn with_dad_state<
O,
F: FnOnce(DadStateRef<'_, Self::DadAddressCtx<'_>, FakeBindingsCtxImpl>) -> O,
>(
&mut self,
&FakeDeviceId: &FakeDeviceId,
request_addr: &Self::AddressId,
cb: F,
) -> O {
let FakeDadContext { state, retrans_timer, max_dad_transmits, address_ctx } =
self.get_mut();
let ctx_addr = address_ctx.get_ref().addr;
let requested_addr = request_addr.addr().get();
assert!(
ctx_addr == requested_addr,
"invalid address {requested_addr} expected {ctx_addr}"
);
cb(DadStateRef {
state: DadAddressStateRef { dad_state: state, core_ctx: address_ctx },
retrans_timer,
max_dad_transmits,
})
}
fn send_dad_packet(
&mut self,
bindings_ctx: &mut FakeBindingsCtxImpl,
&FakeDeviceId: &FakeDeviceId,
dst_ip: MulticastAddr<Ipv6Addr>,
message: NeighborSolicitation,
) -> Result<(), ()> {
self.send_frame(bindings_ctx, DadMessageMeta { dst_ip, message }, EmptyBuf)
.map_err(|EmptyBuf| ())
}
}
const RETRANS_TIMER: NonZeroDuration =
unsafe { NonZeroDuration::new_unchecked(Duration::from_secs(1)) };
const DAD_ADDRESS: UnicastAddr<Ipv6Addr> =
unsafe { UnicastAddr::new_unchecked(Ipv6Addr::new([0xa, 0, 0, 0, 0, 0, 0, 1])) };
#[test]
#[should_panic(expected = "expected address to be tentative")]
fn panic_non_tentative_address_handle_timer() {
let FakeCtx { mut core_ctx, mut bindings_ctx } =
FakeCtx::with_core_ctx(FakeCoreCtxImpl::with_state(FakeDadContext {
state: Ipv6DadState::Assigned,
retrans_timer: RETRANS_TIMER,
max_dad_transmits: None,
address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
addr: DAD_ADDRESS,
assigned: false,
groups: HashMap::default(),
ip_device_id_ctx: Default::default(),
}),
}));
TimerHandler::handle_timer(&mut core_ctx, &mut bindings_ctx, dad_timer_id());
}
#[test]
fn dad_disabled() {
let FakeCtx { mut core_ctx, mut bindings_ctx } =
FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
FakeCoreCtxImpl::with_state(FakeDadContext {
state: Ipv6DadState::Tentative {
dad_transmits_remaining: None,
timer: bindings_ctx.new_timer(dad_timer_id()),
},
retrans_timer: RETRANS_TIMER,
max_dad_transmits: None,
address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
addr: DAD_ADDRESS,
assigned: false,
groups: HashMap::default(),
ip_device_id_ctx: Default::default(),
}),
})
});
DadHandler::start_duplicate_address_detection(
&mut core_ctx,
&mut bindings_ctx,
&FakeDeviceId,
&get_address_id(DAD_ADDRESS.get()),
);
let FakeDadContext { state, retrans_timer: _, max_dad_transmits: _, address_ctx } =
core_ctx.get_ref();
assert_matches!(*state, Ipv6DadState::Assigned);
let FakeDadAddressContext { addr: _, assigned, groups, ip_device_id_ctx: _ } =
address_ctx.get_ref();
assert!(*assigned);
assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
assert_eq!(
bindings_ctx.take_events(),
&[DadEvent::AddressAssigned { device: FakeDeviceId, addr: DAD_ADDRESS }][..]
);
}
fn dad_timer_id() -> TestDadTimerId {
DadTimerId {
addr: FakeWeakAddressId(get_address_id(DAD_ADDRESS.get())),
device_id: FakeWeakDeviceId(FakeDeviceId),
}
}
fn check_dad(
core_ctx: &FakeCoreCtxImpl,
bindings_ctx: &FakeBindingsCtxImpl,
frames_len: usize,
dad_transmits_remaining: Option<NonZeroU16>,
retrans_timer: NonZeroDuration,
) {
let FakeDadContext { state, retrans_timer: _, max_dad_transmits: _, address_ctx } =
core_ctx.get_ref();
assert_matches!(*state, Ipv6DadState::Tentative {
dad_transmits_remaining: got,
timer: _
} => {
assert_eq!(got, dad_transmits_remaining);
});
let FakeDadAddressContext { addr: _, assigned, groups, ip_device_id_ctx: _ } =
address_ctx.get_ref();
assert!(!*assigned);
assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
let frames = core_ctx.frames();
assert_eq!(frames.len(), frames_len, "frames = {:?}", frames);
let (DadMessageMeta { dst_ip, message }, frame) =
frames.last().expect("should have transmitted a frame");
assert_eq!(*dst_ip, DAD_ADDRESS.to_solicited_node_address());
assert_eq!(*message, NeighborSolicitation::new(DAD_ADDRESS.get()));
let options = Options::parse(&frame[..]).expect("parse NDP options");
assert_eq!(options.iter().count(), 0);
bindings_ctx
.timer_ctx()
.assert_timers_installed([(dad_timer_id(), bindings_ctx.now() + retrans_timer.get())]);
}
#[test]
fn perform_dad() {
const DAD_TRANSMITS_REQUIRED: u16 = 5;
const RETRANS_TIMER: NonZeroDuration =
unsafe { NonZeroDuration::new_unchecked(Duration::from_secs(1)) };
let mut ctx = FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
FakeCoreCtxImpl::with_state(FakeDadContext {
state: Ipv6DadState::Tentative {
dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
timer: bindings_ctx.new_timer(dad_timer_id()),
},
retrans_timer: RETRANS_TIMER,
max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
addr: DAD_ADDRESS,
assigned: false,
groups: HashMap::default(),
ip_device_id_ctx: Default::default(),
}),
})
});
let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
DadHandler::start_duplicate_address_detection(
core_ctx,
bindings_ctx,
&FakeDeviceId,
&get_address_id(DAD_ADDRESS.get()),
);
for count in 0..=(DAD_TRANSMITS_REQUIRED - 1) {
check_dad(
core_ctx,
bindings_ctx,
usize::from(count + 1),
NonZeroU16::new(DAD_TRANSMITS_REQUIRED - count - 1),
RETRANS_TIMER,
);
assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(dad_timer_id()));
}
let FakeDadContext { state, retrans_timer: _, max_dad_transmits: _, address_ctx } =
core_ctx.get_ref();
assert_matches!(*state, Ipv6DadState::Assigned);
let FakeDadAddressContext { addr: _, assigned, groups, ip_device_id_ctx: _ } =
address_ctx.get_ref();
assert!(*assigned);
assert_eq!(groups, &HashMap::from([(DAD_ADDRESS.to_solicited_node_address(), 1)]));
assert_eq!(
bindings_ctx.take_events(),
&[DadEvent::AddressAssigned { device: FakeDeviceId, addr: DAD_ADDRESS }][..]
);
}
#[test]
fn stop_dad() {
const DAD_TRANSMITS_REQUIRED: u16 = 2;
const RETRANS_TIMER: NonZeroDuration =
unsafe { NonZeroDuration::new_unchecked(Duration::from_secs(2)) };
let FakeCtx { mut core_ctx, mut bindings_ctx } =
FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
FakeCoreCtxImpl::with_state(FakeDadContext {
state: Ipv6DadState::Tentative {
dad_transmits_remaining: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
timer: bindings_ctx.new_timer(dad_timer_id()),
},
retrans_timer: RETRANS_TIMER,
max_dad_transmits: NonZeroU16::new(DAD_TRANSMITS_REQUIRED),
address_ctx: FakeAddressCtxImpl::with_state(FakeDadAddressContext {
addr: DAD_ADDRESS,
assigned: false,
groups: HashMap::default(),
ip_device_id_ctx: Default::default(),
}),
})
});
DadHandler::start_duplicate_address_detection(
&mut core_ctx,
&mut bindings_ctx,
&FakeDeviceId,
&get_address_id(DAD_ADDRESS.get()),
);
check_dad(
&core_ctx,
&bindings_ctx,
1,
NonZeroU16::new(DAD_TRANSMITS_REQUIRED - 1),
RETRANS_TIMER,
);
DadHandler::stop_duplicate_address_detection(
&mut core_ctx,
&mut bindings_ctx,
&FakeDeviceId,
&get_address_id(DAD_ADDRESS.get()),
);
bindings_ctx.timer_ctx().assert_no_timers_installed();
let FakeDadContext { state, retrans_timer: _, max_dad_transmits: _, address_ctx } =
core_ctx.get_ref();
assert_matches!(*state, Ipv6DadState::Uninitialized);
let FakeDadAddressContext { addr: _, assigned, groups, ip_device_id_ctx: _ } =
address_ctx.get_ref();
assert!(!*assigned);
assert_eq!(groups, &HashMap::new());
}
}