blob: 938a180b40acbcd716f2f0217988d0021cf23467 [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::NonZeroU8, time::Duration};
use net_types::{
ip::{Ipv6, Ipv6Addr},
MulticastAddr, UnicastAddr, Witness as _,
};
use packet_formats::icmp::ndp::NeighborSolicitation;
use crate::{
context::{EventContext, TimerContext},
ip::{device::state::AddressState, IpDeviceIdContext},
};
/// A timer ID for duplicate address detection.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub(crate) struct DadTimerId<DeviceId> {
pub(crate) device_id: DeviceId,
pub(crate) addr: UnicastAddr<Ipv6Addr>,
}
/// The IP device context provided to DAD.
pub(super) trait Ipv6DeviceDadContext<C>: IpDeviceIdContext<Ipv6> {
/// Returns the address's state mutably, if it exists on the interface.
fn get_address_state_mut(
&mut self,
ctx: &mut C,
device_id: Self::DeviceId,
addr: UnicastAddr<Ipv6Addr>,
) -> Option<&mut AddressState>;
/// Returns the NDP retransmission timer configured on the device.
fn retrans_timer(&self, ctx: &mut C, device_id: Self::DeviceId) -> Duration;
}
/// The IP layer context provided to DAD.
pub(super) trait Ipv6LayerDadContext<C>: IpDeviceIdContext<Ipv6> {
/// 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,
ctx: &mut C,
device_id: Self::DeviceId,
dst_ip: MulticastAddr<Ipv6Addr>,
message: NeighborSolicitation,
) -> Result<(), ()>;
}
#[derive(Debug, Eq, 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 non-synchronized execution context for DAD.
pub(super) trait DadNonSyncContext<DeviceId>:
TimerContext<DadTimerId<DeviceId>> + EventContext<DadEvent<DeviceId>>
{
}
impl<DeviceId, C: TimerContext<DadTimerId<DeviceId>> + EventContext<DadEvent<DeviceId>>>
DadNonSyncContext<DeviceId> for C
{
}
/// The execution context for DAD.
pub(super) trait DadContext<C: DadNonSyncContext<Self::DeviceId>>:
Ipv6DeviceDadContext<C> + Ipv6LayerDadContext<C>
{
}
impl<C: DadNonSyncContext<SC::DeviceId>, SC: Ipv6DeviceDadContext<C> + Ipv6LayerDadContext<C>>
DadContext<C> for SC
{
}
/// An implementation for Duplicate Address Detection.
pub(crate) trait DadHandler<C>: IpDeviceIdContext<Ipv6> {
/// Do duplicate address detection.
///
/// # Panics
///
/// Panics if tentative state for the address is not found.
fn do_duplicate_address_detection(
&mut self,
ctx: &mut C,
device_id: Self::DeviceId,
addr: UnicastAddr<Ipv6Addr>,
);
/// Stops duplicate address detection.
///
/// Does nothing if DAD is not being performed on the address.
fn stop_duplicate_address_detection(
&mut self,
ctx: &mut C,
device_id: Self::DeviceId,
addr: UnicastAddr<Ipv6Addr>,
);
/// Handles a timer.
// TODO(https://fxbug.dev/101399): Replace this with a `TimerHandler` bound.
fn handle_timer(
&mut self,
ctx: &mut C,
DadTimerId { device_id, addr }: DadTimerId<Self::DeviceId>,
) {
self.do_duplicate_address_detection(ctx, device_id, addr)
}
}
impl<C: DadNonSyncContext<SC::DeviceId>, SC: DadContext<C>> DadHandler<C> for SC {
fn do_duplicate_address_detection(
&mut self,
ctx: &mut C,
device_id: Self::DeviceId,
addr: UnicastAddr<Ipv6Addr>,
) {
let state = self
.get_address_state_mut(ctx, device_id, addr)
.unwrap_or_else(|| panic!("expected address to exist; addr={}", addr));
let remaining = match state {
AddressState::Tentative { dad_transmits_remaining } => dad_transmits_remaining,
AddressState::Assigned => {
panic!("expected address to be tentative; addr={}", addr)
}
};
match remaining {
None => {
*state = AddressState::Assigned;
ctx.on_event(DadEvent::AddressAssigned { device: device_id, addr });
}
Some(non_zero_remaining) => {
*remaining = NonZeroU8::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.
let retrans_timer = self.retrans_timer(ctx, device_id);
let dst_ip = addr.to_solicited_node_address();
// 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/85055): Either panic or guarantee that this error
// can't happen statically.
let _: Result<(), _> = self.send_dad_packet(
ctx,
device_id,
dst_ip,
NeighborSolicitation::new(addr.get()),
);
assert_eq!(
ctx.schedule_timer(retrans_timer, DadTimerId { device_id, addr }),
None,
"Should not have a DAD timer set when performing DAD work; addr={}, device_id={}",
addr,
device_id
);
}
}
}
fn stop_duplicate_address_detection(
&mut self,
ctx: &mut C,
device_id: Self::DeviceId,
addr: UnicastAddr<Ipv6Addr>,
) {
let _: Option<C::Instant> = ctx.cancel_timer(DadTimerId { device_id, addr });
}
}
#[cfg(test)]
mod tests {
use packet::EmptyBuf;
use packet_formats::icmp::ndp::Options;
use super::*;
use crate::{
context::{
testutil::{DummyCtx, DummyNonSyncCtx, DummySyncCtx, DummyTimerCtxExt as _},
FrameContext as _, InstantContext as _,
},
ip::DummyDeviceId,
};
struct MockDadContext {
addr: UnicastAddr<Ipv6Addr>,
state: AddressState,
retrans_timer: Duration,
}
#[derive(Debug)]
struct DadMessageMeta {
dst_ip: MulticastAddr<Ipv6Addr>,
message: NeighborSolicitation,
}
type MockNonSyncCtx = DummyNonSyncCtx<DadTimerId<DummyDeviceId>, DadEvent<DummyDeviceId>, ()>;
type MockCtx = DummySyncCtx<MockDadContext, DadMessageMeta, DummyDeviceId>;
impl Ipv6DeviceDadContext<MockNonSyncCtx> for MockCtx {
fn get_address_state_mut(
&mut self,
_ctx: &mut MockNonSyncCtx,
DummyDeviceId: DummyDeviceId,
request_addr: UnicastAddr<Ipv6Addr>,
) -> Option<&mut AddressState> {
let MockDadContext { addr, state, retrans_timer: _ } = self.get_mut();
(*addr == request_addr).then(|| state)
}
fn retrans_timer(
&self,
_ctx: &mut MockNonSyncCtx,
DummyDeviceId: DummyDeviceId,
) -> Duration {
let MockDadContext { addr: _, state: _, retrans_timer } = self.get_ref();
*retrans_timer
}
}
impl Ipv6LayerDadContext<MockNonSyncCtx> for MockCtx {
fn send_dad_packet(
&mut self,
ctx: &mut MockNonSyncCtx,
DummyDeviceId: DummyDeviceId,
dst_ip: MulticastAddr<Ipv6Addr>,
message: NeighborSolicitation,
) -> Result<(), ()> {
self.send_frame(ctx, DadMessageMeta { dst_ip, message }, EmptyBuf)
.map_err(|EmptyBuf| ())
}
}
const DAD_ADDRESS: UnicastAddr<Ipv6Addr> =
unsafe { UnicastAddr::new_unchecked(Ipv6Addr::new([0xa, 0, 0, 0, 0, 0, 0, 1])) };
const OTHER_ADDRESS: UnicastAddr<Ipv6Addr> =
unsafe { UnicastAddr::new_unchecked(Ipv6Addr::new([0xa, 0, 0, 0, 0, 0, 0, 2])) };
#[test]
#[should_panic(expected = "expected address to exist")]
fn panic_unknown_address() {
let DummyCtx { mut sync_ctx, mut non_sync_ctx } =
DummyCtx::with_sync_ctx(MockCtx::with_state(MockDadContext {
addr: DAD_ADDRESS,
state: AddressState::Tentative { dad_transmits_remaining: None },
retrans_timer: Duration::default(),
}));
DadHandler::do_duplicate_address_detection(
&mut sync_ctx,
&mut non_sync_ctx,
DummyDeviceId,
OTHER_ADDRESS,
);
}
#[test]
#[should_panic(expected = "expected address to be tentative")]
fn panic_non_tentative_address() {
let DummyCtx { mut sync_ctx, mut non_sync_ctx } =
DummyCtx::with_sync_ctx(MockCtx::with_state(MockDadContext {
addr: DAD_ADDRESS,
state: AddressState::Assigned,
retrans_timer: Duration::default(),
}));
DadHandler::do_duplicate_address_detection(
&mut sync_ctx,
&mut non_sync_ctx,
DummyDeviceId,
DAD_ADDRESS,
);
}
#[test]
fn dad_disabled() {
let DummyCtx { mut sync_ctx, mut non_sync_ctx } =
DummyCtx::with_sync_ctx(MockCtx::with_state(MockDadContext {
addr: DAD_ADDRESS,
state: AddressState::Tentative { dad_transmits_remaining: None },
retrans_timer: Duration::default(),
}));
DadHandler::do_duplicate_address_detection(
&mut sync_ctx,
&mut non_sync_ctx,
DummyDeviceId,
DAD_ADDRESS,
);
let MockDadContext { addr: _, state, retrans_timer: _ } = sync_ctx.get_ref();
assert_eq!(*state, AddressState::Assigned);
assert_eq!(
non_sync_ctx.take_events(),
&[DadEvent::AddressAssigned { device: DummyDeviceId, addr: DAD_ADDRESS }][..]
);
}
const DAD_TIMER_ID: DadTimerId<DummyDeviceId> =
DadTimerId { addr: DAD_ADDRESS, device_id: DummyDeviceId };
fn check_dad(
sync_ctx: &MockCtx,
non_sync_ctx: &MockNonSyncCtx,
frames_len: usize,
dad_transmits_remaining: Option<NonZeroU8>,
retrans_timer: Duration,
) {
let MockDadContext { addr: _, state, retrans_timer: _ } = sync_ctx.get_ref();
assert_eq!(*state, AddressState::Tentative { dad_transmits_remaining });
let frames = sync_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);
non_sync_ctx
.timer_ctx()
.assert_timers_installed([(DAD_TIMER_ID, non_sync_ctx.now() + retrans_timer)]);
}
#[test]
fn perform_dad() {
const DAD_TRANSMITS_REQUIRED: u8 = 2;
const RETRANS_TIMER: Duration = Duration::from_secs(1);
let DummyCtx { mut sync_ctx, mut non_sync_ctx } =
DummyCtx::with_sync_ctx(MockCtx::with_state(MockDadContext {
addr: DAD_ADDRESS,
state: AddressState::Tentative {
dad_transmits_remaining: NonZeroU8::new(DAD_TRANSMITS_REQUIRED),
},
retrans_timer: RETRANS_TIMER,
}));
DadHandler::do_duplicate_address_detection(
&mut sync_ctx,
&mut non_sync_ctx,
DummyDeviceId,
DAD_ADDRESS,
);
for count in 0..=1u8 {
check_dad(
&sync_ctx,
&non_sync_ctx,
usize::from(count + 1),
NonZeroU8::new(DAD_TRANSMITS_REQUIRED - count - 1),
RETRANS_TIMER,
);
assert_eq!(
non_sync_ctx.trigger_next_timer(&mut sync_ctx, DadHandler::handle_timer),
Some(DAD_TIMER_ID)
);
}
let MockDadContext { addr: _, state, retrans_timer: _ } = sync_ctx.get_ref();
assert_eq!(*state, AddressState::Assigned);
assert_eq!(
non_sync_ctx.take_events(),
&[DadEvent::AddressAssigned { device: DummyDeviceId, addr: DAD_ADDRESS }][..]
);
}
#[test]
fn stop_dad() {
const DAD_TRANSMITS_REQUIRED: u8 = 2;
const RETRANS_TIMER: Duration = Duration::from_secs(2);
let DummyCtx { mut sync_ctx, mut non_sync_ctx } =
DummyCtx::with_sync_ctx(MockCtx::with_state(MockDadContext {
addr: DAD_ADDRESS,
state: AddressState::Tentative {
dad_transmits_remaining: NonZeroU8::new(DAD_TRANSMITS_REQUIRED),
},
retrans_timer: RETRANS_TIMER,
}));
DadHandler::do_duplicate_address_detection(
&mut sync_ctx,
&mut non_sync_ctx,
DummyDeviceId,
DAD_ADDRESS,
);
check_dad(
&sync_ctx,
&non_sync_ctx,
1,
NonZeroU8::new(DAD_TRANSMITS_REQUIRED - 1),
RETRANS_TIMER,
);
DadHandler::stop_duplicate_address_detection(
&mut sync_ctx,
&mut non_sync_ctx,
DummyDeviceId,
DAD_ADDRESS,
);
non_sync_ctx.timer_ctx().assert_no_timers_installed();
}
}