1use alloc::collections::{BinaryHeap, VecDeque};
8use alloc::vec::Vec;
9use core::convert::Infallible as Never;
10use core::fmt::Debug;
11use core::hash::Hash;
12use core::marker::PhantomData;
13use core::num::{NonZeroU16, NonZeroU32};
14use core::time::Duration;
15
16use assert_matches::assert_matches;
17use derivative::Derivative;
18use log::{debug, error, warn};
19use net_types::SpecifiedAddr;
20use net_types::ip::{GenericOverIp, Ip, IpMarked, Ipv4, Ipv6};
21use netstack3_base::socket::{SocketIpAddr, SocketIpAddrExt as _};
22use netstack3_base::{
23 AddressResolutionFailed, AnyDevice, CoreTimerContext, Counter, CounterContext, DeviceIdContext,
24 DeviceIdentifier, ErrorAndSerializer, EventContext, HandleableTimer, Instant,
25 InstantBindingsTypes, LinkAddress, LinkDevice, LinkUnicastAddress, LocalTimerHeap,
26 NetworkSerializationContext, NetworkSerializer, SendFrameError, StrongDeviceIdentifier,
27 TimerBindingsTypes, TimerContext, TxMetadataBindingsTypes, WeakDeviceIdentifier,
28};
29use netstack3_hashmap::hash_map::{self, Entry, HashMap};
30use packet::{
31 Buf, BufferMut, GrowBuffer as _, ParsablePacket as _, ParseBufferMut as _, SerializeError,
32};
33use packet_formats::ip::IpPacket as _;
34use packet_formats::ipv4::{Ipv4FragmentType, Ipv4Header as _, Ipv4Packet};
35use packet_formats::ipv6::Ipv6Packet;
36use packet_formats::utils::NonZeroDuration;
37use thiserror::Error;
38use zerocopy::SplitByteSlice;
39
40pub(crate) mod api;
41
42pub(crate) const DEFAULT_MAX_MULTICAST_SOLICIT: NonZeroU16 = NonZeroU16::new(3).unwrap();
47
48const DEFAULT_MAX_UNICAST_SOLICIT: NonZeroU16 = NonZeroU16::new(3).unwrap();
53
54const MAX_RETRANS_TIMER: NonZeroDuration = NonZeroDuration::from_secs(60).unwrap();
59
60pub const DEFAULT_RETRANS_TIMER: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
67
68const BACKOFF_MULTIPLE: NonZeroU32 = NonZeroU32::new(3).unwrap();
73
74const MAX_PENDING_FRAMES: usize = 10;
75
76const DEFAULT_BASE_REACHABLE_TIME: NonZeroDuration = NonZeroDuration::from_secs(30).unwrap();
81
82const DELAY_FIRST_PROBE_TIME: NonZeroDuration = NonZeroDuration::from_secs(5).unwrap();
87
88pub const MAX_ENTRIES: usize = 512;
93
94const MIN_GARBAGE_COLLECTION_INTERVAL: NonZeroDuration = NonZeroDuration::from_secs(30).unwrap();
97
98#[derive(Default)]
100pub struct NudCountersInner {
101 pub icmp_dest_unreachable_dropped: Counter,
103}
104
105pub type NudCounters<I> = IpMarked<I, NudCountersInner>;
107
108#[derive(Debug, Copy, Clone)]
110pub struct ConfirmationFlags {
111 pub solicited_flag: bool,
113 pub override_flag: bool,
115}
116
117#[derive(Debug, Copy, Clone)]
119pub enum DynamicNeighborUpdateSource<A> {
120 Probe {
124 link_address: A,
127 },
128
129 Confirmation {
133 link_address: Option<A>,
136 flags: ConfirmationFlags,
138 },
139}
140
141#[derive(Derivative)]
143#[derivative(Debug(bound = ""))]
144#[cfg_attr(
145 any(test, feature = "testutils"),
146 derivative(
147 Clone(bound = "BT::TxMetadata: Clone"),
148 PartialEq(bound = "BT::TxMetadata: PartialEq"),
149 Eq(bound = "BT::TxMetadata: Eq")
150 )
151)]
152#[allow(missing_docs)]
153pub enum NeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
154 Dynamic(DynamicNeighborState<D, BT>),
155 Static(D::Address),
156}
157
158#[derive(Derivative)]
165#[derivative(Debug(bound = ""))]
166#[cfg_attr(
167 any(test, feature = "testutils"),
168 derivative(
169 Clone(bound = "BT::TxMetadata: Clone"),
170 PartialEq(bound = "BT::TxMetadata: PartialEq"),
171 Eq(bound = "BT::TxMetadata: Eq")
172 )
173)]
174pub enum DynamicNeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
175 Incomplete(Incomplete<D, BT::Notifier, BT::TxMetadata>),
181
182 Reachable(Reachable<D, BT::Instant>),
187
188 Stale(Stale<D>),
199
200 Delay(Delay<D>),
212
213 Probe(Probe<D>),
217
218 Unreachable(Unreachable<D>),
225}
226
227#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
242pub enum EventDynamicState<L: LinkUnicastAddress> {
243 Incomplete,
246 Reachable(L),
249 Stale(L),
257 Delay(L),
265 Probe(L),
270 Unreachable(L),
274}
275
276#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
284pub enum EventState<L: LinkUnicastAddress> {
285 Dynamic(EventDynamicState<L>),
287 Static(L),
289}
290
291#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
293pub enum EventKind<L: LinkUnicastAddress> {
294 Added(EventState<L>),
296 Changed(EventState<L>),
298 Removed,
300}
301
302#[derive(Debug, Eq, Hash, PartialEq, GenericOverIp)]
304#[generic_over_ip(I, Ip)]
305pub struct Event<L: LinkUnicastAddress, DeviceId, I: Ip, Instant> {
306 pub device: DeviceId,
308 pub addr: SpecifiedAddr<I::Addr>,
310 pub kind: EventKind<L>,
312 pub at: Instant,
314}
315
316impl<L: LinkUnicastAddress, DeviceId, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
317 pub fn map_device<N, F: FnOnce(DeviceId) -> N>(self, map: F) -> Event<L, N, I, Instant> {
319 let Self { device, kind, addr, at } = self;
320 Event { device: map(device), kind, addr, at }
321 }
322}
323
324impl<L: LinkUnicastAddress, DeviceId: Clone, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
325 fn changed(
326 device: &DeviceId,
327 event_state: EventState<L>,
328 addr: SpecifiedAddr<I::Addr>,
329 at: Instant,
330 ) -> Self {
331 Self { device: device.clone(), kind: EventKind::Changed(event_state), addr, at }
332 }
333
334 fn added(
335 device: &DeviceId,
336 event_state: EventState<L>,
337 addr: SpecifiedAddr<I::Addr>,
338 at: Instant,
339 ) -> Self {
340 Self { device: device.clone(), kind: EventKind::Added(event_state), addr, at }
341 }
342
343 fn removed(device: &DeviceId, addr: SpecifiedAddr<I::Addr>, at: Instant) -> Self {
344 Self { device: device.clone(), kind: EventKind::Removed, addr, at }
345 }
346}
347
348fn schedule_timer_if_should_retransmit<I, D, DeviceId, CC, BC>(
349 core_ctx: &mut CC,
350 bindings_ctx: &mut BC,
351 timers: &mut TimerHeap<I, BC>,
352 neighbor: SpecifiedAddr<I::Addr>,
353 event: NudEvent,
354 counter: &mut Option<NonZeroU16>,
355) -> bool
356where
357 I: Ip,
358 D: LinkDevice,
359 DeviceId: StrongDeviceIdentifier,
360 BC: NudBindingsContext<I, D, DeviceId>,
361 CC: NudConfigContext<I>,
362{
363 match counter {
364 Some(c) => {
365 *counter = NonZeroU16::new(c.get() - 1);
366 let retransmit_timeout = core_ctx.retransmit_timeout();
367 timers.schedule_neighbor(bindings_ctx, retransmit_timeout, neighbor, event);
368 true
369 }
370 None => false,
371 }
372}
373
374#[derive(Debug, Derivative)]
376#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq(bound = "M: PartialEq"), Eq))]
377pub struct Incomplete<D: LinkDevice, N: LinkResolutionNotifier<D>, M> {
378 transmit_counter: Option<NonZeroU16>,
379 pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
380 #[derivative(PartialEq = "ignore")]
381 notifiers: Vec<N>,
382 _marker: PhantomData<D>,
383}
384
385#[cfg(any(test, feature = "testutils"))]
386impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M: Clone> Clone for Incomplete<D, N, M> {
387 fn clone(&self) -> Self {
388 let Self { transmit_counter, pending_frames, notifiers: _, _marker } = self;
392 Self {
393 transmit_counter: transmit_counter.clone(),
394 pending_frames: pending_frames.clone(),
395 notifiers: Vec::new(),
396 _marker: PhantomData,
397 }
398 }
399}
400
401impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Drop for Incomplete<D, N, M> {
402 fn drop(&mut self) {
403 let Self { transmit_counter: _, pending_frames: _, notifiers, _marker } = self;
404 for notifier in notifiers.drain(..) {
405 notifier.notify(Err(AddressResolutionFailed));
406 }
407 }
408}
409
410impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Incomplete<D, N, M> {
411 #[cfg(any(test, feature = "testutils"))]
414 pub fn new_with_pending_frames_and_transmit_counter(
415 pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
416 transmit_counter: Option<NonZeroU16>,
417 ) -> Self {
418 Self {
419 transmit_counter,
420 pending_frames,
421 notifiers: Default::default(),
422 _marker: PhantomData,
423 }
424 }
425
426 fn new_with_packet<I, CC, BC, DeviceId, B, S>(
427 core_ctx: &mut CC,
428 bindings_ctx: &mut BC,
429 timers: &mut TimerHeap<I, BC>,
430 neighbor: SpecifiedAddr<I::Addr>,
431 packet: S,
432 meta: M,
433 ) -> Result<Self, ErrorAndSerializer<SerializeError<Never>, S>>
434 where
435 I: Ip,
436 D: LinkDevice,
437 BC: NudBindingsContext<I, D, DeviceId>,
438 CC: NudConfigContext<I>,
439 DeviceId: StrongDeviceIdentifier,
440 B: BufferMut,
441 S: NetworkSerializer<Buffer = B>,
442 {
443 let packet = packet
447 .serialize_vec_outer(&mut NetworkSerializationContext::default())
448 .map_err(|(error, serializer)| ErrorAndSerializer { error, serializer })?
449 .map_a(|b| Buf::new(b.as_ref().to_vec(), ..))
450 .into_inner();
451
452 let mut this = Incomplete {
453 transmit_counter: Some(core_ctx.max_multicast_solicit()),
454 pending_frames: VecDeque::from([(packet, meta)]),
455 notifiers: Vec::new(),
456 _marker: PhantomData,
457 };
458 assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
462
463 Ok(this)
464 }
465
466 fn new_with_notifier<I, CC, BC, DeviceId>(
467 core_ctx: &mut CC,
468 bindings_ctx: &mut BC,
469 timers: &mut TimerHeap<I, BC>,
470 neighbor: SpecifiedAddr<I::Addr>,
471 notifier: BC::Notifier,
472 ) -> Self
473 where
474 I: Ip,
475 D: LinkDevice,
476 BC: NudBindingsContext<I, D, DeviceId, Notifier = N>,
477 CC: NudConfigContext<I>,
478 DeviceId: StrongDeviceIdentifier,
479 {
480 let mut this = Incomplete {
481 transmit_counter: Some(core_ctx.max_multicast_solicit()),
482 pending_frames: VecDeque::new(),
483 notifiers: [notifier].into(),
484 _marker: PhantomData,
485 };
486 assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
490
491 this
492 }
493
494 fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
495 &mut self,
496 core_ctx: &mut CC,
497 bindings_ctx: &mut BC,
498 timers: &mut TimerHeap<I, BC>,
499 neighbor: SpecifiedAddr<I::Addr>,
500 ) -> bool
501 where
502 I: Ip,
503 D: LinkDevice,
504 DeviceId: StrongDeviceIdentifier,
505 BC: NudBindingsContext<I, D, DeviceId>,
506 CC: NudConfigContext<I>,
507 {
508 let Self { transmit_counter, pending_frames: _, notifiers: _, _marker } = self;
509 schedule_timer_if_should_retransmit(
510 core_ctx,
511 bindings_ctx,
512 timers,
513 neighbor,
514 NudEvent::RetransmitMulticastProbe,
515 transmit_counter,
516 )
517 }
518
519 fn queue_packet<B, S>(
520 &mut self,
521 body: S,
522 meta: M,
523 ) -> Result<(), ErrorAndSerializer<SerializeError<Never>, S>>
524 where
525 B: BufferMut,
526 S: NetworkSerializer<Buffer = B>,
527 {
528 let Self { pending_frames, transmit_counter: _, notifiers: _, _marker } = self;
529
530 if pending_frames.len() < MAX_PENDING_FRAMES {
537 pending_frames.push_back((
538 body.serialize_vec_outer(&mut NetworkSerializationContext::default())
539 .map_err(|(error, serializer)| ErrorAndSerializer { error, serializer })?
540 .map_a(|b| Buf::new(b.as_ref().to_vec(), ..))
541 .into_inner(),
542 meta,
543 ));
544 }
545 Ok(())
546 }
547
548 fn complete_resolution<I, CC, BC>(
551 &mut self,
552 core_ctx: &mut CC,
553 bindings_ctx: &mut BC,
554 link_address: D::Address,
555 ) where
556 I: Ip,
557 D: LinkDevice,
558 BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata = M>,
559 CC: NudSenderContext<I, D, BC>,
560 {
561 let Self { pending_frames, notifiers, transmit_counter: _, _marker } = self;
562
563 for (body, meta) in pending_frames.drain(..) {
571 core_ctx
575 .send_ip_packet_to_neighbor_link_addr(bindings_ctx, link_address, body, meta)
576 .unwrap_or_else(|err| {
577 error!("failed to send pending IP packet to neighbor {link_address:?} {err:?}")
578 })
579 }
580 for notifier in notifiers.drain(..) {
581 notifier.notify(Ok(link_address));
582 }
583 }
584}
585
586#[derive(Debug, Derivative)]
588#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
589pub struct Reachable<D: LinkDevice, I: Instant> {
590 pub link_address: D::Address,
592 pub last_confirmed_at: I,
594}
595
596#[derive(Debug, Derivative)]
598#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
599pub struct Stale<D: LinkDevice> {
600 pub link_address: D::Address,
602}
603
604impl<D: LinkDevice> Stale<D> {
605 fn enter_delay<I, BC, DeviceId: Clone>(
606 &mut self,
607 bindings_ctx: &mut BC,
608 timers: &mut TimerHeap<I, BC>,
609 neighbor: SpecifiedAddr<I::Addr>,
610 ) -> Delay<D>
611 where
612 I: Ip,
613 BC: NudBindingsContext<I, D, DeviceId>,
614 {
615 let Self { link_address } = *self;
616
617 timers.schedule_neighbor(
620 bindings_ctx,
621 DELAY_FIRST_PROBE_TIME,
622 neighbor,
623 NudEvent::DelayFirstProbe,
624 );
625
626 Delay { link_address }
627 }
628}
629
630#[derive(Debug, Derivative)]
632#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
633pub struct Delay<D: LinkDevice> {
634 pub link_address: D::Address,
636}
637
638impl<D: LinkDevice> Delay<D> {
639 fn enter_probe<I, DeviceId, CC, BC>(
640 &mut self,
641 core_ctx: &mut CC,
642 bindings_ctx: &mut BC,
643 timers: &mut TimerHeap<I, BC>,
644 neighbor: SpecifiedAddr<I::Addr>,
645 ) -> Probe<D>
646 where
647 I: Ip,
648 DeviceId: StrongDeviceIdentifier,
649 BC: NudBindingsContext<I, D, DeviceId>,
650 CC: NudConfigContext<I>,
651 {
652 let Self { link_address } = *self;
653
654 let retransmit_timeout = core_ctx.retransmit_timeout();
658 timers.schedule_neighbor(
659 bindings_ctx,
660 retransmit_timeout,
661 neighbor,
662 NudEvent::RetransmitUnicastProbe,
663 );
664
665 Probe {
666 link_address,
667 transmit_counter: NonZeroU16::new(core_ctx.max_unicast_solicit().get() - 1),
668 }
669 }
670}
671
672#[derive(Debug, Derivative)]
673#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
674pub struct Probe<D: LinkDevice> {
675 link_address: D::Address,
676 transmit_counter: Option<NonZeroU16>,
677}
678
679impl<D: LinkDevice> Probe<D> {
680 fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
681 &mut self,
682 core_ctx: &mut CC,
683 bindings_ctx: &mut BC,
684 timers: &mut TimerHeap<I, BC>,
685 neighbor: SpecifiedAddr<I::Addr>,
686 ) -> bool
687 where
688 I: Ip,
689 DeviceId: StrongDeviceIdentifier,
690 BC: NudBindingsContext<I, D, DeviceId>,
691 CC: NudConfigContext<I>,
692 {
693 let Self { link_address: _, transmit_counter } = self;
694 schedule_timer_if_should_retransmit(
695 core_ctx,
696 bindings_ctx,
697 timers,
698 neighbor,
699 NudEvent::RetransmitUnicastProbe,
700 transmit_counter,
701 )
702 }
703
704 fn enter_unreachable<I, BC, DeviceId>(
705 &mut self,
706 bindings_ctx: &mut BC,
707 timers: &mut TimerHeap<I, BC>,
708 num_entries: usize,
709 last_gc: &mut Option<BC::Instant>,
710 ) -> Unreachable<D>
711 where
712 I: Ip,
713 BC: NudBindingsContext<I, D, DeviceId>,
714 DeviceId: Clone,
715 {
716 timers.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
720
721 let Self { link_address, transmit_counter: _ } = self;
722 Unreachable { link_address: *link_address, mode: UnreachableMode::WaitingForPacketSend }
723 }
724}
725
726#[derive(Debug, Derivative)]
727#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
728pub struct Unreachable<D: LinkDevice> {
729 link_address: D::Address,
730 mode: UnreachableMode,
731}
732
733#[derive(Debug, Clone, Copy, Derivative)]
745#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq, Eq))]
746pub(crate) enum UnreachableMode {
747 WaitingForPacketSend,
748 Backoff { probes_sent: NonZeroU32, packet_sent: bool },
749}
750
751impl UnreachableMode {
752 fn next_backoff_retransmit_timeout<I, CC>(&self, core_ctx: &mut CC) -> NonZeroDuration
758 where
759 I: Ip,
760 CC: NudConfigContext<I>,
761 {
762 let probes_sent = match self {
763 UnreachableMode::Backoff { probes_sent, packet_sent: _ } => probes_sent,
764 UnreachableMode::WaitingForPacketSend => {
765 panic!("cannot calculate exponential backoff in state {self:?}")
766 }
767 };
768 (core_ctx.retransmit_timeout() * BACKOFF_MULTIPLE.saturating_pow(probes_sent.get()))
772 .min(MAX_RETRANS_TIMER)
773 }
774}
775
776impl<D: LinkDevice> Unreachable<D> {
777 fn handle_timer<I, DeviceId, CC, BC>(
778 &mut self,
779 core_ctx: &mut CC,
780 bindings_ctx: &mut BC,
781 timers: &mut TimerHeap<I, BC>,
782 device_id: &DeviceId,
783 neighbor: SpecifiedAddr<I::Addr>,
784 ) -> Option<TransmitProbe<D::Address>>
785 where
786 I: Ip,
787 DeviceId: StrongDeviceIdentifier,
788 BC: NudBindingsContext<I, D, DeviceId>,
789 CC: NudConfigContext<I>,
790 {
791 let Self { link_address: _, mode } = self;
792 match mode {
793 UnreachableMode::WaitingForPacketSend => {
794 panic!(
795 "timer should not have fired in UNREACHABLE while waiting for packet send; got \
796 a retransmit multicast probe event for {neighbor} on {device_id:?}",
797 );
798 }
799 UnreachableMode::Backoff { probes_sent, packet_sent } => {
800 if *packet_sent {
801 *probes_sent = probes_sent.saturating_add(1);
808 *packet_sent = false;
809
810 let duration = mode.next_backoff_retransmit_timeout(core_ctx);
811 timers.schedule_neighbor(
812 bindings_ctx,
813 duration,
814 neighbor,
815 NudEvent::RetransmitMulticastProbe,
816 );
817
818 Some(TransmitProbe::Multicast)
819 } else {
820 *mode = UnreachableMode::WaitingForPacketSend;
821
822 None
823 }
824 }
825 }
826 }
827
828 fn handle_packet_queued_to_send<I, DeviceId, CC, BC>(
833 &mut self,
834 core_ctx: &mut CC,
835 bindings_ctx: &mut BC,
836 timers: &mut TimerHeap<I, BC>,
837 neighbor: SpecifiedAddr<I::Addr>,
838 ) -> bool
839 where
840 I: Ip,
841 DeviceId: StrongDeviceIdentifier,
842 BC: NudBindingsContext<I, D, DeviceId>,
843 CC: NudConfigContext<I>,
844 {
845 let Self { link_address: _, mode } = self;
846 match mode {
847 UnreachableMode::WaitingForPacketSend => {
848 let probes_sent = NonZeroU32::new(1).unwrap();
863 *mode = UnreachableMode::Backoff { probes_sent, packet_sent: false };
864
865 let duration = mode.next_backoff_retransmit_timeout(core_ctx);
866 timers.schedule_neighbor(
867 bindings_ctx,
868 duration,
869 neighbor,
870 NudEvent::RetransmitMulticastProbe,
871 );
872
873 true
875 }
876 UnreachableMode::Backoff { probes_sent: _, packet_sent } => {
877 *packet_sent = true;
881
882 false
883 }
884 }
885 }
886}
887
888#[derive(Debug, PartialEq, Eq, Error)]
889pub(crate) enum EnterProbeError {
890 #[error("link address is unknown")]
891 LinkAddressUnknown,
892}
893
894impl<D: LinkDevice, BT: NudBindingsTypes<D>> NeighborState<D, BT> {
895 fn to_event_state(&self) -> EventState<D::Address> {
896 match self {
897 NeighborState::Dynamic(dynamic_state) => {
898 EventState::Dynamic(dynamic_state.to_event_dynamic_state())
899 }
900 NeighborState::Static(addr) => EventState::Static(*addr),
901 }
902 }
903
904 pub(crate) fn enter_probe<I, DeviceId, CC>(
914 &mut self,
915 core_ctx: &mut CC,
916 bindings_ctx: &mut BT,
917 timers: &mut TimerHeap<I, BT>,
918 neighbor: SpecifiedAddr<I::Addr>,
919 device_id: &DeviceId,
920 ) -> Result<Option<D::Address>, EnterProbeError>
921 where
922 I: Ip,
923 DeviceId: StrongDeviceIdentifier,
924 BT: NudBindingsContext<I, D, DeviceId>,
925 CC: NudConfigContext<I>,
926 {
927 let link_addr = match self {
928 NeighborState::Dynamic(dynamic_state) => {
929 let link_addr = match dynamic_state {
930 DynamicNeighborState::Reachable(Reachable { link_address, .. }) => {
931 *link_address
932 }
933 DynamicNeighborState::Stale(Stale { link_address }) => *link_address,
934 DynamicNeighborState::Delay(Delay { link_address }) => *link_address,
935 DynamicNeighborState::Unreachable(Unreachable { link_address, .. }) => {
936 *link_address
937 }
938 DynamicNeighborState::Incomplete(_) => {
941 return Err(EnterProbeError::LinkAddressUnknown);
942 }
943 DynamicNeighborState::Probe(_) => return Ok(None),
946 };
947
948 dynamic_state.cancel_timer(bindings_ctx, timers, neighbor);
951 link_addr
952 }
953 NeighborState::Static(link_address) => *link_address,
954 };
955
956 let retransmit_timeout = core_ctx.retransmit_timeout();
958 timers.schedule_neighbor(
959 bindings_ctx,
960 retransmit_timeout,
961 neighbor,
962 NudEvent::RetransmitUnicastProbe,
963 );
964
965 *self = NeighborState::Dynamic(DynamicNeighborState::Probe(Probe {
967 link_address: link_addr,
968 transmit_counter: NonZeroU16::new(core_ctx.max_unicast_solicit().get() - 1),
969 }));
970 let event_state = self.to_event_state();
971 bindings_ctx.on_event(Event::changed(
972 &device_id,
973 event_state,
974 neighbor,
975 bindings_ctx.now(),
976 ));
977 Ok(Some(link_addr))
978 }
979}
980
981impl<D: LinkDevice, BC: NudBindingsTypes<D>> DynamicNeighborState<D, BC> {
982 fn cancel_timer<I, DeviceId>(
983 &mut self,
984 bindings_ctx: &mut BC,
985 timers: &mut TimerHeap<I, BC>,
986 neighbor: SpecifiedAddr<I::Addr>,
987 ) where
988 I: Ip,
989 DeviceId: StrongDeviceIdentifier,
990 BC: NudBindingsContext<I, D, DeviceId>,
991 {
992 let expected_event = match self {
993 DynamicNeighborState::Incomplete(Incomplete {
994 transmit_counter: _,
995 pending_frames: _,
996 notifiers: _,
997 _marker,
998 }) => Some(NudEvent::RetransmitMulticastProbe),
999 DynamicNeighborState::Reachable(Reachable {
1000 link_address: _,
1001 last_confirmed_at: _,
1002 }) => Some(NudEvent::ReachableTime),
1003 DynamicNeighborState::Stale(Stale { link_address: _ }) => None,
1004 DynamicNeighborState::Delay(Delay { link_address: _ }) => {
1005 Some(NudEvent::DelayFirstProbe)
1006 }
1007 DynamicNeighborState::Probe(Probe { link_address: _, transmit_counter: _ }) => {
1008 Some(NudEvent::RetransmitUnicastProbe)
1009 }
1010 DynamicNeighborState::Unreachable(Unreachable { link_address: _, mode }) => {
1011 match mode {
1014 UnreachableMode::WaitingForPacketSend => None,
1015 UnreachableMode::Backoff { probes_sent: _, packet_sent: _ } => {
1016 Some(NudEvent::RetransmitMulticastProbe)
1017 }
1018 }
1019 }
1020 };
1021 assert_eq!(
1022 timers.cancel_neighbor(bindings_ctx, neighbor),
1023 expected_event,
1024 "neighbor {neighbor} ({self:?}) had unexpected timer installed"
1025 );
1026 }
1027
1028 fn cancel_timer_and_complete_resolution<I, CC>(
1029 mut self,
1030 core_ctx: &mut CC,
1031 bindings_ctx: &mut BC,
1032 timers: &mut TimerHeap<I, BC>,
1033 neighbor: SpecifiedAddr<I::Addr>,
1034 link_address: D::Address,
1035 ) where
1036 I: Ip,
1037 BC: NudBindingsContext<I, D, CC::DeviceId>,
1038 CC: NudSenderContext<I, D, BC>,
1039 {
1040 self.cancel_timer(bindings_ctx, timers, neighbor);
1041
1042 match self {
1043 DynamicNeighborState::Incomplete(mut incomplete) => {
1044 incomplete.complete_resolution(core_ctx, bindings_ctx, link_address);
1045 }
1046 DynamicNeighborState::Reachable(_)
1047 | DynamicNeighborState::Stale(_)
1048 | DynamicNeighborState::Delay(_)
1049 | DynamicNeighborState::Probe(_)
1050 | DynamicNeighborState::Unreachable(_) => {}
1051 }
1052 }
1053
1054 fn to_event_dynamic_state(&self) -> EventDynamicState<D::Address> {
1055 match self {
1056 Self::Incomplete(_) => EventDynamicState::Incomplete,
1057 Self::Reachable(Reachable { link_address, last_confirmed_at: _ }) => {
1058 EventDynamicState::Reachable(*link_address)
1059 }
1060 Self::Stale(Stale { link_address }) => EventDynamicState::Stale(*link_address),
1061 Self::Delay(Delay { link_address }) => EventDynamicState::Delay(*link_address),
1062 Self::Probe(Probe { link_address, transmit_counter: _ }) => {
1063 EventDynamicState::Probe(*link_address)
1064 }
1065 Self::Unreachable(Unreachable { link_address, mode: _ }) => {
1066 EventDynamicState::Unreachable(*link_address)
1067 }
1068 }
1069 }
1070
1071 fn enter_reachable<I, CC>(
1073 &mut self,
1074 core_ctx: &mut CC,
1075 bindings_ctx: &mut BC,
1076 timers: &mut TimerHeap<I, BC>,
1077 device_id: &CC::DeviceId,
1078 neighbor: SpecifiedAddr<I::Addr>,
1079 link_address: D::Address,
1080 ) where
1081 I: Ip,
1082 BC: NudBindingsContext<I, D, CC::DeviceId>,
1083 CC: NudSenderContext<I, D, BC>,
1084 {
1085 let now = bindings_ctx.now();
1088 match self {
1089 DynamicNeighborState::Reachable(Reachable {
1094 link_address: current,
1095 last_confirmed_at,
1096 }) if *current == link_address => {
1097 *last_confirmed_at = now;
1098 return;
1099 }
1100 DynamicNeighborState::Incomplete(_)
1101 | DynamicNeighborState::Reachable(_)
1102 | DynamicNeighborState::Stale(_)
1103 | DynamicNeighborState::Delay(_)
1104 | DynamicNeighborState::Probe(_)
1105 | DynamicNeighborState::Unreachable(_) => {}
1106 }
1107 let previous = core::mem::replace(
1108 self,
1109 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: now }),
1110 );
1111 let event_dynamic_state = self.to_event_dynamic_state();
1112 debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1113 let event_state = EventState::Dynamic(event_dynamic_state);
1114 bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1115 previous.cancel_timer_and_complete_resolution(
1116 core_ctx,
1117 bindings_ctx,
1118 timers,
1119 neighbor,
1120 link_address,
1121 );
1122 timers.schedule_neighbor(
1123 bindings_ctx,
1124 core_ctx.base_reachable_time(),
1125 neighbor,
1126 NudEvent::ReachableTime,
1127 );
1128 }
1129
1130 fn enter_stale<I, CC>(
1138 &mut self,
1139 core_ctx: &mut CC,
1140 bindings_ctx: &mut BC,
1141 timers: &mut TimerHeap<I, BC>,
1142 device_id: &CC::DeviceId,
1143 neighbor: SpecifiedAddr<I::Addr>,
1144 link_address: D::Address,
1145 num_entries: usize,
1146 last_gc: &mut Option<BC::Instant>,
1147 ) where
1148 I: Ip,
1149 BC: NudBindingsContext<I, D, CC::DeviceId>,
1150 CC: NudSenderContext<I, D, BC>,
1151 {
1152 let previous =
1155 core::mem::replace(self, DynamicNeighborState::Stale(Stale { link_address }));
1156 let event_dynamic_state = self.to_event_dynamic_state();
1157 debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1158 let event_state = EventState::Dynamic(event_dynamic_state);
1159 bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1160 previous.cancel_timer_and_complete_resolution(
1161 core_ctx,
1162 bindings_ctx,
1163 timers,
1164 neighbor,
1165 link_address,
1166 );
1167
1168 timers.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
1172
1173 }
1176
1177 fn resolve_link_addr<I, DeviceId, CC>(
1183 &mut self,
1184 core_ctx: &mut CC,
1185 bindings_ctx: &mut BC,
1186 timers: &mut TimerHeap<I, BC>,
1187 device_id: &DeviceId,
1188 neighbor: SpecifiedAddr<I::Addr>,
1189 ) -> (
1190 LinkResolutionResult<
1191 D::Address,
1192 <<BC as LinkResolutionContext<D>>::Notifier as LinkResolutionNotifier<D>>::Observer,
1193 >,
1194 bool,
1195 )
1196 where
1197 I: Ip,
1198 DeviceId: StrongDeviceIdentifier,
1199 BC: NudBindingsContext<I, D, DeviceId>,
1200 CC: NudConfigContext<I>,
1201 {
1202 match self {
1203 DynamicNeighborState::Incomplete(Incomplete {
1204 notifiers,
1205 transmit_counter: _,
1206 pending_frames: _,
1207 _marker,
1208 }) => {
1209 let (notifier, observer) = BC::Notifier::new();
1210 notifiers.push(notifier);
1211
1212 (LinkResolutionResult::Pending(observer), false)
1213 }
1214 DynamicNeighborState::Stale(entry) => {
1215 let delay @ Delay { link_address } =
1224 entry.enter_delay(bindings_ctx, timers, neighbor);
1225 *self = DynamicNeighborState::Delay(delay);
1226 let event_state = EventState::Dynamic(self.to_event_dynamic_state());
1227 bindings_ctx.on_event(Event::changed(
1228 device_id,
1229 event_state,
1230 neighbor,
1231 bindings_ctx.now(),
1232 ));
1233
1234 (LinkResolutionResult::Resolved(link_address), false)
1235 }
1236 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1237 | DynamicNeighborState::Delay(Delay { link_address })
1238 | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1239 (LinkResolutionResult::Resolved(*link_address), false)
1240 }
1241 DynamicNeighborState::Unreachable(unreachable) => {
1242 let Unreachable { link_address, mode: _ } = unreachable;
1243 let link_address = *link_address;
1244
1245 let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1247 core_ctx,
1248 bindings_ctx,
1249 timers,
1250 neighbor,
1251 );
1252 (LinkResolutionResult::Resolved(link_address), do_multicast_solicit)
1253 }
1254 }
1255 }
1256
1257 fn handle_packet_queued_to_send<I, CC, S>(
1263 &mut self,
1264 core_ctx: &mut CC,
1265 bindings_ctx: &mut BC,
1266 timers: &mut TimerHeap<I, BC>,
1267 device_id: &CC::DeviceId,
1268 neighbor: SpecifiedAddr<I::Addr>,
1269 body: S,
1270 meta: BC::TxMetadata,
1271 ) -> Result<bool, SendFrameError<S>>
1272 where
1273 I: Ip,
1274 BC: NudBindingsContext<I, D, CC::DeviceId>,
1275 CC: NudSenderContext<I, D, BC>,
1276 S: NetworkSerializer,
1277 S::Buffer: BufferMut,
1278 {
1279 match self {
1280 DynamicNeighborState::Incomplete(incomplete) => {
1281 incomplete.queue_packet(body, meta).map(|()| false).map_err(|e| e.err_into())
1282 }
1283 DynamicNeighborState::Stale(entry) => {
1290 let delay @ Delay { link_address } =
1298 entry.enter_delay(bindings_ctx, timers, neighbor);
1299 *self = DynamicNeighborState::Delay(delay);
1300 let event_state = EventState::Dynamic(self.to_event_dynamic_state());
1301 bindings_ctx.on_event(Event::changed(
1302 device_id,
1303 event_state,
1304 neighbor,
1305 bindings_ctx.now(),
1306 ));
1307
1308 core_ctx.send_ip_packet_to_neighbor_link_addr(
1309 bindings_ctx,
1310 link_address,
1311 body,
1312 meta,
1313 )?;
1314
1315 Ok(false)
1316 }
1317 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1318 | DynamicNeighborState::Delay(Delay { link_address })
1319 | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1320 core_ctx.send_ip_packet_to_neighbor_link_addr(
1321 bindings_ctx,
1322 *link_address,
1323 body,
1324 meta,
1325 )?;
1326
1327 Ok(false)
1328 }
1329 DynamicNeighborState::Unreachable(unreachable) => {
1330 let Unreachable { link_address, mode: _ } = unreachable;
1331 core_ctx.send_ip_packet_to_neighbor_link_addr(
1332 bindings_ctx,
1333 *link_address,
1334 body,
1335 meta,
1336 )?;
1337
1338 let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1339 core_ctx,
1340 bindings_ctx,
1341 timers,
1342 neighbor,
1343 );
1344 Ok(do_multicast_solicit)
1345 }
1346 }
1347 }
1348
1349 fn handle_probe<I, CC>(
1350 &mut self,
1351 core_ctx: &mut CC,
1352 bindings_ctx: &mut BC,
1353 timers: &mut TimerHeap<I, BC>,
1354 device_id: &CC::DeviceId,
1355 neighbor: SpecifiedAddr<I::Addr>,
1356 link_address: D::Address,
1357 num_entries: usize,
1358 last_gc: &mut Option<BC::Instant>,
1359 ) where
1360 I: Ip,
1361 BC: NudBindingsContext<I, D, CC::DeviceId>,
1362 CC: NudSenderContext<I, D, BC>,
1363 {
1364 let transition_to_stale = match self {
1373 DynamicNeighborState::Incomplete(_) => true,
1374 DynamicNeighborState::Reachable(Reachable {
1375 link_address: current,
1376 last_confirmed_at: _,
1377 })
1378 | DynamicNeighborState::Stale(Stale { link_address: current })
1379 | DynamicNeighborState::Delay(Delay { link_address: current })
1380 | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1381 | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1382 current != &link_address
1383 }
1384 };
1385 if transition_to_stale {
1386 self.enter_stale(
1387 core_ctx,
1388 bindings_ctx,
1389 timers,
1390 device_id,
1391 neighbor,
1392 link_address,
1393 num_entries,
1394 last_gc,
1395 );
1396 }
1397 }
1398
1399 fn handle_confirmation<I, CC>(
1400 &mut self,
1401 core_ctx: &mut CC,
1402 bindings_ctx: &mut BC,
1403 timers: &mut TimerHeap<I, BC>,
1404 device_id: &CC::DeviceId,
1405 neighbor: SpecifiedAddr<I::Addr>,
1406 link_address: Option<D::Address>,
1407 flags: ConfirmationFlags,
1408 num_entries: usize,
1409 last_gc: &mut Option<BC::Instant>,
1410 ) where
1411 I: Ip,
1412 BC: NudBindingsContext<I, D, CC::DeviceId>,
1413 CC: NudSenderContext<I, D, BC>,
1414 {
1415 let ConfirmationFlags { solicited_flag, override_flag } = flags;
1416 enum NewState<A> {
1417 Reachable { link_address: A },
1418 Stale { link_address: A },
1419 }
1420
1421 let new_state = match self {
1422 DynamicNeighborState::Incomplete(Incomplete {
1423 transmit_counter: _,
1424 pending_frames: _,
1425 notifiers: _,
1426 _marker,
1427 }) => {
1428 link_address.map(|link_address| {
1440 if solicited_flag {
1441 NewState::Reachable { link_address }
1442 } else {
1443 NewState::Stale { link_address }
1444 }
1445 })
1446 }
1447
1448 DynamicNeighborState::Reachable(Reachable {
1452 link_address: current,
1453 last_confirmed_at,
1454 }) if !core_ctx.override_lock_time().is_zero()
1455 && solicited_flag
1456 && bindings_ctx.now().saturating_duration_since(*last_confirmed_at)
1457 < core_ctx.override_lock_time() =>
1458 {
1459 log::warn!(
1460 "Ignoring duplicate neighbor confirmation for {:?}. Current address {:?}.
1461 New address {:?}",
1462 neighbor,
1463 current,
1464 link_address
1465 );
1466 None
1467 }
1468
1469 DynamicNeighborState::Reachable(Reachable {
1470 link_address: current,
1471 last_confirmed_at: _,
1472 })
1473 | DynamicNeighborState::Stale(Stale { link_address: current })
1474 | DynamicNeighborState::Delay(Delay { link_address: current })
1475 | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1476 | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1477 let updated_link_address = link_address
1496 .and_then(|link_address| (current != &link_address).then_some(link_address));
1497
1498 match (solicited_flag, updated_link_address, override_flag) {
1499 (true, _, true) | (true, None, _) => {
1505 Some(NewState::Reachable { link_address: link_address.unwrap_or(*current) })
1506 }
1507 (_, Some(_), false) => match self {
1517 DynamicNeighborState::Reachable(Reachable {
1519 link_address,
1520 last_confirmed_at: _,
1521 }) => Some(NewState::Stale { link_address: *link_address }),
1522 DynamicNeighborState::Stale(_)
1524 | DynamicNeighborState::Delay(_)
1525 | DynamicNeighborState::Probe(_)
1526 | DynamicNeighborState::Unreachable(_) => None,
1527 DynamicNeighborState::Incomplete(_) => unreachable!(),
1529 },
1530 (false, Some(link_address), true) => Some(NewState::Stale { link_address }),
1536 (false, None, _) => None,
1541 }
1542 }
1543 };
1544 match new_state {
1545 Some(NewState::Reachable { link_address }) => self.enter_reachable(
1546 core_ctx,
1547 bindings_ctx,
1548 timers,
1549 device_id,
1550 neighbor,
1551 link_address,
1552 ),
1553 Some(NewState::Stale { link_address }) => self.enter_stale(
1554 core_ctx,
1555 bindings_ctx,
1556 timers,
1557 device_id,
1558 neighbor,
1559 link_address,
1560 num_entries,
1561 last_gc,
1562 ),
1563 None => {}
1564 }
1565 }
1566}
1567
1568#[cfg(any(test, feature = "testutils"))]
1569pub(crate) mod testutil {
1570 use super::*;
1571
1572 use alloc::sync::Arc;
1573
1574 use netstack3_base::sync::Mutex;
1575 use netstack3_base::testutil::{FakeBindingsCtx, FakeCoreCtx};
1576
1577 pub fn assert_dynamic_neighbor_with_addr<
1579 I: Ip,
1580 D: LinkDevice,
1581 BC: NudBindingsContext<I, D, CC::DeviceId>,
1582 CC: NudContext<I, D, BC>,
1583 >(
1584 core_ctx: &mut CC,
1585 device_id: CC::DeviceId,
1586 neighbor: SpecifiedAddr<I::Addr>,
1587 expected_link_addr: D::Address,
1588 ) {
1589 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1590 assert_matches!(
1591 neighbors.get(&neighbor),
1592 Some(NeighborState::Dynamic(
1593 DynamicNeighborState::Reachable(Reachable{ link_address, last_confirmed_at: _ })
1594 | DynamicNeighborState::Stale(Stale{ link_address })
1595 )) => {
1596 assert_eq!(link_address, &expected_link_addr)
1597 }
1598 )
1599 })
1600 }
1601
1602 pub fn assert_dynamic_neighbor_state<I, D, BC, CC>(
1604 core_ctx: &mut CC,
1605 device_id: CC::DeviceId,
1606 neighbor: SpecifiedAddr<I::Addr>,
1607 expected_state: DynamicNeighborState<D, BC>,
1608 ) where
1609 I: Ip,
1610 D: LinkDevice + PartialEq,
1611 BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata: PartialEq>,
1612 CC: NudContext<I, D, BC>,
1613 {
1614 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1615 assert_matches!(
1616 neighbors.get(&neighbor),
1617 Some(NeighborState::Dynamic(state)) => {
1618 assert_eq!(state, &expected_state)
1619 }
1620 )
1621 })
1622 }
1623
1624 pub fn assert_neighbor_unknown<
1626 I: Ip,
1627 D: LinkDevice,
1628 BC: NudBindingsContext<I, D, CC::DeviceId>,
1629 CC: NudContext<I, D, BC>,
1630 >(
1631 core_ctx: &mut CC,
1632 device_id: CC::DeviceId,
1633 neighbor: SpecifiedAddr<I::Addr>,
1634 ) {
1635 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1636 assert_matches!(neighbors.get(&neighbor), None)
1637 })
1638 }
1639
1640 impl<D: LinkDevice, Id, Event: Debug, State, FrameMeta> LinkResolutionContext<D>
1641 for FakeBindingsCtx<Id, Event, State, FrameMeta>
1642 {
1643 type Notifier = FakeLinkResolutionNotifier<D>;
1644 }
1645
1646 #[derive(Debug)]
1648 pub struct FakeLinkResolutionNotifier<D: LinkDevice>(
1649 Arc<Mutex<Option<Result<D::Address, AddressResolutionFailed>>>>,
1650 );
1651
1652 impl<D: LinkDevice> LinkResolutionNotifier<D> for FakeLinkResolutionNotifier<D> {
1653 type Observer = Arc<Mutex<Option<Result<D::Address, AddressResolutionFailed>>>>;
1654
1655 fn new() -> (Self, Self::Observer) {
1656 let inner = Arc::new(Mutex::new(None));
1657 (Self(inner.clone()), inner)
1658 }
1659
1660 fn notify(self, result: Result<D::Address, AddressResolutionFailed>) {
1661 let Self(inner) = self;
1662 let mut inner = inner.lock();
1663 assert_eq!(*inner, None, "resolved link address was set more than once");
1664 *inner = Some(result);
1665 }
1666 }
1667
1668 impl<S, Meta, DeviceId> UseDelegateNudContext for FakeCoreCtx<S, Meta, DeviceId> where
1669 S: UseDelegateNudContext
1670 {
1671 }
1672 impl<I: Ip, S, Meta, DeviceId> DelegateNudContext<I> for FakeCoreCtx<S, Meta, DeviceId>
1673 where
1674 S: DelegateNudContext<I>,
1675 {
1676 type Delegate<T> = S::Delegate<T>;
1677 }
1678}
1679
1680#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1681enum NudEvent {
1682 RetransmitMulticastProbe,
1683 ReachableTime,
1684 DelayFirstProbe,
1685 RetransmitUnicastProbe,
1686}
1687
1688#[derive(GenericOverIp, Copy, Clone, Debug, Eq, PartialEq, Hash)]
1690#[generic_over_ip(I, Ip)]
1691pub struct NudTimerId<I: Ip, L: LinkDevice, D: WeakDeviceIdentifier> {
1692 device_id: D,
1693 timer_type: NudTimerType,
1694 _marker: PhantomData<(I, L)>,
1695}
1696
1697#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1698enum NudTimerType {
1699 Neighbor,
1700 GarbageCollection,
1701}
1702
1703#[derive(Debug)]
1705pub(crate) struct TimerHeap<I: Ip, BT: TimerBindingsTypes + InstantBindingsTypes> {
1706 gc: BT::Timer,
1707 neighbor: LocalTimerHeap<SpecifiedAddr<I::Addr>, NudEvent, BT>,
1708}
1709
1710impl<I: Ip, BC: TimerContext> TimerHeap<I, BC> {
1711 fn new<
1712 DeviceId: WeakDeviceIdentifier,
1713 L: LinkDevice,
1714 CC: CoreTimerContext<NudTimerId<I, L, DeviceId>, BC>,
1715 >(
1716 bindings_ctx: &mut BC,
1717 device_id: DeviceId,
1718 ) -> Self {
1719 Self {
1720 neighbor: LocalTimerHeap::new_with_context::<_, CC>(
1721 bindings_ctx,
1722 NudTimerId {
1723 device_id: device_id.clone(),
1724 timer_type: NudTimerType::Neighbor,
1725 _marker: PhantomData,
1726 },
1727 ),
1728 gc: CC::new_timer(
1729 bindings_ctx,
1730 NudTimerId {
1731 device_id,
1732 timer_type: NudTimerType::GarbageCollection,
1733 _marker: PhantomData,
1734 },
1735 ),
1736 }
1737 }
1738
1739 fn schedule_neighbor(
1740 &mut self,
1741 bindings_ctx: &mut BC,
1742 after: NonZeroDuration,
1743 neighbor: SpecifiedAddr<I::Addr>,
1744 event: NudEvent,
1745 ) {
1746 let Self { neighbor: heap, gc: _ } = self;
1747 assert_eq!(heap.schedule_after(bindings_ctx, neighbor, event, after.get()), None);
1748 }
1749
1750 fn schedule_neighbor_at(
1751 &mut self,
1752 bindings_ctx: &mut BC,
1753 at: BC::Instant,
1754 neighbor: SpecifiedAddr<I::Addr>,
1755 event: NudEvent,
1756 ) {
1757 let Self { neighbor: heap, gc: _ } = self;
1758 assert_eq!(heap.schedule_instant(bindings_ctx, neighbor, event, at), None);
1759 }
1760
1761 fn cancel_neighbor(
1763 &mut self,
1764 bindings_ctx: &mut BC,
1765 neighbor: SpecifiedAddr<I::Addr>,
1766 ) -> Option<NudEvent> {
1767 let Self { neighbor: heap, gc: _ } = self;
1768 heap.cancel(bindings_ctx, &neighbor).map(|(_instant, v)| v)
1769 }
1770
1771 fn pop_neighbor(
1772 &mut self,
1773 bindings_ctx: &mut BC,
1774 ) -> Option<(SpecifiedAddr<I::Addr>, NudEvent)> {
1775 let Self { neighbor: heap, gc: _ } = self;
1776 heap.pop(bindings_ctx)
1777 }
1778
1779 fn maybe_schedule_gc(
1782 &mut self,
1783 bindings_ctx: &mut BC,
1784 num_entries: usize,
1785 last_gc: &Option<BC::Instant>,
1786 ) {
1787 let Self { gc, neighbor: _ } = self;
1788 if num_entries > MAX_ENTRIES && bindings_ctx.scheduled_instant(gc).is_none() {
1789 let instant = if let Some(last_gc) = last_gc {
1790 last_gc.panicking_add(MIN_GARBAGE_COLLECTION_INTERVAL.get())
1791 } else {
1792 bindings_ctx.now()
1793 };
1794 assert_eq!(bindings_ctx.schedule_timer_instant(instant, gc), None);
1798 }
1799 }
1800}
1801
1802#[derive(Debug)]
1804pub struct NudState<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> {
1805 neighbors: HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>>,
1807 last_gc: Option<BT::Instant>,
1808 timer_heap: TimerHeap<I, BT>,
1809}
1810
1811impl<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> NudState<I, D, BT> {
1812 #[cfg(any(test, feature = "testutils"))]
1814 pub fn neighbors(&self) -> &HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>> {
1815 &self.neighbors
1816 }
1817
1818 fn entry_and_timer_heap(
1819 &mut self,
1820 addr: SpecifiedAddr<I::Addr>,
1821 ) -> (Entry<'_, SpecifiedAddr<I::Addr>, NeighborState<D, BT>>, &mut TimerHeap<I, BT>) {
1822 let Self { neighbors, timer_heap, .. } = self;
1823 (neighbors.entry(addr), timer_heap)
1824 }
1825}
1826
1827impl<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D> + TimerContext> NudState<I, D, BC> {
1828 pub fn new<
1830 DeviceId: WeakDeviceIdentifier,
1831 CC: CoreTimerContext<NudTimerId<I, D, DeviceId>, BC>,
1832 >(
1833 bindings_ctx: &mut BC,
1834 device_id: DeviceId,
1835 ) -> Self {
1836 Self {
1837 neighbors: Default::default(),
1838 last_gc: None,
1839 timer_heap: TimerHeap::new::<_, _, CC>(bindings_ctx, device_id),
1840 }
1841 }
1842}
1843
1844pub trait NudBindingsContext<I: Ip, D: LinkDevice, DeviceId>:
1846 TimerContext
1847 + LinkResolutionContext<D>
1848 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1849 + NudBindingsTypes<D>
1850{
1851}
1852
1853impl<
1854 I: Ip,
1855 D: LinkDevice,
1856 DeviceId,
1857 BC: TimerContext
1858 + LinkResolutionContext<D>
1859 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1860 + NudBindingsTypes<D>,
1861> NudBindingsContext<I, D, DeviceId> for BC
1862{
1863}
1864
1865pub trait NudBindingsTypes<D: LinkDevice>:
1867 LinkResolutionContext<D> + InstantBindingsTypes + TimerBindingsTypes + TxMetadataBindingsTypes
1868{
1869}
1870
1871impl<BT, D> NudBindingsTypes<D> for BT
1872where
1873 D: LinkDevice,
1874 BT: LinkResolutionContext<D>
1875 + InstantBindingsTypes
1876 + TimerBindingsTypes
1877 + TxMetadataBindingsTypes,
1878{
1879}
1880
1881pub trait LinkResolutionContext<D: LinkDevice> {
1883 type Notifier: LinkResolutionNotifier<D>;
1886}
1887
1888pub trait LinkResolutionNotifier<D: LinkDevice>: Debug + Sized + Send {
1891 type Observer;
1894
1895 fn new() -> (Self, Self::Observer);
1897
1898 fn notify(self, result: Result<D::Address, AddressResolutionFailed>);
1901}
1902
1903pub trait NudContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
1905 type ConfigCtx<'a>: NudConfigContext<I>;
1907 type SenderCtx<'a>: NudSenderContext<I, D, BC, DeviceId = Self::DeviceId>;
1909
1910 fn with_nud_state_mut_and_sender_ctx<
1913 O,
1914 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1915 >(
1916 &mut self,
1917 device_id: &Self::DeviceId,
1918 cb: F,
1919 ) -> O;
1920
1921 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1924 &mut self,
1925 device_id: &Self::DeviceId,
1926 cb: F,
1927 ) -> O;
1928
1929 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1931 &mut self,
1932 device_id: &Self::DeviceId,
1933 cb: F,
1934 ) -> O;
1935
1936 fn send_neighbor_solicitation(
1941 &mut self,
1942 bindings_ctx: &mut BC,
1943 device_id: &Self::DeviceId,
1944 lookup_addr: SpecifiedAddr<I::Addr>,
1945 remote_link_addr: Option<D::Address>,
1946 );
1947}
1948
1949pub trait UseDelegateNudContext {}
1952
1953pub trait DelegateNudContext<I: Ip>: UseDelegateNudContext + Sized {
1959 type Delegate<T>: ref_cast::RefCast<From = T>;
1961 fn wrap(&mut self) -> &mut Self::Delegate<Self> {
1963 <Self::Delegate<Self> as ref_cast::RefCast>::ref_cast_mut(self)
1964 }
1965}
1966
1967impl<I, D, BC, CC> NudContext<I, D, BC> for CC
1968where
1969 I: Ip,
1970 D: LinkDevice,
1971 BC: NudBindingsTypes<D>,
1972 CC: DelegateNudContext<I, Delegate<CC>: NudContext<I, D, BC, DeviceId = CC::DeviceId>>
1973 + UseDelegateNudContext
1976 + DeviceIdContext<D>,
1977{
1978 type ConfigCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::ConfigCtx<'a>;
1979 type SenderCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::SenderCtx<'a>;
1980 fn with_nud_state_mut_and_sender_ctx<
1981 O,
1982 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1983 >(
1984 &mut self,
1985 device_id: &Self::DeviceId,
1986 cb: F,
1987 ) -> O {
1988 self.wrap().with_nud_state_mut_and_sender_ctx(device_id, cb)
1989 }
1990
1991 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1992 &mut self,
1993 device_id: &Self::DeviceId,
1994 cb: F,
1995 ) -> O {
1996 self.wrap().with_nud_state_mut(device_id, cb)
1997 }
1998 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1999 &mut self,
2000 device_id: &Self::DeviceId,
2001 cb: F,
2002 ) -> O {
2003 self.wrap().with_nud_state(device_id, cb)
2004 }
2005 fn send_neighbor_solicitation(
2006 &mut self,
2007 bindings_ctx: &mut BC,
2008 device_id: &Self::DeviceId,
2009 lookup_addr: SpecifiedAddr<I::Addr>,
2010 remote_link_addr: Option<D::Address>,
2011 ) {
2012 self.wrap().send_neighbor_solicitation(
2013 bindings_ctx,
2014 device_id,
2015 lookup_addr,
2016 remote_link_addr,
2017 )
2018 }
2019}
2020
2021pub trait NudIcmpIpExt: packet_formats::ip::IpExt {
2023 type Metadata;
2026
2027 fn extract_metadata<B: SplitByteSlice>(packet: &Self::Packet<B>) -> Self::Metadata;
2029}
2030
2031impl NudIcmpIpExt for Ipv4 {
2032 type Metadata = Ipv4FragmentType;
2033
2034 fn extract_metadata<B: SplitByteSlice>(packet: &Ipv4Packet<B>) -> Self::Metadata {
2035 packet.fragment_type()
2036 }
2037}
2038
2039impl NudIcmpIpExt for Ipv6 {
2040 type Metadata = ();
2041
2042 fn extract_metadata<B: SplitByteSlice>(_packet: &Ipv6Packet<B>) -> Self::Metadata {
2043 ()
2044 }
2045}
2046
2047pub trait NudIcmpContext<I: NudIcmpIpExt, D: LinkDevice, BC>: DeviceIdContext<D> {
2050 fn send_icmp_dest_unreachable(
2057 &mut self,
2058 bindings_ctx: &mut BC,
2059 frame: Buf<Vec<u8>>,
2060 device_id: Option<&Self::DeviceId>,
2061 original_src_ip: SocketIpAddr<I::Addr>,
2062 original_dst_ip: SocketIpAddr<I::Addr>,
2063 header_len: usize,
2064 proto: I::Proto,
2065 metadata: I::Metadata,
2066 );
2067}
2068
2069#[derive(Clone, Debug)]
2071pub struct NudUserConfig {
2072 pub max_unicast_solicitations: NonZeroU16,
2077 pub max_multicast_solicitations: NonZeroU16,
2082 pub base_reachable_time: NonZeroDuration,
2088 pub retrans_timer: NonZeroDuration,
2094}
2095
2096impl Default for NudUserConfig {
2097 fn default() -> Self {
2098 NudUserConfig {
2099 max_unicast_solicitations: DEFAULT_MAX_UNICAST_SOLICIT,
2100 max_multicast_solicitations: DEFAULT_MAX_MULTICAST_SOLICIT,
2101 base_reachable_time: DEFAULT_BASE_REACHABLE_TIME,
2102 retrans_timer: DEFAULT_RETRANS_TIMER,
2103 }
2104 }
2105}
2106
2107#[allow(missing_docs)]
2111#[derive(Clone, Debug, Eq, PartialEq, Default)]
2112pub struct NudUserConfigUpdate {
2113 pub max_unicast_solicitations: Option<NonZeroU16>,
2114 pub max_multicast_solicitations: Option<NonZeroU16>,
2115 pub base_reachable_time: Option<NonZeroDuration>,
2116 pub retrans_timer: Option<NonZeroDuration>,
2117}
2118
2119impl NudUserConfigUpdate {
2120 pub fn apply_and_take_previous(mut self, config: &mut NudUserConfig) -> Self {
2123 fn swap_if_set<T>(opt: &mut Option<T>, target: &mut T) {
2124 if let Some(opt) = opt.as_mut() {
2125 core::mem::swap(opt, target)
2126 }
2127 }
2128 let Self {
2129 max_unicast_solicitations,
2130 max_multicast_solicitations,
2131 base_reachable_time,
2132 retrans_timer,
2133 } = &mut self;
2134 swap_if_set(max_unicast_solicitations, &mut config.max_unicast_solicitations);
2135 swap_if_set(max_multicast_solicitations, &mut config.max_multicast_solicitations);
2136 swap_if_set(base_reachable_time, &mut config.base_reachable_time);
2137 swap_if_set(retrans_timer, &mut config.retrans_timer);
2138
2139 self
2140 }
2141}
2142
2143pub trait NudConfigContext<I: Ip> {
2146 fn retransmit_timeout(&mut self) -> NonZeroDuration;
2153
2154 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O;
2156
2157 fn max_unicast_solicit(&mut self) -> NonZeroU16 {
2159 self.with_nud_user_config(|NudUserConfig { max_unicast_solicitations, .. }| {
2160 *max_unicast_solicitations
2161 })
2162 }
2163
2164 fn max_multicast_solicit(&mut self) -> NonZeroU16 {
2166 self.with_nud_user_config(|NudUserConfig { max_multicast_solicitations, .. }| {
2167 *max_multicast_solicitations
2168 })
2169 }
2170
2171 fn base_reachable_time(&mut self) -> NonZeroDuration {
2174 self.with_nud_user_config(|NudUserConfig { base_reachable_time, .. }| *base_reachable_time)
2175 }
2176
2177 fn override_lock_time(&mut self) -> Duration;
2180}
2181
2182pub trait NudSenderContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>:
2185 NudConfigContext<I> + DeviceIdContext<D>
2186{
2187 fn send_ip_packet_to_neighbor_link_addr<S>(
2189 &mut self,
2190 bindings_ctx: &mut BC,
2191 neighbor_link_addr: D::Address,
2192 body: S,
2193 meta: BC::TxMetadata,
2194 ) -> Result<(), SendFrameError<S>>
2195 where
2196 S: NetworkSerializer,
2197 S::Buffer: BufferMut;
2198}
2199
2200pub trait NudIpHandler<I: Ip, BC>: DeviceIdContext<AnyDevice> {
2202 fn handle_neighbor_probe(
2207 &mut self,
2208 bindings_ctx: &mut BC,
2209 device_id: &Self::DeviceId,
2210 neighbor: SpecifiedAddr<I::Addr>,
2211 link_addr: &[u8],
2212 );
2213
2214 fn handle_neighbor_confirmation(
2218 &mut self,
2219 bindings_ctx: &mut BC,
2220 device_id: &Self::DeviceId,
2221 neighbor: SpecifiedAddr<I::Addr>,
2222 link_addr: Option<&[u8]>,
2223 flags: ConfirmationFlags,
2224 );
2225
2226 fn flush_neighbor_table(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2228}
2229
2230#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2232pub enum LinkResolutionResult<A: LinkAddress, Observer> {
2233 Resolved(A),
2235 Pending(Observer),
2237}
2238
2239pub trait NudHandler<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
2241 fn handle_neighbor_update(
2244 &mut self,
2245 bindings_ctx: &mut BC,
2246 device_id: &Self::DeviceId,
2247 neighbor: SpecifiedAddr<I::Addr>,
2252 source: DynamicNeighborUpdateSource<D::Address>,
2253 );
2254
2255 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2257
2258 fn send_ip_packet_to_neighbor<S>(
2263 &mut self,
2264 bindings_ctx: &mut BC,
2265 device_id: &Self::DeviceId,
2266 neighbor: SpecifiedAddr<I::Addr>,
2267 body: S,
2268 meta: BC::TxMetadata,
2269 ) -> Result<(), SendFrameError<S>>
2270 where
2271 S: NetworkSerializer,
2272 S::Buffer: BufferMut;
2273}
2274
2275enum TransmitProbe<A> {
2276 Multicast,
2277 Unicast(A),
2278}
2279
2280impl<
2281 I: NudIcmpIpExt,
2282 D: LinkDevice,
2283 BC: NudBindingsContext<I, D, CC::DeviceId>,
2284 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2285> HandleableTimer<CC, BC> for NudTimerId<I, D, CC::WeakDeviceId>
2286{
2287 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
2288 let Self { device_id, timer_type, _marker: PhantomData } = self;
2289 let Some(device_id) = device_id.upgrade() else {
2290 return;
2291 };
2292 match timer_type {
2293 NudTimerType::Neighbor => handle_neighbor_timer(core_ctx, bindings_ctx, device_id),
2294 NudTimerType::GarbageCollection => collect_garbage(core_ctx, bindings_ctx, device_id),
2295 }
2296 }
2297}
2298
2299fn handle_neighbor_timer<I, D, CC, BC>(
2300 core_ctx: &mut CC,
2301 bindings_ctx: &mut BC,
2302 device_id: CC::DeviceId,
2303) where
2304 I: NudIcmpIpExt,
2305 D: LinkDevice,
2306 BC: NudBindingsContext<I, D, CC::DeviceId>,
2307 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2308{
2309 enum Action<L, A, M> {
2310 TransmitProbe { probe: TransmitProbe<L>, to: A },
2311 SendIcmpDestUnreachable(VecDeque<(Buf<Vec<u8>>, M)>),
2312 }
2313 let action = core_ctx.with_nud_state_mut(
2314 &device_id,
2315 |NudState { neighbors, last_gc, timer_heap }, core_ctx| {
2316 let (lookup_addr, event) = timer_heap.pop_neighbor(bindings_ctx)?;
2317 let num_entries = neighbors.len();
2318 let mut entry = match neighbors.entry(lookup_addr) {
2319 Entry::Occupied(entry) => entry,
2320 Entry::Vacant(_) => panic!("timer fired for invalid entry"),
2321 };
2322
2323 match entry.get_mut() {
2324 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)) => {
2325 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2326
2327 if incomplete.schedule_timer_if_should_retransmit(
2328 core_ctx,
2329 bindings_ctx,
2330 timer_heap,
2331 lookup_addr,
2332 ) {
2333 Some(Action::TransmitProbe {
2334 probe: TransmitProbe::Multicast,
2335 to: lookup_addr,
2336 })
2337 } else {
2338 debug!("neighbor resolution failed for {lookup_addr}; removing entry");
2346 let Incomplete {
2347 transmit_counter: _,
2348 ref mut pending_frames,
2349 notifiers: _,
2350 _marker,
2351 } = assert_matches!(
2352 entry.remove(),
2353 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete))
2354 => incomplete
2355 );
2356 let pending_frames = core::mem::take(pending_frames);
2357 bindings_ctx.on_event(Event::removed(
2358 &device_id,
2359 lookup_addr,
2360 bindings_ctx.now(),
2361 ));
2362 Some(Action::SendIcmpDestUnreachable(pending_frames))
2363 }
2364 }
2365 NeighborState::Dynamic(DynamicNeighborState::Probe(probe)) => {
2366 assert_eq!(event, NudEvent::RetransmitUnicastProbe);
2367
2368 let Probe { link_address, transmit_counter: _ } = probe;
2369 let link_address = *link_address;
2370 if probe.schedule_timer_if_should_retransmit(
2371 core_ctx,
2372 bindings_ctx,
2373 timer_heap,
2374 lookup_addr,
2375 ) {
2376 Some(Action::TransmitProbe {
2377 probe: TransmitProbe::Unicast(link_address),
2378 to: lookup_addr,
2379 })
2380 } else {
2381 let unreachable =
2382 probe.enter_unreachable(bindings_ctx, timer_heap, num_entries, last_gc);
2383 *entry.get_mut() =
2384 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable));
2385 let event_state = entry.get_mut().to_event_state();
2386 let event = Event::changed(
2387 &device_id,
2388 event_state,
2389 lookup_addr,
2390 bindings_ctx.now(),
2391 );
2392 bindings_ctx.on_event(event);
2393 None
2394 }
2395 }
2396 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable)) => {
2397 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2398 unreachable
2399 .handle_timer(core_ctx, bindings_ctx, timer_heap, &device_id, lookup_addr)
2400 .map(|probe| Action::TransmitProbe { probe, to: lookup_addr })
2401 }
2402 NeighborState::Dynamic(DynamicNeighborState::Reachable(Reachable {
2403 link_address,
2404 last_confirmed_at,
2405 })) => {
2406 assert_eq!(event, NudEvent::ReachableTime);
2407 let link_address = *link_address;
2408
2409 let expiration =
2410 last_confirmed_at.saturating_add(core_ctx.base_reachable_time().get());
2411 if expiration > bindings_ctx.now() {
2412 timer_heap.schedule_neighbor_at(
2413 bindings_ctx,
2414 expiration,
2415 lookup_addr,
2416 NudEvent::ReachableTime,
2417 );
2418 } else {
2419 *entry.get_mut() =
2427 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2428 link_address,
2429 }));
2430 let event_state = entry.get_mut().to_event_state();
2431 let event = Event::changed(
2432 &device_id,
2433 event_state,
2434 lookup_addr,
2435 bindings_ctx.now(),
2436 );
2437 bindings_ctx.on_event(event);
2438
2439 timer_heap.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
2443 }
2444
2445 None
2446 }
2447 NeighborState::Dynamic(DynamicNeighborState::Delay(delay)) => {
2448 assert_eq!(event, NudEvent::DelayFirstProbe);
2449
2450 let probe @ Probe { link_address, transmit_counter: _ } =
2457 delay.enter_probe(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2458 *entry.get_mut() = NeighborState::Dynamic(DynamicNeighborState::Probe(probe));
2459 let event_state = entry.get_mut().to_event_state();
2460 bindings_ctx.on_event(Event::changed(
2461 &device_id,
2462 event_state,
2463 lookup_addr,
2464 bindings_ctx.now(),
2465 ));
2466
2467 Some(Action::TransmitProbe {
2468 probe: TransmitProbe::Unicast(link_address),
2469 to: lookup_addr,
2470 })
2471 }
2472 state @ (NeighborState::Static(_)
2473 | NeighborState::Dynamic(DynamicNeighborState::Stale(_))) => {
2474 panic!("timer unexpectedly fired in state {state:?}")
2475 }
2476 }
2477 },
2478 );
2479
2480 match action {
2481 Some(Action::SendIcmpDestUnreachable(mut pending_frames)) => {
2482 for (mut frame, meta) in pending_frames.drain(..) {
2483 core::mem::drop(meta);
2486
2487 let Some((packet, original_src_ip, original_dst_ip)) = frame
2491 .parse_mut::<I::Packet<_>>()
2492 .map_err(|e| {
2493 warn!("not sending ICMP dest unreachable due to parsing error: {:?}", e);
2494 })
2495 .ok()
2496 .and_then(|packet| {
2497 let original_src_ip = SocketIpAddr::new(packet.src_ip())?;
2498 let original_dst_ip = SocketIpAddr::new(packet.dst_ip())?;
2499 Some((packet, original_src_ip, original_dst_ip))
2500 })
2501 .or_else(|| {
2502 core_ctx.counters().icmp_dest_unreachable_dropped.increment();
2503 None
2504 })
2505 else {
2506 continue;
2507 };
2508 let header_metadata = I::extract_metadata(&packet);
2509 let header_len = packet.parse_metadata().header_len();
2510 let proto = packet.proto();
2511 let metadata = packet.parse_metadata();
2512 core::mem::drop(packet);
2513 frame.undo_parse(metadata);
2514 core_ctx.send_icmp_dest_unreachable(
2515 bindings_ctx,
2516 frame,
2517 original_src_ip.as_ref().must_have_zone().then_some(&device_id),
2529 original_src_ip,
2530 original_dst_ip,
2531 header_len,
2532 proto,
2533 header_metadata,
2534 );
2535 }
2536 }
2537 Some(Action::TransmitProbe { probe, to }) => {
2538 let remote_link_addr = match probe {
2539 TransmitProbe::Multicast => None,
2540 TransmitProbe::Unicast(link_addr) => Some(link_addr),
2541 };
2542 core_ctx.send_neighbor_solicitation(bindings_ctx, &device_id, to, remote_link_addr);
2543 }
2544 None => {}
2545 }
2546}
2547
2548impl<I: Ip, D: LinkDevice, BC: NudBindingsContext<I, D, CC::DeviceId>, CC: NudContext<I, D, BC>>
2549 NudHandler<I, D, BC> for CC
2550{
2551 fn handle_neighbor_update(
2552 &mut self,
2553 bindings_ctx: &mut BC,
2554 device_id: &CC::DeviceId,
2555 neighbor: SpecifiedAddr<I::Addr>,
2556 source: DynamicNeighborUpdateSource<D::Address>,
2557 ) {
2558 debug!("received neighbor {:?} from {}", source, neighbor);
2559 self.with_nud_state_mut_and_sender_ctx(
2560 device_id,
2561 |NudState { neighbors, last_gc, timer_heap }, core_ctx| {
2562 let num_entries = neighbors.len();
2563 match neighbors.entry(neighbor) {
2564 Entry::Vacant(e) => match source {
2565 DynamicNeighborUpdateSource::Probe { link_address } => {
2566 insert_new_entry(
2574 bindings_ctx,
2575 device_id,
2576 e,
2577 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2578 link_address,
2579 })),
2580 );
2581
2582 timer_heap.maybe_schedule_gc(bindings_ctx, neighbors.len(), last_gc);
2585 }
2586 DynamicNeighborUpdateSource::Confirmation { .. } => {}
2595 },
2596 Entry::Occupied(e) => match e.into_mut() {
2597 NeighborState::Dynamic(e) => match source {
2598 DynamicNeighborUpdateSource::Probe { link_address } => e.handle_probe(
2599 core_ctx,
2600 bindings_ctx,
2601 timer_heap,
2602 device_id,
2603 neighbor,
2604 link_address,
2605 num_entries,
2606 last_gc,
2607 ),
2608 DynamicNeighborUpdateSource::Confirmation { link_address, flags } => e
2609 .handle_confirmation(
2610 core_ctx,
2611 bindings_ctx,
2612 timer_heap,
2613 device_id,
2614 neighbor,
2615 link_address,
2616 flags,
2617 num_entries,
2618 last_gc,
2619 ),
2620 },
2621 NeighborState::Static(_) => {}
2622 },
2623 }
2624 },
2625 );
2626 }
2627
2628 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
2629 self.with_nud_state_mut(
2630 device_id,
2631 |NudState { neighbors, last_gc: _, timer_heap }, _config| {
2632 neighbors.drain().for_each(|(neighbor, state)| {
2633 match state {
2634 NeighborState::Dynamic(mut entry) => {
2635 entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
2636 }
2637 NeighborState::Static(_) => {}
2638 }
2639 bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
2640 });
2641 },
2642 );
2643 }
2644
2645 fn send_ip_packet_to_neighbor<S>(
2646 &mut self,
2647 bindings_ctx: &mut BC,
2648 device_id: &Self::DeviceId,
2649 lookup_addr: SpecifiedAddr<I::Addr>,
2650 body: S,
2651 meta: BC::TxMetadata,
2652 ) -> Result<(), SendFrameError<S>>
2653 where
2654 S: NetworkSerializer,
2655 S::Buffer: BufferMut,
2656 {
2657 let do_multicast_solicit = self.with_nud_state_mut_and_sender_ctx(
2658 device_id,
2659 |state, core_ctx| -> Result<_, SendFrameError<S>> {
2660 let (entry, timer_heap) = state.entry_and_timer_heap(lookup_addr);
2661 match entry {
2662 Entry::Vacant(e) => {
2663 let incomplete = Incomplete::new_with_packet(
2664 core_ctx,
2665 bindings_ctx,
2666 timer_heap,
2667 lookup_addr,
2668 body,
2669 meta,
2670 )
2671 .map_err(|e| e.err_into())?;
2672 insert_new_entry(
2673 bindings_ctx,
2674 device_id,
2675 e,
2676 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)),
2677 );
2678 Ok(true)
2679 }
2680 Entry::Occupied(e) => {
2681 match e.into_mut() {
2682 NeighborState::Static(link_address) => {
2683 core_ctx.send_ip_packet_to_neighbor_link_addr(
2691 bindings_ctx,
2692 *link_address,
2693 body,
2694 meta,
2695 )?;
2696
2697 Ok(false)
2698 }
2699 NeighborState::Dynamic(e) => {
2700 let do_multicast_solicit = e.handle_packet_queued_to_send(
2701 core_ctx,
2702 bindings_ctx,
2703 timer_heap,
2704 device_id,
2705 lookup_addr,
2706 body,
2707 meta,
2708 )?;
2709
2710 Ok(do_multicast_solicit)
2711 }
2712 }
2713 }
2714 }
2715 },
2716 )?;
2717
2718 if do_multicast_solicit {
2719 self.send_neighbor_solicitation(
2720 bindings_ctx,
2721 &device_id,
2722 lookup_addr,
2723 None,
2724 );
2725 }
2726
2727 Ok(())
2728 }
2729}
2730
2731fn insert_new_entry<
2732 I: Ip,
2733 D: LinkDevice,
2734 DeviceId: DeviceIdentifier,
2735 BC: NudBindingsContext<I, D, DeviceId>,
2736>(
2737 bindings_ctx: &mut BC,
2738 device_id: &DeviceId,
2739 vacant: hash_map::VacantEntry<'_, SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2740 entry: NeighborState<D, BC>,
2741) {
2742 let lookup_addr = *vacant.key();
2743 let state = vacant.insert(entry);
2744 let event = Event::added(device_id, state.to_event_state(), lookup_addr, bindings_ctx.now());
2745 bindings_ctx.on_event(event);
2746}
2747
2748pub fn confirm_reachable<I, D, CC, BC>(
2751 core_ctx: &mut CC,
2752 bindings_ctx: &mut BC,
2753 device_id: &CC::DeviceId,
2754 neighbor: SpecifiedAddr<I::Addr>,
2755) where
2756 I: Ip,
2757 D: LinkDevice,
2758 BC: NudBindingsContext<I, D, CC::DeviceId>,
2759 CC: NudContext<I, D, BC>,
2760{
2761 core_ctx.with_nud_state_mut_and_sender_ctx(
2762 device_id,
2763 |NudState { neighbors, last_gc: _, timer_heap }, core_ctx| {
2764 match neighbors.entry(neighbor) {
2765 Entry::Vacant(_) => {
2766 debug!(
2767 "got an upper-layer confirmation for non-existent neighbor entry {}",
2768 neighbor
2769 );
2770 }
2771 Entry::Occupied(e) => match e.into_mut() {
2772 NeighborState::Static(_) => {}
2773 NeighborState::Dynamic(e) => {
2774 let link_address = match e {
2784 DynamicNeighborState::Incomplete(_) => return,
2785 DynamicNeighborState::Reachable(Reachable {
2786 link_address,
2787 last_confirmed_at: _,
2788 })
2789 | DynamicNeighborState::Stale(Stale { link_address })
2790 | DynamicNeighborState::Delay(Delay { link_address })
2791 | DynamicNeighborState::Probe(Probe {
2792 link_address,
2793 transmit_counter: _,
2794 })
2795 | DynamicNeighborState::Unreachable(Unreachable {
2796 link_address,
2797 mode: _,
2798 }) => *link_address,
2799 };
2800 e.enter_reachable(
2801 core_ctx,
2802 bindings_ctx,
2803 timer_heap,
2804 device_id,
2805 neighbor,
2806 link_address,
2807 );
2808 }
2809 },
2810 }
2811 },
2812 );
2813}
2814
2815fn collect_garbage<I, D, CC, BC>(core_ctx: &mut CC, bindings_ctx: &mut BC, device_id: CC::DeviceId)
2826where
2827 I: Ip,
2828 D: LinkDevice,
2829 BC: NudBindingsContext<I, D, CC::DeviceId>,
2830 CC: NudContext<I, D, BC>,
2831{
2832 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, last_gc, timer_heap }, _| {
2833 let max_to_remove = neighbors.len().saturating_sub(MAX_ENTRIES);
2834 if max_to_remove == 0 {
2835 return;
2836 }
2837
2838 *last_gc = Some(bindings_ctx.now());
2839
2840 fn gc_priority<D: LinkDevice, BT: NudBindingsTypes<D>>(
2849 state: &DynamicNeighborState<D, BT>,
2850 ) -> usize {
2851 match state {
2852 DynamicNeighborState::Incomplete(_)
2853 | DynamicNeighborState::Reachable(_)
2854 | DynamicNeighborState::Delay(_)
2855 | DynamicNeighborState::Probe(_) => unreachable!(
2856 "the netstack should only ever discard STALE or UNREACHABLE entries; \
2857 found {:?}",
2858 state,
2859 ),
2860 DynamicNeighborState::Stale(_) => 0,
2861 DynamicNeighborState::Unreachable(Unreachable {
2862 link_address: _,
2863 mode: UnreachableMode::Backoff { probes_sent: _, packet_sent: _ },
2864 }) => 1,
2865 DynamicNeighborState::Unreachable(Unreachable {
2866 link_address: _,
2867 mode: UnreachableMode::WaitingForPacketSend,
2868 }) => 2,
2869 }
2870 }
2871
2872 struct SortEntry<'a, K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> {
2873 key: K,
2874 state: &'a mut DynamicNeighborState<D, BT>,
2875 }
2876
2877 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialEq for SortEntry<'_, K, D, BT> {
2878 fn eq(&self, other: &Self) -> bool {
2879 self.key == other.key && gc_priority(self.state) == gc_priority(other.state)
2880 }
2881 }
2882 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Eq for SortEntry<'_, K, D, BT> {}
2883 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Ord for SortEntry<'_, K, D, BT> {
2884 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2885 gc_priority(self.state).cmp(&gc_priority(other.state)).reverse()
2889 }
2890 }
2891 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialOrd for SortEntry<'_, K, D, BT> {
2892 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
2893 Some(self.cmp(&other))
2894 }
2895 }
2896
2897 let mut entries_to_remove = BinaryHeap::with_capacity(max_to_remove);
2898 for (ip, neighbor) in neighbors.iter_mut() {
2899 match neighbor {
2900 NeighborState::Static(_) => {
2901 continue;
2903 }
2904 NeighborState::Dynamic(state) => {
2905 match state {
2906 DynamicNeighborState::Incomplete(_)
2907 | DynamicNeighborState::Reachable(_)
2908 | DynamicNeighborState::Delay(_)
2909 | DynamicNeighborState::Probe(_) => {
2910 continue;
2912 }
2913 DynamicNeighborState::Stale(_) | DynamicNeighborState::Unreachable(_) => {
2914 if entries_to_remove.len() < max_to_remove {
2916 entries_to_remove.push(SortEntry { key: ip, state });
2917 continue;
2918 }
2919 let minimum = entries_to_remove
2923 .peek()
2924 .expect("heap should have at least 1 entry");
2925 let candidate = SortEntry { key: ip, state };
2926 if &candidate > minimum {
2927 let _: SortEntry<'_, _, _, _> = entries_to_remove.pop().unwrap();
2928 entries_to_remove.push(candidate);
2929 }
2930 }
2931 }
2932 }
2933 }
2934 }
2935
2936 let entries_to_remove = entries_to_remove
2937 .into_iter()
2938 .map(|SortEntry { key: neighbor, state }| {
2939 state.cancel_timer(bindings_ctx, timer_heap, *neighbor);
2940 *neighbor
2941 })
2942 .collect::<Vec<_>>();
2943
2944 for neighbor in entries_to_remove {
2945 assert_matches!(neighbors.remove(&neighbor), Some(_));
2946 bindings_ctx.on_event(Event::removed(&device_id, neighbor, bindings_ctx.now()));
2947 }
2948 })
2949}
2950
2951#[cfg(test)]
2952mod tests {
2953 use alloc::vec;
2954
2955 use ip_test_macro::ip_test;
2956 use net_declare::{net_ip_v4, net_ip_v6};
2957 use net_types::ip::{Ipv4Addr, Ipv6Addr};
2958 use netstack3_base::testutil::{
2959 FakeBindingsCtx, FakeCoreCtx, FakeInstant, FakeLinkAddress, FakeLinkDevice,
2960 FakeLinkDeviceId, FakeTimerCtxExt as _, FakeTxMetadata, FakeWeakDeviceId,
2961 };
2962 use netstack3_base::{
2963 CtxPair, InstantContext, IntoCoreTimerCtx, SendFrameContext as _, SendFrameErrorReason,
2964 };
2965 use netstack3_hashmap::HashSet;
2966 use test_case::test_case;
2967
2968 use super::*;
2969 use crate::internal::device::nud::api::NeighborApi;
2970 use packet::NestableSerializer as _;
2971
2972 struct FakeNudContext<I: Ip, D: LinkDevice> {
2973 state: NudState<I, D, FakeBindingsCtxImpl<I>>,
2974 counters: NudCounters<I>,
2975 }
2976
2977 struct FakeConfigContext {
2978 retrans_timer: NonZeroDuration,
2979 nud_config: NudUserConfig,
2980 }
2981
2982 struct FakeCoreCtxImpl<I: Ip> {
2983 nud: FakeNudContext<I, FakeLinkDevice>,
2984 inner: FakeInnerCtxImpl<I>,
2985 }
2986
2987 type FakeInnerCtxImpl<I> =
2988 FakeCoreCtx<FakeConfigContext, FakeNudMessageMeta<I>, FakeLinkDeviceId>;
2989
2990 #[derive(Debug, PartialEq, Eq)]
2991 enum FakeNudMessageMeta<I: Ip> {
2992 NeighborSolicitation {
2993 lookup_addr: SpecifiedAddr<I::Addr>,
2994 remote_link_addr: Option<FakeLinkAddress>,
2995 },
2996 IpFrame {
2997 dst_link_address: FakeLinkAddress,
2998 },
2999 IcmpDestUnreachable,
3000 }
3001
3002 type FakeBindingsCtxImpl<I> = FakeBindingsCtx<
3003 NudTimerId<I, FakeLinkDevice, FakeWeakDeviceId<FakeLinkDeviceId>>,
3004 Event<FakeLinkAddress, FakeLinkDeviceId, I, FakeInstant>,
3005 (),
3006 (),
3007 >;
3008
3009 impl<I: Ip> FakeCoreCtxImpl<I> {
3010 fn new(bindings_ctx: &mut FakeBindingsCtxImpl<I>) -> Self {
3011 Self {
3012 nud: {
3013 FakeNudContext {
3014 state: NudState::new::<_, IntoCoreTimerCtx>(
3015 bindings_ctx,
3016 FakeWeakDeviceId(FakeLinkDeviceId),
3017 ),
3018 counters: Default::default(),
3019 }
3020 },
3021 inner: FakeInnerCtxImpl::with_state(FakeConfigContext {
3022 retrans_timer: ONE_SECOND,
3023 nud_config: NudUserConfig {
3027 max_unicast_solicitations: NonZeroU16::new(4).unwrap(),
3028 max_multicast_solicitations: NonZeroU16::new(5).unwrap(),
3029 base_reachable_time: NonZeroDuration::from_secs(23).unwrap(),
3030 retrans_timer: NonZeroDuration::from_secs(3).unwrap(),
3031 },
3032 }),
3033 }
3034 }
3035 }
3036
3037 fn new_context<I: Ip>() -> CtxPair<FakeCoreCtxImpl<I>, FakeBindingsCtxImpl<I>> {
3038 CtxPair::with_default_bindings_ctx(|bindings_ctx| FakeCoreCtxImpl::<I>::new(bindings_ctx))
3039 }
3040
3041 impl<I: Ip> DeviceIdContext<FakeLinkDevice> for FakeCoreCtxImpl<I> {
3042 type DeviceId = FakeLinkDeviceId;
3043 type WeakDeviceId = FakeWeakDeviceId<FakeLinkDeviceId>;
3044 }
3045
3046 impl<I: Ip> NudContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeCoreCtxImpl<I> {
3047 type ConfigCtx<'a> = FakeConfigContext;
3048
3049 type SenderCtx<'a> = FakeInnerCtxImpl<I>;
3050
3051 fn with_nud_state_mut_and_sender_ctx<
3052 O,
3053 F: FnOnce(
3054 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3055 &mut Self::SenderCtx<'_>,
3056 ) -> O,
3057 >(
3058 &mut self,
3059 _device_id: &Self::DeviceId,
3060 cb: F,
3061 ) -> O {
3062 cb(&mut self.nud.state, &mut self.inner)
3063 }
3064
3065 fn with_nud_state_mut<
3066 O,
3067 F: FnOnce(
3068 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3069 &mut Self::ConfigCtx<'_>,
3070 ) -> O,
3071 >(
3072 &mut self,
3073 &FakeLinkDeviceId: &FakeLinkDeviceId,
3074 cb: F,
3075 ) -> O {
3076 cb(&mut self.nud.state, &mut self.inner.state)
3077 }
3078
3079 fn with_nud_state<
3080 O,
3081 F: FnOnce(&NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>) -> O,
3082 >(
3083 &mut self,
3084 &FakeLinkDeviceId: &FakeLinkDeviceId,
3085 cb: F,
3086 ) -> O {
3087 cb(&self.nud.state)
3088 }
3089
3090 fn send_neighbor_solicitation(
3091 &mut self,
3092 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3093 &FakeLinkDeviceId: &FakeLinkDeviceId,
3094 lookup_addr: SpecifiedAddr<I::Addr>,
3095 remote_link_addr: Option<FakeLinkAddress>,
3096 ) {
3097 self.inner
3098 .send_frame(
3099 bindings_ctx,
3100 FakeNudMessageMeta::NeighborSolicitation { lookup_addr, remote_link_addr },
3101 Buf::new(Vec::new(), ..),
3102 )
3103 .unwrap()
3104 }
3105 }
3106
3107 impl<I: NudIcmpIpExt> NudIcmpContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>
3108 for FakeCoreCtxImpl<I>
3109 {
3110 fn send_icmp_dest_unreachable(
3111 &mut self,
3112 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3113 frame: Buf<Vec<u8>>,
3114 _device_id: Option<&Self::DeviceId>,
3115 _original_src_ip: SocketIpAddr<I::Addr>,
3116 _original_dst_ip: SocketIpAddr<I::Addr>,
3117 _header_len: usize,
3118 _proto: I::Proto,
3119 _metadata: I::Metadata,
3120 ) {
3121 self.inner
3122 .send_frame(bindings_ctx, FakeNudMessageMeta::IcmpDestUnreachable, frame)
3123 .unwrap()
3124 }
3125 }
3126
3127 impl<I: Ip> CounterContext<NudCounters<I>> for FakeCoreCtxImpl<I> {
3128 fn counters(&self) -> &NudCounters<I> {
3129 &self.nud.counters
3130 }
3131 }
3132
3133 impl<I: Ip> NudConfigContext<I> for FakeConfigContext {
3134 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3135 self.retrans_timer
3136 }
3137
3138 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3139 cb(&self.nud_config)
3140 }
3141
3142 fn override_lock_time(&mut self) -> Duration {
3143 Duration::ZERO
3144 }
3145 }
3146
3147 impl<I: Ip> NudSenderContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeInnerCtxImpl<I> {
3148 fn send_ip_packet_to_neighbor_link_addr<S>(
3149 &mut self,
3150 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3151 dst_link_address: FakeLinkAddress,
3152 body: S,
3153 _tx_meta: FakeTxMetadata,
3154 ) -> Result<(), SendFrameError<S>>
3155 where
3156 S: NetworkSerializer,
3157 S::Buffer: BufferMut,
3158 {
3159 self.send_frame(bindings_ctx, FakeNudMessageMeta::IpFrame { dst_link_address }, body)
3160 }
3161 }
3162
3163 impl<I: Ip> NudConfigContext<I> for FakeInnerCtxImpl<I> {
3164 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3165 <FakeConfigContext as NudConfigContext<I>>::retransmit_timeout(&mut self.state)
3166 }
3167
3168 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3169 <FakeConfigContext as NudConfigContext<I>>::with_nud_user_config(&mut self.state, cb)
3170 }
3171
3172 fn override_lock_time(&mut self) -> Duration {
3173 <FakeConfigContext as NudConfigContext<I>>::override_lock_time(&mut self.state)
3174 }
3175 }
3176
3177 const ONE_SECOND: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
3178
3179 #[track_caller]
3180 fn check_lookup_has<I: Ip>(
3181 core_ctx: &mut FakeCoreCtxImpl<I>,
3182 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3183 lookup_addr: SpecifiedAddr<I::Addr>,
3184 expected_link_addr: FakeLinkAddress,
3185 ) {
3186 let entry = assert_matches!(
3187 core_ctx.nud.state.neighbors.get(&lookup_addr),
3188 Some(entry @ (
3189 NeighborState::Dynamic(
3190 DynamicNeighborState::Reachable (Reachable { link_address, last_confirmed_at: _ })
3191 | DynamicNeighborState::Stale (Stale { link_address })
3192 | DynamicNeighborState::Delay (Delay { link_address })
3193 | DynamicNeighborState::Probe (Probe { link_address, transmit_counter: _ })
3194 | DynamicNeighborState::Unreachable (Unreachable { link_address, mode: _ })
3195 )
3196 | NeighborState::Static(link_address)
3197 )) => {
3198 assert_eq!(link_address, &expected_link_addr);
3199 entry
3200 }
3201 );
3202 match entry {
3203 NeighborState::Dynamic(DynamicNeighborState::Incomplete { .. }) => {
3204 unreachable!("entry must be static, REACHABLE, or STALE")
3205 }
3206 NeighborState::Dynamic(DynamicNeighborState::Reachable { .. }) => {
3207 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3208 bindings_ctx,
3209 [(
3210 lookup_addr,
3211 NudEvent::ReachableTime,
3212 core_ctx.inner.base_reachable_time().get(),
3213 )],
3214 );
3215 }
3216 NeighborState::Dynamic(DynamicNeighborState::Delay { .. }) => {
3217 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3218 bindings_ctx,
3219 [(lookup_addr, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
3220 );
3221 }
3222 NeighborState::Dynamic(DynamicNeighborState::Probe { .. }) => {
3223 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3224 bindings_ctx,
3225 [(
3226 lookup_addr,
3227 NudEvent::RetransmitUnicastProbe,
3228 core_ctx.inner.state.retrans_timer.get(),
3229 )],
3230 );
3231 }
3232 NeighborState::Dynamic(DynamicNeighborState::Unreachable(Unreachable {
3233 link_address: _,
3234 mode,
3235 })) => {
3236 let instant = match mode {
3237 UnreachableMode::WaitingForPacketSend => None,
3238 mode @ UnreachableMode::Backoff { .. } => {
3239 let duration =
3240 mode.next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state);
3241 Some(bindings_ctx.now() + duration.get())
3242 }
3243 };
3244 if let Some(instant) = instant {
3245 core_ctx.nud.state.timer_heap.neighbor.assert_timers([(
3246 lookup_addr,
3247 NudEvent::RetransmitUnicastProbe,
3248 instant,
3249 )]);
3250 }
3251 }
3252 NeighborState::Dynamic(DynamicNeighborState::Stale { .. })
3253 | NeighborState::Static(_) => bindings_ctx.timers.assert_no_timers_installed(),
3254 }
3255 }
3256
3257 trait TestIpExt: NudIcmpIpExt {
3258 const LOOKUP_ADDR1: SpecifiedAddr<Self::Addr>;
3259 const LOOKUP_ADDR2: SpecifiedAddr<Self::Addr>;
3260 const LOOKUP_ADDR3: SpecifiedAddr<Self::Addr>;
3261 }
3262
3263 impl TestIpExt for Ipv4 {
3264 const LOOKUP_ADDR1: SpecifiedAddr<Ipv4Addr> =
3266 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.1")) };
3267 const LOOKUP_ADDR2: SpecifiedAddr<Ipv4Addr> =
3268 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.2")) };
3269 const LOOKUP_ADDR3: SpecifiedAddr<Ipv4Addr> =
3270 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.3")) };
3271 }
3272
3273 impl TestIpExt for Ipv6 {
3274 const LOOKUP_ADDR1: SpecifiedAddr<Ipv6Addr> =
3276 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::1")) };
3277 const LOOKUP_ADDR2: SpecifiedAddr<Ipv6Addr> =
3278 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::2")) };
3279 const LOOKUP_ADDR3: SpecifiedAddr<Ipv6Addr> =
3280 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::3")) };
3281 }
3282
3283 const LINK_ADDR1: FakeLinkAddress = FakeLinkAddress([1]);
3284 const LINK_ADDR2: FakeLinkAddress = FakeLinkAddress([2]);
3285 const LINK_ADDR3: FakeLinkAddress = FakeLinkAddress([3]);
3286
3287 impl<I: Ip, L: LinkDevice> NudTimerId<I, L, FakeWeakDeviceId<FakeLinkDeviceId>> {
3288 fn neighbor() -> Self {
3289 Self {
3290 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3291 timer_type: NudTimerType::Neighbor,
3292 _marker: PhantomData,
3293 }
3294 }
3295
3296 fn garbage_collection() -> Self {
3297 Self {
3298 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3299 timer_type: NudTimerType::GarbageCollection,
3300 _marker: PhantomData,
3301 }
3302 }
3303 }
3304
3305 fn queue_ip_packet_to_unresolved_neighbor<I: TestIpExt>(
3306 core_ctx: &mut FakeCoreCtxImpl<I>,
3307 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3308 neighbor: SpecifiedAddr<I::Addr>,
3309 pending_frames: &mut VecDeque<Buf<Vec<u8>>>,
3310 body: u8,
3311 expect_event: bool,
3312 ) {
3313 let body = [body];
3314 assert_eq!(
3315 NudHandler::send_ip_packet_to_neighbor(
3316 core_ctx,
3317 bindings_ctx,
3318 &FakeLinkDeviceId,
3319 neighbor,
3320 Buf::new(body, ..),
3321 FakeTxMetadata::default(),
3322 ),
3323 Ok(())
3324 );
3325
3326 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
3327
3328 pending_frames.push_back(Buf::new(body.to_vec(), ..));
3329
3330 assert_neighbor_state_with_ip(
3331 core_ctx,
3332 bindings_ctx,
3333 neighbor,
3334 DynamicNeighborState::Incomplete(Incomplete {
3335 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
3336 pending_frames: pending_frames
3337 .iter()
3338 .cloned()
3339 .map(|buf| (buf, FakeTxMetadata::default()))
3340 .collect(),
3341 notifiers: Vec::new(),
3342 _marker: PhantomData,
3343 }),
3344 expect_event.then_some(ExpectedEvent::Added),
3345 );
3346
3347 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3348 bindings_ctx,
3349 [(neighbor, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
3350 );
3351 }
3352
3353 fn init_incomplete_neighbor_with_ip<I: TestIpExt>(
3354 core_ctx: &mut FakeCoreCtxImpl<I>,
3355 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3356 ip_address: SpecifiedAddr<I::Addr>,
3357 take_probe: bool,
3358 ) -> VecDeque<Buf<Vec<u8>>> {
3359 let mut pending_frames = VecDeque::new();
3360 queue_ip_packet_to_unresolved_neighbor(
3361 core_ctx,
3362 bindings_ctx,
3363 ip_address,
3364 &mut pending_frames,
3365 1,
3366 true, );
3368 if take_probe {
3369 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, None);
3370 }
3371 pending_frames
3372 }
3373
3374 fn init_incomplete_neighbor<I: TestIpExt>(
3375 core_ctx: &mut FakeCoreCtxImpl<I>,
3376 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3377 take_probe: bool,
3378 ) -> VecDeque<Buf<Vec<u8>>> {
3379 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, take_probe)
3380 }
3381
3382 fn init_stale_neighbor_with_ip<I: TestIpExt>(
3383 core_ctx: &mut FakeCoreCtxImpl<I>,
3384 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3385 ip_address: SpecifiedAddr<I::Addr>,
3386 link_address: FakeLinkAddress,
3387 ) {
3388 NudHandler::handle_neighbor_update(
3389 core_ctx,
3390 bindings_ctx,
3391 &FakeLinkDeviceId,
3392 ip_address,
3393 DynamicNeighborUpdateSource::Probe { link_address },
3394 );
3395 assert_neighbor_state_with_ip(
3396 core_ctx,
3397 bindings_ctx,
3398 ip_address,
3399 DynamicNeighborState::Stale(Stale { link_address }),
3400 Some(ExpectedEvent::Added),
3401 );
3402 }
3403
3404 fn init_stale_neighbor<I: TestIpExt>(
3405 core_ctx: &mut FakeCoreCtxImpl<I>,
3406 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3407 link_address: FakeLinkAddress,
3408 ) {
3409 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3410 }
3411
3412 fn init_reachable_neighbor_with_ip<I: TestIpExt>(
3413 core_ctx: &mut FakeCoreCtxImpl<I>,
3414 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3415 ip_address: SpecifiedAddr<I::Addr>,
3416 link_address: FakeLinkAddress,
3417 ) {
3418 let queued_frame =
3419 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, true);
3420 NudHandler::handle_neighbor_update(
3421 core_ctx,
3422 bindings_ctx,
3423 &FakeLinkDeviceId,
3424 ip_address,
3425 DynamicNeighborUpdateSource::Confirmation {
3426 link_address: Some(link_address),
3427 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
3428 },
3429 );
3430 assert_neighbor_state_with_ip(
3431 core_ctx,
3432 bindings_ctx,
3433 ip_address,
3434 DynamicNeighborState::Reachable(Reachable {
3435 link_address,
3436 last_confirmed_at: bindings_ctx.now(),
3437 }),
3438 Some(ExpectedEvent::Changed),
3439 );
3440 assert_pending_frame_sent(core_ctx, queued_frame, link_address);
3441 }
3442
3443 fn init_reachable_neighbor<I: TestIpExt>(
3444 core_ctx: &mut FakeCoreCtxImpl<I>,
3445 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3446 link_address: FakeLinkAddress,
3447 ) {
3448 init_reachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3449 }
3450
3451 fn init_delay_neighbor_with_ip<I: TestIpExt>(
3452 core_ctx: &mut FakeCoreCtxImpl<I>,
3453 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3454 ip_address: SpecifiedAddr<I::Addr>,
3455 link_address: FakeLinkAddress,
3456 ) {
3457 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3458 assert_eq!(
3459 NudHandler::send_ip_packet_to_neighbor(
3460 core_ctx,
3461 bindings_ctx,
3462 &FakeLinkDeviceId,
3463 ip_address,
3464 Buf::new([1], ..),
3465 FakeTxMetadata::default(),
3466 ),
3467 Ok(())
3468 );
3469 assert_neighbor_state_with_ip(
3470 core_ctx,
3471 bindings_ctx,
3472 ip_address,
3473 DynamicNeighborState::Delay(Delay { link_address }),
3474 Some(ExpectedEvent::Changed),
3475 );
3476 assert_eq!(
3477 core_ctx.inner.take_frames(),
3478 vec![(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![1])],
3479 );
3480 }
3481
3482 fn init_delay_neighbor<I: TestIpExt>(
3483 core_ctx: &mut FakeCoreCtxImpl<I>,
3484 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3485 link_address: FakeLinkAddress,
3486 ) {
3487 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3488 }
3489
3490 fn init_probe_neighbor_with_ip<I: TestIpExt>(
3491 core_ctx: &mut FakeCoreCtxImpl<I>,
3492 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3493 ip_address: SpecifiedAddr<I::Addr>,
3494 link_address: FakeLinkAddress,
3495 take_probe: bool,
3496 ) {
3497 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3498 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3499 core_ctx.nud.state.timer_heap.neighbor.assert_top(&ip_address, &NudEvent::DelayFirstProbe);
3500 assert_eq!(
3501 bindings_ctx.trigger_timers_for(DELAY_FIRST_PROBE_TIME.into(), core_ctx),
3502 [NudTimerId::neighbor()]
3503 );
3504 assert_neighbor_state_with_ip(
3505 core_ctx,
3506 bindings_ctx,
3507 ip_address,
3508 DynamicNeighborState::Probe(Probe {
3509 link_address,
3510 transmit_counter: NonZeroU16::new(max_unicast_solicit - 1),
3511 }),
3512 Some(ExpectedEvent::Changed),
3513 );
3514 if take_probe {
3515 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3516 }
3517 }
3518
3519 fn init_probe_neighbor<I: TestIpExt>(
3520 core_ctx: &mut FakeCoreCtxImpl<I>,
3521 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3522 link_address: FakeLinkAddress,
3523 take_probe: bool,
3524 ) {
3525 init_probe_neighbor_with_ip(
3526 core_ctx,
3527 bindings_ctx,
3528 I::LOOKUP_ADDR1,
3529 link_address,
3530 take_probe,
3531 );
3532 }
3533
3534 fn init_unreachable_neighbor_with_ip<I: TestIpExt>(
3535 core_ctx: &mut FakeCoreCtxImpl<I>,
3536 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3537 ip_address: SpecifiedAddr<I::Addr>,
3538 link_address: FakeLinkAddress,
3539 ) {
3540 init_probe_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address, false);
3541 let retransmit_timeout = core_ctx.inner.retransmit_timeout();
3542 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3543 for _ in 0..max_unicast_solicit {
3544 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3545 assert_eq!(
3546 bindings_ctx.trigger_timers_for(retransmit_timeout.into(), core_ctx),
3547 [NudTimerId::neighbor()]
3548 );
3549 }
3550 assert_neighbor_state_with_ip(
3551 core_ctx,
3552 bindings_ctx,
3553 ip_address,
3554 DynamicNeighborState::Unreachable(Unreachable {
3555 link_address,
3556 mode: UnreachableMode::WaitingForPacketSend,
3557 }),
3558 Some(ExpectedEvent::Changed),
3559 );
3560 }
3561
3562 fn init_unreachable_neighbor<I: TestIpExt>(
3563 core_ctx: &mut FakeCoreCtxImpl<I>,
3564 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3565 link_address: FakeLinkAddress,
3566 ) {
3567 init_unreachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3568 }
3569
3570 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
3571 enum InitialState {
3572 Incomplete,
3573 Stale,
3574 Reachable,
3575 Delay,
3576 Probe,
3577 Unreachable,
3578 }
3579
3580 fn init_neighbor_in_state<I: TestIpExt>(
3581 core_ctx: &mut FakeCoreCtxImpl<I>,
3582 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3583 state: InitialState,
3584 ) -> DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>> {
3585 match state {
3586 InitialState::Incomplete => {
3587 let _: VecDeque<Buf<Vec<u8>>> =
3588 init_incomplete_neighbor(core_ctx, bindings_ctx, true);
3589 }
3590 InitialState::Reachable => {
3591 init_reachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3592 }
3593 InitialState::Stale => {
3594 init_stale_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3595 }
3596 InitialState::Delay => {
3597 init_delay_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3598 }
3599 InitialState::Probe => {
3600 init_probe_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, true);
3601 }
3602 InitialState::Unreachable => {
3603 init_unreachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3604 }
3605 }
3606 assert_matches!(core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
3607 Some(NeighborState::Dynamic(state)) => state.clone()
3608 )
3609 }
3610
3611 #[track_caller]
3612 fn init_static_neighbor_with_ip<I: TestIpExt>(
3613 core_ctx: &mut FakeCoreCtxImpl<I>,
3614 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3615 ip_address: SpecifiedAddr<I::Addr>,
3616 link_address: FakeLinkAddress,
3617 expected_event: ExpectedEvent,
3618 ) {
3619 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3620 NeighborApi::new(&mut ctx)
3621 .insert_static_entry(&FakeLinkDeviceId, *ip_address, link_address)
3622 .unwrap();
3623 assert_eq!(
3624 ctx.bindings_ctx.take_events(),
3625 [Event {
3626 device: FakeLinkDeviceId,
3627 addr: ip_address,
3628 kind: match expected_event {
3629 ExpectedEvent::Added => EventKind::Added(EventState::Static(link_address)),
3630 ExpectedEvent::Changed => EventKind::Changed(EventState::Static(link_address)),
3631 },
3632 at: ctx.bindings_ctx.now(),
3633 }],
3634 );
3635 }
3636
3637 #[track_caller]
3638 fn init_static_neighbor<I: TestIpExt>(
3639 core_ctx: &mut FakeCoreCtxImpl<I>,
3640 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3641 link_address: FakeLinkAddress,
3642 expected_event: ExpectedEvent,
3643 ) {
3644 init_static_neighbor_with_ip(
3645 core_ctx,
3646 bindings_ctx,
3647 I::LOOKUP_ADDR1,
3648 link_address,
3649 expected_event,
3650 );
3651 }
3652
3653 #[track_caller]
3654 fn delete_neighbor<I: TestIpExt>(
3655 core_ctx: &mut FakeCoreCtxImpl<I>,
3656 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3657 ) {
3658 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3659 NeighborApi::new(&mut ctx)
3660 .remove_entry(&FakeLinkDeviceId, *I::LOOKUP_ADDR1)
3661 .expect("neighbor entry should exist");
3662 assert_eq!(
3663 ctx.bindings_ctx.take_events(),
3664 [Event::removed(&FakeLinkDeviceId, I::LOOKUP_ADDR1, ctx.bindings_ctx.now())],
3665 );
3666 }
3667
3668 #[track_caller]
3669 fn assert_neighbor_state<I: TestIpExt>(
3670 core_ctx: &FakeCoreCtxImpl<I>,
3671 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3672 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3673 event_kind: Option<ExpectedEvent>,
3674 ) {
3675 assert_neighbor_state_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, state, event_kind);
3676 }
3677
3678 #[derive(Clone, Copy, Debug)]
3679 enum ExpectedEvent {
3680 Added,
3681 Changed,
3682 }
3683
3684 #[track_caller]
3685 fn assert_neighbor_state_with_ip<I: TestIpExt>(
3686 core_ctx: &FakeCoreCtxImpl<I>,
3687 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3688 neighbor: SpecifiedAddr<I::Addr>,
3689 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3690 expected_event: Option<ExpectedEvent>,
3691 ) {
3692 if let Some(expected_event) = expected_event {
3693 let event_state = EventState::Dynamic(state.to_event_dynamic_state());
3694 assert_eq!(
3695 bindings_ctx.take_events(),
3696 [Event {
3697 device: FakeLinkDeviceId,
3698 addr: neighbor,
3699 kind: match expected_event {
3700 ExpectedEvent::Added => EventKind::Added(event_state),
3701 ExpectedEvent::Changed => EventKind::Changed(event_state),
3702 },
3703 at: bindings_ctx.now(),
3704 }],
3705 );
3706 }
3707
3708 assert_eq!(
3709 core_ctx.nud.state.neighbors.get(&neighbor),
3710 Some(&NeighborState::Dynamic(state))
3711 );
3712 }
3713
3714 #[track_caller]
3715 fn assert_pending_frame_sent<I: TestIpExt>(
3716 core_ctx: &mut FakeCoreCtxImpl<I>,
3717 pending_frames: VecDeque<Buf<Vec<u8>>>,
3718 link_address: FakeLinkAddress,
3719 ) {
3720 assert_eq!(
3721 core_ctx.inner.take_frames(),
3722 pending_frames
3723 .into_iter()
3724 .map(|f| (
3725 FakeNudMessageMeta::IpFrame { dst_link_address: link_address },
3726 f.as_ref().to_vec(),
3727 ))
3728 .collect::<Vec<_>>()
3729 );
3730 }
3731
3732 #[track_caller]
3733 fn assert_neighbor_probe_sent_for_ip<I: TestIpExt>(
3734 core_ctx: &mut FakeCoreCtxImpl<I>,
3735 ip_address: SpecifiedAddr<I::Addr>,
3736 link_address: Option<FakeLinkAddress>,
3737 ) {
3738 assert_eq!(
3739 core_ctx.inner.take_frames(),
3740 [(
3741 FakeNudMessageMeta::NeighborSolicitation {
3742 lookup_addr: ip_address,
3743 remote_link_addr: link_address,
3744 },
3745 Vec::new()
3746 )]
3747 );
3748 }
3749
3750 #[track_caller]
3751 fn assert_neighbor_probe_sent<I: TestIpExt>(
3752 core_ctx: &mut FakeCoreCtxImpl<I>,
3753 link_address: Option<FakeLinkAddress>,
3754 ) {
3755 assert_neighbor_probe_sent_for_ip(core_ctx, I::LOOKUP_ADDR1, link_address);
3756 }
3757
3758 #[track_caller]
3759 fn assert_neighbor_removed_with_ip<I: TestIpExt>(
3760 core_ctx: &mut FakeCoreCtxImpl<I>,
3761 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3762 neighbor: SpecifiedAddr<I::Addr>,
3763 ) {
3764 super::testutil::assert_neighbor_unknown(core_ctx, FakeLinkDeviceId, neighbor);
3765 assert_eq!(
3766 bindings_ctx.take_events(),
3767 [Event::removed(&FakeLinkDeviceId, neighbor, bindings_ctx.now())],
3768 );
3769 }
3770
3771 #[ip_test(I)]
3772 fn serialization_failure_doesnt_schedule_timer<I: TestIpExt>() {
3773 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3774
3775 let packet = Buf::new([0; 2], ..).with_size_limit(1);
3778
3779 let err = assert_matches!(
3780 NudHandler::send_ip_packet_to_neighbor(
3781 &mut core_ctx,
3782 &mut bindings_ctx,
3783 &FakeLinkDeviceId,
3784 I::LOOKUP_ADDR1,
3785 packet,
3786 FakeTxMetadata::default(),
3787 ),
3788 Err(ErrorAndSerializer { error, serializer: _ }) => error
3789 );
3790 assert_eq!(err, SendFrameErrorReason::SizeConstraintsViolation);
3791
3792 super::testutil::assert_neighbor_unknown(&mut core_ctx, FakeLinkDeviceId, I::LOOKUP_ADDR1);
3795 assert_eq!(core_ctx.inner.take_frames(), []);
3796 bindings_ctx.timers.assert_no_timers_installed();
3797 }
3798
3799 #[ip_test(I)]
3800 fn incomplete_to_stale_on_probe<I: TestIpExt>() {
3801 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3802
3803 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3805
3806 NudHandler::handle_neighbor_update(
3808 &mut core_ctx,
3809 &mut bindings_ctx,
3810 &FakeLinkDeviceId,
3811 I::LOOKUP_ADDR1,
3812 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
3813 );
3814
3815 assert_neighbor_state(
3817 &core_ctx,
3818 &mut bindings_ctx,
3819 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3820 Some(ExpectedEvent::Changed),
3821 );
3822 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3823 }
3824
3825 #[ip_test(I)]
3826 #[test_case(true, true; "solicited override")]
3827 #[test_case(true, false; "solicited non-override")]
3828 #[test_case(false, true; "unsolicited override")]
3829 #[test_case(false, false; "unsolicited non-override")]
3830 fn incomplete_on_confirmation<I: TestIpExt>(solicited_flag: bool, override_flag: bool) {
3831 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3832
3833 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3835
3836 NudHandler::handle_neighbor_update(
3838 &mut core_ctx,
3839 &mut bindings_ctx,
3840 &FakeLinkDeviceId,
3841 I::LOOKUP_ADDR1,
3842 DynamicNeighborUpdateSource::Confirmation {
3843 link_address: Some(LINK_ADDR1),
3844 flags: ConfirmationFlags { solicited_flag, override_flag },
3845 },
3846 );
3847
3848 let expected_state = if solicited_flag {
3849 DynamicNeighborState::Reachable(Reachable {
3850 link_address: LINK_ADDR1,
3851 last_confirmed_at: bindings_ctx.now(),
3852 })
3853 } else {
3854 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 })
3855 };
3856 assert_neighbor_state(
3857 &core_ctx,
3858 &mut bindings_ctx,
3859 expected_state,
3860 Some(ExpectedEvent::Changed),
3861 );
3862 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3863 }
3864
3865 #[ip_test(I)]
3866 fn reachable_to_stale_on_timeout<I: TestIpExt>() {
3867 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3868
3869 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
3871
3872 assert_eq!(
3874 bindings_ctx
3875 .trigger_timers_for(core_ctx.inner.base_reachable_time().into(), &mut core_ctx,),
3876 [NudTimerId::neighbor()]
3877 );
3878 assert_neighbor_state(
3879 &core_ctx,
3880 &mut bindings_ctx,
3881 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3882 Some(ExpectedEvent::Changed),
3883 );
3884 }
3885
3886 #[ip_test(I)]
3887 #[test_case(InitialState::Reachable, true; "reachable with different address")]
3888 #[test_case(InitialState::Reachable, false; "reachable with same address")]
3889 #[test_case(InitialState::Stale, true; "stale with different address")]
3890 #[test_case(InitialState::Stale, false; "stale with same address")]
3891 #[test_case(InitialState::Delay, true; "delay with different address")]
3892 #[test_case(InitialState::Delay, false; "delay with same address")]
3893 #[test_case(InitialState::Probe, true; "probe with different address")]
3894 #[test_case(InitialState::Probe, false; "probe with same address")]
3895 #[test_case(InitialState::Unreachable, true; "unreachable with different address")]
3896 #[test_case(InitialState::Unreachable, false; "unreachable with same address")]
3897 fn transition_to_stale_on_probe_with_different_address<I: TestIpExt>(
3898 initial_state: InitialState,
3899 update_link_address: bool,
3900 ) {
3901 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3902
3903 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3905
3906 NudHandler::handle_neighbor_update(
3908 &mut core_ctx,
3909 &mut bindings_ctx,
3910 &FakeLinkDeviceId,
3911 I::LOOKUP_ADDR1,
3912 DynamicNeighborUpdateSource::Probe {
3913 link_address: if update_link_address { LINK_ADDR2 } else { LINK_ADDR1 },
3914 },
3915 );
3916
3917 let expected_state = if update_link_address {
3923 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 })
3924 } else {
3925 initial_state
3926 };
3927 assert_neighbor_state(
3928 &core_ctx,
3929 &mut bindings_ctx,
3930 expected_state,
3931 update_link_address.then_some(ExpectedEvent::Changed),
3932 );
3933 }
3934
3935 #[ip_test(I)]
3936 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
3937 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
3938 #[test_case(InitialState::Stale, true; "stale with override flag set")]
3939 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
3940 #[test_case(InitialState::Delay, true; "delay with override flag set")]
3941 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
3942 #[test_case(InitialState::Probe, true; "probe with override flag set")]
3943 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
3944 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
3945 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
3946 fn transition_to_reachable_on_solicited_confirmation_same_address<I: TestIpExt>(
3947 initial_state: InitialState,
3948 override_flag: bool,
3949 ) {
3950 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3951
3952 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3954
3955 NudHandler::handle_neighbor_update(
3957 &mut core_ctx,
3958 &mut bindings_ctx,
3959 &FakeLinkDeviceId,
3960 I::LOOKUP_ADDR1,
3961 DynamicNeighborUpdateSource::Confirmation {
3962 link_address: Some(LINK_ADDR1),
3963 flags: ConfirmationFlags { solicited_flag: true, override_flag },
3964 },
3965 );
3966
3967 let now = bindings_ctx.now();
3969 assert_neighbor_state(
3970 &core_ctx,
3971 &mut bindings_ctx,
3972 DynamicNeighborState::Reachable(Reachable {
3973 link_address: LINK_ADDR1,
3974 last_confirmed_at: now,
3975 }),
3976 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
3977 );
3978 }
3979
3980 #[ip_test(I)]
3981 #[test_case(InitialState::Reachable; "reachable")]
3982 #[test_case(InitialState::Stale; "stale")]
3983 #[test_case(InitialState::Delay; "delay")]
3984 #[test_case(InitialState::Probe; "probe")]
3985 #[test_case(InitialState::Unreachable; "unreachable")]
3986 fn transition_to_stale_on_unsolicited_override_confirmation_with_different_address<
3987 I: TestIpExt,
3988 >(
3989 initial_state: InitialState,
3990 ) {
3991 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3992
3993 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3995
3996 NudHandler::handle_neighbor_update(
3998 &mut core_ctx,
3999 &mut bindings_ctx,
4000 &FakeLinkDeviceId,
4001 I::LOOKUP_ADDR1,
4002 DynamicNeighborUpdateSource::Confirmation {
4003 link_address: Some(LINK_ADDR2),
4004 flags: ConfirmationFlags { solicited_flag: false, override_flag: true },
4005 },
4006 );
4007
4008 assert_neighbor_state(
4010 &core_ctx,
4011 &mut bindings_ctx,
4012 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4013 Some(ExpectedEvent::Changed),
4014 );
4015 }
4016
4017 #[ip_test(I)]
4018 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
4019 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
4020 #[test_case(InitialState::Stale, true; "stale with override flag set")]
4021 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
4022 #[test_case(InitialState::Delay, true; "delay with override flag set")]
4023 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
4024 #[test_case(InitialState::Probe, true; "probe with override flag set")]
4025 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
4026 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
4027 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
4028 fn noop_on_unsolicited_confirmation_with_same_address<I: TestIpExt>(
4029 initial_state: InitialState,
4030 override_flag: bool,
4031 ) {
4032 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4033
4034 let expected_state =
4036 init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4037
4038 NudHandler::handle_neighbor_update(
4040 &mut core_ctx,
4041 &mut bindings_ctx,
4042 &FakeLinkDeviceId,
4043 I::LOOKUP_ADDR1,
4044 DynamicNeighborUpdateSource::Confirmation {
4045 link_address: Some(LINK_ADDR1),
4046 flags: ConfirmationFlags { solicited_flag: false, override_flag },
4047 },
4048 );
4049
4050 assert_neighbor_state(&core_ctx, &mut bindings_ctx, expected_state, None);
4052 }
4053
4054 #[ip_test(I)]
4055 #[test_case(InitialState::Reachable; "reachable")]
4056 #[test_case(InitialState::Stale; "stale")]
4057 #[test_case(InitialState::Delay; "delay")]
4058 #[test_case(InitialState::Probe; "probe")]
4059 #[test_case(InitialState::Unreachable; "unreachable")]
4060 fn transition_to_reachable_on_solicited_override_confirmation_with_different_address<
4061 I: TestIpExt,
4062 >(
4063 initial_state: InitialState,
4064 ) {
4065 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4066
4067 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4069
4070 NudHandler::handle_neighbor_update(
4072 &mut core_ctx,
4073 &mut bindings_ctx,
4074 &FakeLinkDeviceId,
4075 I::LOOKUP_ADDR1,
4076 DynamicNeighborUpdateSource::Confirmation {
4077 link_address: Some(LINK_ADDR2),
4078 flags: ConfirmationFlags { solicited_flag: true, override_flag: true },
4079 },
4080 );
4081
4082 let now = bindings_ctx.now();
4084 assert_neighbor_state(
4085 &core_ctx,
4086 &mut bindings_ctx,
4087 DynamicNeighborState::Reachable(Reachable {
4088 link_address: LINK_ADDR2,
4089 last_confirmed_at: now,
4090 }),
4091 Some(ExpectedEvent::Changed),
4092 );
4093 }
4094
4095 #[ip_test(I)]
4096 fn reachable_to_reachable_on_probe_with_same_address<I: TestIpExt>() {
4097 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4098
4099 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4101
4102 NudHandler::handle_neighbor_update(
4104 &mut core_ctx,
4105 &mut bindings_ctx,
4106 &FakeLinkDeviceId,
4107 I::LOOKUP_ADDR1,
4108 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
4109 );
4110
4111 let now = bindings_ctx.now();
4113 assert_neighbor_state(
4114 &core_ctx,
4115 &mut bindings_ctx,
4116 DynamicNeighborState::Reachable(Reachable {
4117 link_address: LINK_ADDR1,
4118 last_confirmed_at: now,
4119 }),
4120 None,
4121 );
4122 }
4123
4124 #[ip_test(I)]
4125 #[test_case(true; "solicited")]
4126 #[test_case(false; "unsolicited")]
4127 fn reachable_to_stale_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4128 solicited_flag: bool,
4129 ) {
4130 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4131
4132 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4134
4135 NudHandler::handle_neighbor_update(
4137 &mut core_ctx,
4138 &mut bindings_ctx,
4139 &FakeLinkDeviceId,
4140 I::LOOKUP_ADDR1,
4141 DynamicNeighborUpdateSource::Confirmation {
4142 link_address: Some(LINK_ADDR2),
4143 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4144 },
4145 );
4146
4147 assert_neighbor_state(
4150 &core_ctx,
4151 &mut bindings_ctx,
4152 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4153 Some(ExpectedEvent::Changed),
4154 );
4155 }
4156
4157 #[ip_test(I)]
4158 #[test_case(InitialState::Stale, true; "stale solicited")]
4159 #[test_case(InitialState::Stale, false; "stale unsolicited")]
4160 #[test_case(InitialState::Delay, true; "delay solicited")]
4161 #[test_case(InitialState::Delay, false; "delay unsolicited")]
4162 #[test_case(InitialState::Probe, true; "probe solicited")]
4163 #[test_case(InitialState::Probe, false; "probe unsolicited")]
4164 #[test_case(InitialState::Unreachable, true; "unreachable solicited")]
4165 #[test_case(InitialState::Unreachable, false; "unreachable unsolicited")]
4166 fn noop_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4167 initial_state: InitialState,
4168 solicited_flag: bool,
4169 ) {
4170 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4171
4172 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4174
4175 NudHandler::handle_neighbor_update(
4177 &mut core_ctx,
4178 &mut bindings_ctx,
4179 &FakeLinkDeviceId,
4180 I::LOOKUP_ADDR1,
4181 DynamicNeighborUpdateSource::Confirmation {
4182 link_address: Some(LINK_ADDR2),
4183 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4184 },
4185 );
4186
4187 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial_state, None);
4190 }
4191
4192 #[ip_test(I)]
4193 fn stale_to_delay_on_packet_sent<I: TestIpExt>() {
4194 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4195
4196 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4198
4199 let body = 1;
4201 assert_eq!(
4202 NudHandler::send_ip_packet_to_neighbor(
4203 &mut core_ctx,
4204 &mut bindings_ctx,
4205 &FakeLinkDeviceId,
4206 I::LOOKUP_ADDR1,
4207 Buf::new([body], ..),
4208 FakeTxMetadata::default(),
4209 ),
4210 Ok(())
4211 );
4212
4213 assert_neighbor_state(
4215 &core_ctx,
4216 &mut bindings_ctx,
4217 DynamicNeighborState::Delay(Delay { link_address: LINK_ADDR1 }),
4218 Some(ExpectedEvent::Changed),
4219 );
4220 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4221 &mut bindings_ctx,
4222 [(I::LOOKUP_ADDR1, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
4223 );
4224 assert_pending_frame_sent(
4225 &mut core_ctx,
4226 VecDeque::from([Buf::new(vec![body], ..)]),
4227 LINK_ADDR1,
4228 );
4229 }
4230
4231 #[ip_test(I)]
4232 #[test_case(InitialState::Delay,
4233 NudEvent::DelayFirstProbe;
4234 "delay to probe")]
4235 #[test_case(InitialState::Probe,
4236 NudEvent::RetransmitUnicastProbe;
4237 "probe retransmit unicast probe")]
4238 fn delay_or_probe_to_probe_on_timeout<I: TestIpExt>(
4239 initial_state: InitialState,
4240 expected_initial_event: NudEvent,
4241 ) {
4242 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4243
4244 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4246
4247 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4248
4249 let (time, transmit_counter) = match initial_state {
4255 InitialState::Delay => {
4256 (DELAY_FIRST_PROBE_TIME, NonZeroU16::new(max_unicast_solicit - 1))
4257 }
4258 InitialState::Probe => {
4259 (core_ctx.inner.state.retrans_timer, NonZeroU16::new(max_unicast_solicit - 2))
4260 }
4261 other => unreachable!("test only covers DELAY and PROBE, got {:?}", other),
4262 };
4263 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4264 &mut bindings_ctx,
4265 [(I::LOOKUP_ADDR1, expected_initial_event, time.get())],
4266 );
4267 assert_eq!(
4268 bindings_ctx.trigger_timers_for(time.into(), &mut core_ctx,),
4269 [NudTimerId::neighbor()]
4270 );
4271 assert_neighbor_state(
4272 &core_ctx,
4273 &mut bindings_ctx,
4274 DynamicNeighborState::Probe(Probe { link_address: LINK_ADDR1, transmit_counter }),
4275 (initial_state != InitialState::Probe).then_some(ExpectedEvent::Changed),
4276 );
4277 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4278 &mut bindings_ctx,
4279 [(
4280 I::LOOKUP_ADDR1,
4281 NudEvent::RetransmitUnicastProbe,
4282 core_ctx.inner.state.retrans_timer.get(),
4283 )],
4284 );
4285 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4286 }
4287
4288 #[ip_test(I)]
4289 fn unreachable_probes_with_exponential_backoff_while_packets_sent<I: TestIpExt>() {
4290 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4291
4292 init_unreachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4293
4294 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4295 let timer_id = NudTimerId::neighbor();
4296
4297 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), []);
4299 assert_eq!(core_ctx.inner.take_frames(), []);
4300
4301 const BODY: u8 = 0x33;
4303 assert_eq!(
4304 NudHandler::send_ip_packet_to_neighbor(
4305 &mut core_ctx,
4306 &mut bindings_ctx,
4307 &FakeLinkDeviceId,
4308 I::LOOKUP_ADDR1,
4309 Buf::new([BODY], ..),
4310 FakeTxMetadata::default(),
4311 ),
4312 Ok(())
4313 );
4314 assert_eq!(
4315 core_ctx.inner.take_frames(),
4316 [
4317 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4318 (
4319 FakeNudMessageMeta::NeighborSolicitation {
4320 lookup_addr: I::LOOKUP_ADDR1,
4321 remote_link_addr: None,
4322 },
4323 Vec::new()
4324 )
4325 ]
4326 );
4327
4328 let next_backoff_timer = |core_ctx: &mut FakeCoreCtxImpl<I>, probes_sent| {
4329 UnreachableMode::Backoff {
4330 probes_sent: NonZeroU32::new(probes_sent).unwrap(),
4331 packet_sent: false,
4332 }
4333 .next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state)
4334 .get()
4335 };
4336
4337 const ITERATIONS: u8 = 2;
4338 for i in 1..ITERATIONS {
4339 let probes_sent = u32::from(i);
4340
4341 assert_eq!(
4344 NudHandler::send_ip_packet_to_neighbor(
4345 &mut core_ctx,
4346 &mut bindings_ctx,
4347 &FakeLinkDeviceId,
4348 I::LOOKUP_ADDR1,
4349 Buf::new([BODY + i], ..),
4350 FakeTxMetadata::default(),
4351 ),
4352 Ok(())
4353 );
4354 assert_eq!(
4355 core_ctx.inner.take_frames(),
4356 [(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY + i])]
4357 );
4358
4359 assert_eq!(
4364 bindings_ctx.trigger_timers_for(
4365 next_backoff_timer(&mut core_ctx, probes_sent),
4366 &mut core_ctx,
4367 ),
4368 [timer_id]
4369 );
4370 assert_neighbor_probe_sent(&mut core_ctx, None);
4371 bindings_ctx.timers.assert_timers_installed([(
4372 timer_id,
4373 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, probes_sent + 1),
4374 )]);
4375 }
4376
4377 let current_timer = next_backoff_timer(&mut core_ctx, u32::from(ITERATIONS));
4380 assert_eq!(bindings_ctx.trigger_timers_for(current_timer, &mut core_ctx,), [timer_id]);
4381 assert_eq!(core_ctx.inner.take_frames(), []);
4382 bindings_ctx.timers.assert_no_timers_installed();
4383
4384 assert_eq!(
4387 NudHandler::send_ip_packet_to_neighbor(
4388 &mut core_ctx,
4389 &mut bindings_ctx,
4390 &FakeLinkDeviceId,
4391 I::LOOKUP_ADDR1,
4392 Buf::new([BODY], ..),
4393 FakeTxMetadata::default(),
4394 ),
4395 Ok(())
4396 );
4397 assert_eq!(
4398 core_ctx.inner.take_frames(),
4399 [
4400 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4401 (
4402 FakeNudMessageMeta::NeighborSolicitation {
4403 lookup_addr: I::LOOKUP_ADDR1,
4404 remote_link_addr: None,
4405 },
4406 Vec::new()
4407 )
4408 ]
4409 );
4410 bindings_ctx.timers.assert_timers_installed([(
4411 timer_id,
4412 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, 1),
4413 )]);
4414 }
4415
4416 #[ip_test(I)]
4417 #[test_case(true; "solicited confirmation")]
4418 #[test_case(false; "unsolicited confirmation")]
4419 fn confirmation_should_not_create_entry<I: TestIpExt>(solicited_flag: bool) {
4420 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4421
4422 let link_address = Some(FakeLinkAddress([1]));
4423 NudHandler::handle_neighbor_update(
4424 &mut core_ctx,
4425 &mut bindings_ctx,
4426 &FakeLinkDeviceId,
4427 I::LOOKUP_ADDR1,
4428 DynamicNeighborUpdateSource::Confirmation {
4429 link_address,
4430 flags: ConfirmationFlags { solicited_flag, override_flag: false },
4431 },
4432 );
4433 assert_eq!(core_ctx.nud.state.neighbors, HashMap::new());
4434 }
4435
4436 #[ip_test(I)]
4437 #[test_case(true; "set_with_dynamic")]
4438 #[test_case(false; "set_with_static")]
4439 fn pending_frames<I: TestIpExt>(dynamic: bool) {
4440 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4441 assert_eq!(core_ctx.inner.take_frames(), []);
4442
4443 const MAX_PENDING_FRAMES_U8: u8 = MAX_PENDING_FRAMES as u8;
4447 let expected_pending_frames = (0..MAX_PENDING_FRAMES_U8)
4448 .map(|i| (Buf::new(vec![i], ..), FakeTxMetadata::default()))
4449 .collect::<VecDeque<_>>();
4450
4451 for (body, meta) in expected_pending_frames.iter() {
4452 assert_eq!(
4453 NudHandler::send_ip_packet_to_neighbor(
4454 &mut core_ctx,
4455 &mut bindings_ctx,
4456 &FakeLinkDeviceId,
4457 I::LOOKUP_ADDR1,
4458 body.clone(),
4459 meta.clone(),
4460 ),
4461 Ok(())
4462 );
4463 }
4464 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4465 assert_neighbor_probe_sent(&mut core_ctx, None);
4467 assert_neighbor_state(
4468 &core_ctx,
4469 &mut bindings_ctx,
4470 DynamicNeighborState::Incomplete(Incomplete {
4471 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4472 pending_frames: expected_pending_frames.clone(),
4473 notifiers: Vec::new(),
4474 _marker: PhantomData,
4475 }),
4476 Some(ExpectedEvent::Added),
4477 );
4478
4479 assert_eq!(
4481 NudHandler::send_ip_packet_to_neighbor(
4482 &mut core_ctx,
4483 &mut bindings_ctx,
4484 &FakeLinkDeviceId,
4485 I::LOOKUP_ADDR1,
4486 Buf::new([123], ..),
4487 FakeTxMetadata::default(),
4488 ),
4489 Ok(())
4490 );
4491 assert_eq!(core_ctx.inner.take_frames(), []);
4492 assert_neighbor_state(
4493 &core_ctx,
4494 &mut bindings_ctx,
4495 DynamicNeighborState::Incomplete(Incomplete {
4496 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4497 pending_frames: expected_pending_frames.clone(),
4498 notifiers: Vec::new(),
4499 _marker: PhantomData,
4500 }),
4501 None,
4502 );
4503
4504 if dynamic {
4506 NudHandler::handle_neighbor_update(
4507 &mut core_ctx,
4508 &mut bindings_ctx,
4509 &FakeLinkDeviceId,
4510 I::LOOKUP_ADDR1,
4511 DynamicNeighborUpdateSource::Confirmation {
4512 link_address: Some(LINK_ADDR1),
4513 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4514 },
4515 );
4516 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4517 &mut bindings_ctx,
4518 [(
4519 I::LOOKUP_ADDR1,
4520 NudEvent::ReachableTime,
4521 core_ctx.inner.base_reachable_time().get(),
4522 )],
4523 );
4524 let last_confirmed_at = bindings_ctx.now();
4525 assert_neighbor_state(
4526 &core_ctx,
4527 &mut bindings_ctx,
4528 DynamicNeighborState::Reachable(Reachable {
4529 link_address: LINK_ADDR1,
4530 last_confirmed_at,
4531 }),
4532 Some(ExpectedEvent::Changed),
4533 );
4534 } else {
4535 init_static_neighbor(
4536 &mut core_ctx,
4537 &mut bindings_ctx,
4538 LINK_ADDR1,
4539 ExpectedEvent::Changed,
4540 );
4541 bindings_ctx.timers.assert_no_timers_installed();
4542 }
4543 assert_eq!(
4544 core_ctx.inner.take_frames(),
4545 expected_pending_frames
4546 .into_iter()
4547 .map(|(p, FakeTxMetadata)| (
4548 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4549 p.as_ref().to_vec()
4550 ))
4551 .collect::<Vec<_>>()
4552 );
4553 }
4554
4555 #[ip_test(I)]
4556 fn static_neighbor<I: TestIpExt>() {
4557 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4558
4559 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4560 bindings_ctx.timers.assert_no_timers_installed();
4561 assert_eq!(core_ctx.inner.take_frames(), []);
4562 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4563
4564 NudHandler::handle_neighbor_update(
4566 &mut core_ctx,
4567 &mut bindings_ctx,
4568 &FakeLinkDeviceId,
4569 I::LOOKUP_ADDR1,
4570 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4571 );
4572 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4573
4574 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4575
4576 let neighbors = &core_ctx.nud.state.neighbors;
4577 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4578 }
4579
4580 #[ip_test(I)]
4581 fn dynamic_neighbor<I: TestIpExt>() {
4582 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4583
4584 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4585 bindings_ctx.timers.assert_no_timers_installed();
4586 assert_eq!(core_ctx.inner.take_frames(), []);
4587 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4588
4589 NudHandler::handle_neighbor_update(
4591 &mut core_ctx,
4592 &mut bindings_ctx,
4593 &FakeLinkDeviceId,
4594 I::LOOKUP_ADDR1,
4595 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4596 );
4597 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR2);
4598 assert_eq!(core_ctx.inner.take_frames(), []);
4599 assert_neighbor_state(
4600 &core_ctx,
4601 &mut bindings_ctx,
4602 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4603 Some(ExpectedEvent::Changed),
4604 );
4605
4606 init_static_neighbor_with_ip(
4608 &mut core_ctx,
4609 &mut bindings_ctx,
4610 I::LOOKUP_ADDR1,
4611 LINK_ADDR3,
4612 ExpectedEvent::Changed,
4613 );
4614 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR3);
4615 assert_eq!(core_ctx.inner.take_frames(), []);
4616 }
4617
4618 #[ip_test(I)]
4619 fn send_solicitation_on_lookup<I: TestIpExt>() {
4620 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4621 bindings_ctx.timers.assert_no_timers_installed();
4622 assert_eq!(core_ctx.inner.take_frames(), []);
4623
4624 let mut pending_frames = VecDeque::new();
4625
4626 queue_ip_packet_to_unresolved_neighbor(
4627 &mut core_ctx,
4628 &mut bindings_ctx,
4629 I::LOOKUP_ADDR1,
4630 &mut pending_frames,
4631 1,
4632 true, );
4634 assert_neighbor_probe_sent(&mut core_ctx, None);
4635
4636 queue_ip_packet_to_unresolved_neighbor(
4637 &mut core_ctx,
4638 &mut bindings_ctx,
4639 I::LOOKUP_ADDR1,
4640 &mut pending_frames,
4641 2,
4642 false, );
4644 assert_eq!(core_ctx.inner.take_frames(), []);
4645
4646 NudHandler::handle_neighbor_update(
4648 &mut core_ctx,
4649 &mut bindings_ctx,
4650 &FakeLinkDeviceId,
4651 I::LOOKUP_ADDR1,
4652 DynamicNeighborUpdateSource::Confirmation {
4653 link_address: Some(LINK_ADDR1),
4654 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4655 },
4656 );
4657 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4658
4659 let now = bindings_ctx.now();
4660 assert_neighbor_state(
4661 &core_ctx,
4662 &mut bindings_ctx,
4663 DynamicNeighborState::Reachable(Reachable {
4664 link_address: LINK_ADDR1,
4665 last_confirmed_at: now,
4666 }),
4667 Some(ExpectedEvent::Changed),
4668 );
4669 assert_eq!(
4670 core_ctx.inner.take_frames(),
4671 pending_frames
4672 .into_iter()
4673 .map(|f| (
4674 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4675 f.as_ref().to_vec(),
4676 ))
4677 .collect::<Vec<_>>()
4678 );
4679 }
4680
4681 #[ip_test(I)]
4682 fn solicitation_failure_in_incomplete<I: TestIpExt>() {
4683 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4684 bindings_ctx.timers.assert_no_timers_installed();
4685 assert_eq!(core_ctx.inner.take_frames(), []);
4686
4687 let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
4688
4689 let timer_id = NudTimerId::neighbor();
4690
4691 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4692 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4693
4694 for i in 1..=max_multicast_solicit {
4695 assert_neighbor_state(
4696 &core_ctx,
4697 &mut bindings_ctx,
4698 DynamicNeighborState::Incomplete(Incomplete {
4699 transmit_counter: NonZeroU16::new(max_multicast_solicit - i),
4700 pending_frames: pending_frames
4701 .iter()
4702 .cloned()
4703 .map(|b| (b, FakeTxMetadata::default()))
4704 .collect(),
4705 notifiers: Vec::new(),
4706 _marker: PhantomData,
4707 }),
4708 None,
4709 );
4710
4711 bindings_ctx
4712 .timers
4713 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4714 assert_neighbor_probe_sent(&mut core_ctx, None);
4715
4716 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4717 }
4718
4719 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1);
4721 bindings_ctx.timers.assert_no_timers_installed();
4722
4723 assert_eq!(core_ctx.inner.take_frames(), []);
4727 assert_eq!(core_ctx.counters().as_ref().icmp_dest_unreachable_dropped.get(), 1);
4728 }
4729
4730 #[ip_test(I)]
4731 fn solicitation_failure_in_probe<I: TestIpExt>() {
4732 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4733 bindings_ctx.timers.assert_no_timers_installed();
4734 assert_eq!(core_ctx.inner.take_frames(), []);
4735
4736 init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
4737
4738 let timer_id = NudTimerId::neighbor();
4739 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4740 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4741 for i in 1..=max_unicast_solicit {
4742 assert_neighbor_state(
4743 &core_ctx,
4744 &mut bindings_ctx,
4745 DynamicNeighborState::Probe(Probe {
4746 transmit_counter: NonZeroU16::new(max_unicast_solicit - i),
4747 link_address: LINK_ADDR1,
4748 }),
4749 None,
4750 );
4751
4752 bindings_ctx
4753 .timers
4754 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4755 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4756
4757 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4758 }
4759
4760 assert_neighbor_state(
4761 &core_ctx,
4762 &mut bindings_ctx,
4763 DynamicNeighborState::Unreachable(Unreachable {
4764 link_address: LINK_ADDR1,
4765 mode: UnreachableMode::WaitingForPacketSend,
4766 }),
4767 Some(ExpectedEvent::Changed),
4768 );
4769 bindings_ctx.timers.assert_no_timers_installed();
4770 assert_eq!(core_ctx.inner.take_frames(), []);
4771 }
4772
4773 #[ip_test(I)]
4774 fn flush_entries<I: TestIpExt>() {
4775 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4776 bindings_ctx.timers.assert_no_timers_installed();
4777 assert_eq!(core_ctx.inner.take_frames(), []);
4778
4779 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4780 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR2, LINK_ADDR2);
4781 let pending_frames = init_incomplete_neighbor_with_ip(
4782 &mut core_ctx,
4783 &mut bindings_ctx,
4784 I::LOOKUP_ADDR3,
4785 true,
4786 );
4787 let pending_frames =
4788 pending_frames.into_iter().map(|b| (b, FakeTxMetadata::default())).collect();
4789
4790 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4791 assert_eq!(
4792 core_ctx.nud.state.neighbors,
4793 HashMap::from([
4794 (I::LOOKUP_ADDR1, NeighborState::Static(LINK_ADDR1)),
4795 (
4796 I::LOOKUP_ADDR2,
4797 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
4798 link_address: LINK_ADDR2,
4799 })),
4800 ),
4801 (
4802 I::LOOKUP_ADDR3,
4803 NeighborState::Dynamic(DynamicNeighborState::Incomplete(Incomplete {
4804 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4805 pending_frames,
4806 notifiers: Vec::new(),
4807 _marker: PhantomData,
4808 })),
4809 ),
4810 ]),
4811 );
4812 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4813 &mut bindings_ctx,
4814 [(I::LOOKUP_ADDR3, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
4815 );
4816
4817 NudHandler::flush(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId);
4819 let neighbors = &core_ctx.nud.state.neighbors;
4820 assert!(neighbors.is_empty(), "neighbor table should be empty: {:?}", neighbors);
4821 assert_eq!(
4822 bindings_ctx.take_events().into_iter().collect::<HashSet<_>>(),
4823 [I::LOOKUP_ADDR1, I::LOOKUP_ADDR2, I::LOOKUP_ADDR3]
4824 .into_iter()
4825 .map(|addr| { Event::removed(&FakeLinkDeviceId, addr, bindings_ctx.now()) })
4826 .collect(),
4827 );
4828 bindings_ctx.timers.assert_no_timers_installed();
4829 }
4830
4831 #[ip_test(I)]
4832 fn delete_dynamic_entry<I: TestIpExt>() {
4833 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4834 bindings_ctx.timers.assert_no_timers_installed();
4835 assert_eq!(core_ctx.inner.take_frames(), []);
4836
4837 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4838 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4839
4840 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4841
4842 let neighbors = &core_ctx.nud.state.neighbors;
4844 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4845 bindings_ctx.timers.assert_no_timers_installed();
4846 }
4847
4848 #[ip_test(I)]
4849 #[test_case(InitialState::Reachable; "reachable neighbor")]
4850 #[test_case(InitialState::Stale; "stale neighbor")]
4851 #[test_case(InitialState::Delay; "delay neighbor")]
4852 #[test_case(InitialState::Probe; "probe neighbor")]
4853 #[test_case(InitialState::Unreachable; "unreachable neighbor")]
4854 fn resolve_cached_linked_addr<I: TestIpExt>(initial_state: InitialState) {
4855 let mut ctx = new_context::<I>();
4856 ctx.bindings_ctx.timers.assert_no_timers_installed();
4857 assert_eq!(ctx.core_ctx.inner.take_frames(), []);
4858
4859 let _ = init_neighbor_in_state(&mut ctx.core_ctx, &mut ctx.bindings_ctx, initial_state);
4860
4861 let link_addr = assert_matches!(
4862 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4863 &FakeLinkDeviceId,
4864 &I::LOOKUP_ADDR1,
4865 ),
4866 LinkResolutionResult::Resolved(addr) => addr
4867 );
4868 assert_eq!(link_addr, LINK_ADDR1);
4869 if initial_state == InitialState::Stale {
4870 assert_eq!(
4871 ctx.bindings_ctx.take_events(),
4872 [Event::changed(
4873 &FakeLinkDeviceId,
4874 EventState::Dynamic(EventDynamicState::Delay(LINK_ADDR1)),
4875 I::LOOKUP_ADDR1,
4876 ctx.bindings_ctx.now(),
4877 )],
4878 );
4879 }
4880 }
4881
4882 enum ResolutionSuccess {
4883 Confirmation,
4884 StaticEntryAdded,
4885 }
4886
4887 #[ip_test(I)]
4888 #[test_case(ResolutionSuccess::Confirmation; "incomplete entry timed out")]
4889 #[test_case(ResolutionSuccess::StaticEntryAdded; "incomplete entry removed from table")]
4890 fn dynamic_neighbor_resolution_success<I: TestIpExt>(reason: ResolutionSuccess) {
4891 let mut ctx = new_context::<I>();
4892
4893 let observers = (0..10)
4894 .map(|_| {
4895 let observer = assert_matches!(
4896 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4897 &FakeLinkDeviceId,
4898 &I::LOOKUP_ADDR1,
4899 ),
4900 LinkResolutionResult::Pending(observer) => observer
4901 );
4902 assert_eq!(*observer.lock(), None);
4903 observer
4904 })
4905 .collect::<Vec<_>>();
4906 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
4907 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4908
4909 assert_neighbor_state(
4912 core_ctx,
4913 bindings_ctx,
4914 DynamicNeighborState::Incomplete(Incomplete {
4915 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4916 pending_frames: VecDeque::new(),
4917 notifiers: Vec::new(),
4919 _marker: PhantomData,
4920 }),
4921 Some(ExpectedEvent::Added),
4922 );
4923 assert_neighbor_probe_sent(core_ctx, None);
4924
4925 match reason {
4926 ResolutionSuccess::Confirmation => {
4927 NudHandler::handle_neighbor_update(
4929 core_ctx,
4930 bindings_ctx,
4931 &FakeLinkDeviceId,
4932 I::LOOKUP_ADDR1,
4933 DynamicNeighborUpdateSource::Confirmation {
4934 link_address: Some(LINK_ADDR1),
4935 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4936 },
4937 );
4938 let now = bindings_ctx.now();
4939 assert_neighbor_state(
4940 core_ctx,
4941 bindings_ctx,
4942 DynamicNeighborState::Reachable(Reachable {
4943 link_address: LINK_ADDR1,
4944 last_confirmed_at: now,
4945 }),
4946 Some(ExpectedEvent::Changed),
4947 );
4948 }
4949 ResolutionSuccess::StaticEntryAdded => {
4950 init_static_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, ExpectedEvent::Changed);
4951 assert_eq!(
4952 core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
4953 Some(&NeighborState::Static(LINK_ADDR1))
4954 );
4955 }
4956 }
4957
4958 for observer in observers {
4960 assert_eq!(*observer.lock(), Some(Ok(LINK_ADDR1)));
4961 }
4962 }
4963
4964 enum ResolutionFailure {
4965 Timeout,
4966 Removed,
4967 }
4968
4969 #[ip_test(I)]
4970 #[test_case(ResolutionFailure::Timeout; "incomplete entry timed out")]
4971 #[test_case(ResolutionFailure::Removed; "incomplete entry removed from table")]
4972 fn dynamic_neighbor_resolution_failure<I: TestIpExt>(reason: ResolutionFailure) {
4973 let mut ctx = new_context::<I>();
4974
4975 let observers = (0..10)
4976 .map(|_| {
4977 let observer = assert_matches!(
4978 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4979 &FakeLinkDeviceId,
4980 &I::LOOKUP_ADDR1,
4981 ),
4982 LinkResolutionResult::Pending(observer) => observer
4983 );
4984 assert_eq!(*observer.lock(), None);
4985 observer
4986 })
4987 .collect::<Vec<_>>();
4988
4989 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
4990 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4991
4992 assert_neighbor_state(
4995 core_ctx,
4996 bindings_ctx,
4997 DynamicNeighborState::Incomplete(Incomplete {
4998 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4999 pending_frames: VecDeque::new(),
5000 notifiers: Vec::new(),
5002 _marker: PhantomData,
5003 }),
5004 Some(ExpectedEvent::Added),
5005 );
5006 assert_neighbor_probe_sent(core_ctx, None);
5007
5008 match reason {
5009 ResolutionFailure::Timeout => {
5010 for _ in 1..=max_multicast_solicit {
5013 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
5014 assert_eq!(
5015 bindings_ctx.trigger_timers_for(retrans_timer, core_ctx),
5016 [NudTimerId::neighbor()]
5017 );
5018 }
5019 }
5020 ResolutionFailure::Removed => {
5021 NudHandler::flush(core_ctx, bindings_ctx, &FakeLinkDeviceId);
5023 }
5024 }
5025
5026 assert_neighbor_removed_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1);
5027 for observer in observers {
5029 assert_eq!(*observer.lock(), Some(Err(AddressResolutionFailed)));
5030 }
5031 }
5032
5033 #[ip_test(I)]
5034 #[test_case(InitialState::Incomplete, false; "incomplete neighbor")]
5035 #[test_case(InitialState::Reachable, true; "reachable neighbor")]
5036 #[test_case(InitialState::Stale, true; "stale neighbor")]
5037 #[test_case(InitialState::Delay, true; "delay neighbor")]
5038 #[test_case(InitialState::Probe, true; "probe neighbor")]
5039 #[test_case(InitialState::Unreachable, true; "unreachable neighbor")]
5040 fn upper_layer_confirmation<I: TestIpExt>(
5041 initial_state: InitialState,
5042 should_transition_to_reachable: bool,
5043 ) {
5044 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5045 let base_reachable_time = core_ctx.inner.base_reachable_time().get();
5046
5047 let initial = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
5048
5049 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5050
5051 if !should_transition_to_reachable {
5052 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial, None);
5053 return;
5054 }
5055
5056 let now = bindings_ctx.now();
5058 assert_neighbor_state(
5059 &core_ctx,
5060 &mut bindings_ctx,
5061 DynamicNeighborState::Reachable(Reachable {
5062 link_address: LINK_ADDR1,
5063 last_confirmed_at: now,
5064 }),
5065 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
5066 );
5067 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5068 &mut bindings_ctx,
5069 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time)],
5070 );
5071
5072 bindings_ctx.timers.instant.sleep(base_reachable_time / 2);
5076 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5077 let now = bindings_ctx.now();
5078 assert_neighbor_state(
5079 &core_ctx,
5080 &mut bindings_ctx,
5081 DynamicNeighborState::Reachable(Reachable {
5082 link_address: LINK_ADDR1,
5083 last_confirmed_at: now,
5084 }),
5085 None,
5086 );
5087 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5088 &mut bindings_ctx,
5089 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5090 );
5091
5092 assert_eq!(
5095 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5096 [NudTimerId::neighbor()]
5097 );
5098 let now = bindings_ctx.now();
5099 assert_neighbor_state(
5100 &core_ctx,
5101 &mut bindings_ctx,
5102 DynamicNeighborState::Reachable(Reachable {
5103 link_address: LINK_ADDR1,
5104 last_confirmed_at: now - base_reachable_time / 2,
5105 }),
5106 None,
5107 );
5108
5109 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5110 &mut bindings_ctx,
5111 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5112 );
5113
5114 assert_eq!(
5117 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5118 [NudTimerId::neighbor()]
5119 );
5120 assert_neighbor_state(
5121 &core_ctx,
5122 &mut bindings_ctx,
5123 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
5124 Some(ExpectedEvent::Changed),
5125 );
5126 bindings_ctx.timers.assert_no_timers_installed();
5127 }
5128
5129 fn generate_ip_addr<I: Ip>(i: usize) -> SpecifiedAddr<I::Addr> {
5130 I::map_ip_out(
5131 i,
5132 |i| {
5133 let start = u32::from_be_bytes(net_ip_v4!("192.168.0.1").ipv4_bytes());
5134 let bytes = (start + u32::try_from(i).unwrap()).to_be_bytes();
5135 SpecifiedAddr::new(Ipv4Addr::new(bytes)).unwrap()
5136 },
5137 |i| {
5138 let start = u128::from_be_bytes(net_ip_v6!("fe80::1").ipv6_bytes());
5139 let bytes = (start + u128::try_from(i).unwrap()).to_be_bytes();
5140 SpecifiedAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap()
5141 },
5142 )
5143 }
5144
5145 #[ip_test(I)]
5146 fn garbage_collection_retains_static_entries<I: TestIpExt>() {
5147 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5148
5149 for i in 0..MAX_ENTRIES * 2 {
5153 if i % 2 == 0 {
5154 init_stale_neighbor_with_ip(
5155 &mut core_ctx,
5156 &mut bindings_ctx,
5157 generate_ip_addr::<I>(i),
5158 LINK_ADDR1,
5159 );
5160 } else {
5161 init_static_neighbor_with_ip(
5162 &mut core_ctx,
5163 &mut bindings_ctx,
5164 generate_ip_addr::<I>(i),
5165 LINK_ADDR1,
5166 ExpectedEvent::Added,
5167 );
5168 }
5169 }
5170 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES * 2);
5171
5172 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5174 for event in bindings_ctx.take_events() {
5175 assert_matches!(event, Event {
5176 device,
5177 addr: _,
5178 kind,
5179 at,
5180 } => {
5181 assert_eq!(kind, EventKind::Removed);
5182 assert_eq!(device, FakeLinkDeviceId);
5183 assert_eq!(at, bindings_ctx.now());
5184 });
5185 }
5186 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5187 for (_, neighbor) in core_ctx.nud.state.neighbors {
5188 assert_matches!(neighbor, NeighborState::Static(_));
5189 }
5190 }
5191
5192 #[ip_test(I)]
5193 fn garbage_collection_retains_in_use_entries<I: TestIpExt>() {
5194 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5195
5196 for i in 0..MAX_ENTRIES - 1 {
5198 init_static_neighbor_with_ip(
5199 &mut core_ctx,
5200 &mut bindings_ctx,
5201 generate_ip_addr::<I>(i),
5202 LINK_ADDR1,
5203 ExpectedEvent::Added,
5204 );
5205 }
5206
5207 let stale_entry = generate_ip_addr::<I>(MAX_ENTRIES - 1);
5209 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry, LINK_ADDR1);
5210 let reachable_entry = generate_ip_addr::<I>(MAX_ENTRIES);
5212 init_reachable_neighbor_with_ip(
5213 &mut core_ctx,
5214 &mut bindings_ctx,
5215 reachable_entry,
5216 LINK_ADDR1,
5217 );
5218
5219 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5221 super::testutil::assert_dynamic_neighbor_state(
5222 &mut core_ctx,
5223 FakeLinkDeviceId,
5224 reachable_entry,
5225 DynamicNeighborState::Reachable(Reachable {
5226 link_address: LINK_ADDR1,
5227 last_confirmed_at: bindings_ctx.now(),
5228 }),
5229 );
5230 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry);
5231 }
5232
5233 #[ip_test(I)]
5234 fn garbage_collection_triggered_on_new_stale_entry<I: TestIpExt>() {
5235 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5236 core_ctx.nud.state.last_gc = Some(bindings_ctx.now());
5238
5239 for i in 0..MAX_ENTRIES {
5241 init_static_neighbor_with_ip(
5242 &mut core_ctx,
5243 &mut bindings_ctx,
5244 generate_ip_addr::<I>(i),
5245 LINK_ADDR1,
5246 ExpectedEvent::Added,
5247 );
5248 }
5249
5250 init_stale_neighbor_with_ip(
5253 &mut core_ctx,
5254 &mut bindings_ctx,
5255 generate_ip_addr::<I>(MAX_ENTRIES + 1),
5256 LINK_ADDR1,
5257 );
5258 let expected_gc_time = bindings_ctx.now() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5259 bindings_ctx
5260 .timers
5261 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5262
5263 bindings_ctx.timers.instant.sleep(ONE_SECOND.get());
5267 init_stale_neighbor_with_ip(
5268 &mut core_ctx,
5269 &mut bindings_ctx,
5270 generate_ip_addr::<I>(MAX_ENTRIES + 2),
5271 LINK_ADDR1,
5272 );
5273 bindings_ctx
5274 .timers
5275 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5276 }
5277
5278 #[ip_test(I)]
5279 fn garbage_collection_triggered_on_transition_to_unreachable<I: TestIpExt>() {
5280 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5281 core_ctx.nud.state.last_gc = Some(bindings_ctx.now());
5283
5284 for i in 0..MAX_ENTRIES {
5286 init_static_neighbor_with_ip(
5287 &mut core_ctx,
5288 &mut bindings_ctx,
5289 generate_ip_addr::<I>(i),
5290 LINK_ADDR1,
5291 ExpectedEvent::Added,
5292 );
5293 }
5294 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5295
5296 init_unreachable_neighbor_with_ip(
5299 &mut core_ctx,
5300 &mut bindings_ctx,
5301 generate_ip_addr::<I>(MAX_ENTRIES),
5302 LINK_ADDR1,
5303 );
5304 let expected_gc_time =
5305 core_ctx.nud.state.last_gc.unwrap() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5306 bindings_ctx
5307 .timers
5308 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5309
5310 init_unreachable_neighbor_with_ip(
5313 &mut core_ctx,
5314 &mut bindings_ctx,
5315 generate_ip_addr::<I>(MAX_ENTRIES + 1),
5316 LINK_ADDR1,
5317 );
5318 bindings_ctx
5319 .timers
5320 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5321 }
5322
5323 #[ip_test(I)]
5324 fn garbage_collection_not_triggered_on_new_incomplete_entry<I: TestIpExt>() {
5325 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5326
5327 for i in 0..MAX_ENTRIES {
5329 init_static_neighbor_with_ip(
5330 &mut core_ctx,
5331 &mut bindings_ctx,
5332 generate_ip_addr::<I>(i),
5333 LINK_ADDR1,
5334 ExpectedEvent::Added,
5335 );
5336 }
5337 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5338
5339 let _: VecDeque<Buf<Vec<u8>>> = init_incomplete_neighbor_with_ip(
5340 &mut core_ctx,
5341 &mut bindings_ctx,
5342 generate_ip_addr::<I>(MAX_ENTRIES),
5343 true,
5344 );
5345 assert_eq!(
5346 bindings_ctx.timers.scheduled_instant(&mut core_ctx.nud.state.timer_heap.gc),
5347 None
5348 );
5349 }
5350
5351 #[ip_test(I)]
5352 fn confirmation_processed_even_if_no_target_link_layer_addr<I: TestIpExt>() {
5353 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5354
5355 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
5357
5358 NudHandler::handle_neighbor_update(
5362 &mut core_ctx,
5363 &mut bindings_ctx,
5364 &FakeLinkDeviceId,
5365 I::LOOKUP_ADDR1,
5366 DynamicNeighborUpdateSource::Confirmation {
5367 link_address: None,
5368 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
5369 },
5370 );
5371 let now = bindings_ctx.now();
5372 assert_neighbor_state(
5373 &core_ctx,
5374 &mut bindings_ctx,
5375 DynamicNeighborState::Reachable(Reachable {
5376 link_address: LINK_ADDR1,
5377 last_confirmed_at: now,
5378 }),
5379 Some(ExpectedEvent::Changed),
5380 );
5381 }
5382
5383 #[ip_test(I)]
5384 #[test_case(InitialState::Stale; "stale")]
5385 #[test_case(InitialState::Reachable; "reachable")]
5386 #[test_case(InitialState::Delay; "delay")]
5387 #[test_case(InitialState::Unreachable; "unreachable")]
5388 fn enter_probe_from_dynamic_state<I: TestIpExt>(initial: InitialState) {
5389 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5390
5391 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial);
5392
5393 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5394 let result = neighbor.enter_probe(
5395 &mut core_ctx.inner.state,
5396 &mut bindings_ctx,
5397 &mut core_ctx.nud.state.timer_heap,
5398 I::LOOKUP_ADDR1,
5399 &FakeLinkDeviceId,
5400 );
5401
5402 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5403 assert_matches!(result, Ok(Some(LINK_ADDR1)));
5404 assert_neighbor_state(
5405 &core_ctx,
5406 &mut bindings_ctx,
5407 DynamicNeighborState::Probe(Probe {
5408 link_address: LINK_ADDR1,
5409 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5410 }),
5411 Some(ExpectedEvent::Changed),
5412 );
5413 }
5414
5415 #[ip_test(I)]
5416 fn enter_probe_from_static_state<I: TestIpExt>() {
5417 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5418
5419 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
5420
5421 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5422 let result = neighbor.enter_probe(
5423 &mut core_ctx.inner.state,
5424 &mut bindings_ctx,
5425 &mut core_ctx.nud.state.timer_heap,
5426 I::LOOKUP_ADDR1,
5427 &FakeLinkDeviceId,
5428 );
5429
5430 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5431 assert_matches!(result, Ok(Some(LINK_ADDR1)));
5432 assert_neighbor_state(
5433 &core_ctx,
5434 &mut bindings_ctx,
5435 DynamicNeighborState::Probe(Probe {
5436 link_address: LINK_ADDR1,
5437 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5438 }),
5439 Some(ExpectedEvent::Changed),
5440 );
5441 }
5442
5443 #[ip_test(I)]
5444 fn enter_probe_from_probe_state<I: TestIpExt>() {
5445 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5446
5447 init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
5448
5449 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5450 let result = neighbor.enter_probe(
5451 &mut core_ctx.inner.state,
5452 &mut bindings_ctx,
5453 &mut core_ctx.nud.state.timer_heap,
5454 I::LOOKUP_ADDR1,
5455 &FakeLinkDeviceId,
5456 );
5457
5458 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5459 assert_matches!(result, Ok(None)); assert_neighbor_state(
5461 &core_ctx,
5462 &mut bindings_ctx,
5463 DynamicNeighborState::Probe(Probe {
5464 link_address: LINK_ADDR1,
5465 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5466 }),
5467 None, );
5469 }
5470
5471 #[ip_test(I)]
5472 fn enter_probe_from_incomplete_state<I: TestIpExt>() {
5473 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5474
5475 let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
5476
5477 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5478 let result = neighbor.enter_probe(
5479 &mut core_ctx.inner.state,
5480 &mut bindings_ctx,
5481 &mut core_ctx.nud.state.timer_heap,
5482 I::LOOKUP_ADDR1,
5483 &FakeLinkDeviceId,
5484 );
5485
5486 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5487 assert_matches!(result, Err(EnterProbeError::LinkAddressUnknown));
5488 assert_neighbor_state(
5489 &core_ctx,
5490 &mut bindings_ctx,
5491 DynamicNeighborState::Incomplete(Incomplete {
5492 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5493 pending_frames: pending_frames
5494 .iter()
5495 .cloned()
5496 .map(|b| (b, FakeTxMetadata::default()))
5497 .collect(),
5498 notifiers: Vec::new(),
5499 _marker: PhantomData,
5500 }),
5501 None, );
5503 }
5504}