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};
14
15use assert_matches::assert_matches;
16use derivative::Derivative;
17use log::{debug, error, warn};
18use net_types::SpecifiedAddr;
19use net_types::ip::{GenericOverIp, Ip, IpMarked, Ipv4, Ipv6};
20use netstack3_base::socket::{SocketIpAddr, SocketIpAddrExt as _};
21use netstack3_base::{
22 AddressResolutionFailed, AnyDevice, CoreTimerContext, Counter, CounterContext, DeviceIdContext,
23 DeviceIdentifier, ErrorAndSerializer, EventContext, HandleableTimer, Instant,
24 InstantBindingsTypes, LinkAddress, LinkDevice, LinkUnicastAddress, LocalTimerHeap,
25 SendFrameError, StrongDeviceIdentifier, TimerBindingsTypes, TimerContext,
26 TxMetadataBindingsTypes, WeakDeviceIdentifier,
27};
28use netstack3_hashmap::hash_map::{self, Entry, HashMap};
29use packet::{
30 Buf, BufferMut, GrowBuffer as _, ParsablePacket as _, ParseBufferMut as _, SerializeError,
31 Serializer,
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: Serializer<Buffer = B>,
442 {
443 let packet = packet
447 .serialize_vec_outer()
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: Serializer<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()
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: Serializer,
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 DynamicNeighborState::Reachable(Reachable {
1448 link_address: current,
1449 last_confirmed_at: _,
1450 })
1451 | DynamicNeighborState::Stale(Stale { link_address: current })
1452 | DynamicNeighborState::Delay(Delay { link_address: current })
1453 | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1454 | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1455 let updated_link_address = link_address
1474 .and_then(|link_address| (current != &link_address).then_some(link_address));
1475
1476 match (solicited_flag, updated_link_address, override_flag) {
1477 (true, _, true) | (true, None, _) => {
1483 Some(NewState::Reachable { link_address: link_address.unwrap_or(*current) })
1484 }
1485 (_, Some(_), false) => match self {
1495 DynamicNeighborState::Reachable(Reachable {
1497 link_address,
1498 last_confirmed_at: _,
1499 }) => Some(NewState::Stale { link_address: *link_address }),
1500 DynamicNeighborState::Stale(_)
1502 | DynamicNeighborState::Delay(_)
1503 | DynamicNeighborState::Probe(_)
1504 | DynamicNeighborState::Unreachable(_) => None,
1505 DynamicNeighborState::Incomplete(_) => unreachable!(),
1507 },
1508 (false, Some(link_address), true) => Some(NewState::Stale { link_address }),
1514 (false, None, _) => None,
1519 }
1520 }
1521 };
1522 match new_state {
1523 Some(NewState::Reachable { link_address }) => self.enter_reachable(
1524 core_ctx,
1525 bindings_ctx,
1526 timers,
1527 device_id,
1528 neighbor,
1529 link_address,
1530 ),
1531 Some(NewState::Stale { link_address }) => self.enter_stale(
1532 core_ctx,
1533 bindings_ctx,
1534 timers,
1535 device_id,
1536 neighbor,
1537 link_address,
1538 num_entries,
1539 last_gc,
1540 ),
1541 None => {}
1542 }
1543 }
1544}
1545
1546#[cfg(any(test, feature = "testutils"))]
1547pub(crate) mod testutil {
1548 use super::*;
1549
1550 use alloc::sync::Arc;
1551
1552 use netstack3_base::sync::Mutex;
1553 use netstack3_base::testutil::{FakeBindingsCtx, FakeCoreCtx};
1554
1555 pub fn assert_dynamic_neighbor_with_addr<
1557 I: Ip,
1558 D: LinkDevice,
1559 BC: NudBindingsContext<I, D, CC::DeviceId>,
1560 CC: NudContext<I, D, BC>,
1561 >(
1562 core_ctx: &mut CC,
1563 device_id: CC::DeviceId,
1564 neighbor: SpecifiedAddr<I::Addr>,
1565 expected_link_addr: D::Address,
1566 ) {
1567 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1568 assert_matches!(
1569 neighbors.get(&neighbor),
1570 Some(NeighborState::Dynamic(
1571 DynamicNeighborState::Reachable(Reachable{ link_address, last_confirmed_at: _ })
1572 | DynamicNeighborState::Stale(Stale{ link_address })
1573 )) => {
1574 assert_eq!(link_address, &expected_link_addr)
1575 }
1576 )
1577 })
1578 }
1579
1580 pub fn assert_dynamic_neighbor_state<I, D, BC, CC>(
1582 core_ctx: &mut CC,
1583 device_id: CC::DeviceId,
1584 neighbor: SpecifiedAddr<I::Addr>,
1585 expected_state: DynamicNeighborState<D, BC>,
1586 ) where
1587 I: Ip,
1588 D: LinkDevice + PartialEq,
1589 BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata: PartialEq>,
1590 CC: NudContext<I, D, BC>,
1591 {
1592 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1593 assert_matches!(
1594 neighbors.get(&neighbor),
1595 Some(NeighborState::Dynamic(state)) => {
1596 assert_eq!(state, &expected_state)
1597 }
1598 )
1599 })
1600 }
1601
1602 pub fn assert_neighbor_unknown<
1604 I: Ip,
1605 D: LinkDevice,
1606 BC: NudBindingsContext<I, D, CC::DeviceId>,
1607 CC: NudContext<I, D, BC>,
1608 >(
1609 core_ctx: &mut CC,
1610 device_id: CC::DeviceId,
1611 neighbor: SpecifiedAddr<I::Addr>,
1612 ) {
1613 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1614 assert_matches!(neighbors.get(&neighbor), None)
1615 })
1616 }
1617
1618 impl<D: LinkDevice, Id, Event: Debug, State, FrameMeta> LinkResolutionContext<D>
1619 for FakeBindingsCtx<Id, Event, State, FrameMeta>
1620 {
1621 type Notifier = FakeLinkResolutionNotifier<D>;
1622 }
1623
1624 #[derive(Debug)]
1626 pub struct FakeLinkResolutionNotifier<D: LinkDevice>(
1627 Arc<Mutex<Option<Result<D::Address, AddressResolutionFailed>>>>,
1628 );
1629
1630 impl<D: LinkDevice> LinkResolutionNotifier<D> for FakeLinkResolutionNotifier<D> {
1631 type Observer = Arc<Mutex<Option<Result<D::Address, AddressResolutionFailed>>>>;
1632
1633 fn new() -> (Self, Self::Observer) {
1634 let inner = Arc::new(Mutex::new(None));
1635 (Self(inner.clone()), inner)
1636 }
1637
1638 fn notify(self, result: Result<D::Address, AddressResolutionFailed>) {
1639 let Self(inner) = self;
1640 let mut inner = inner.lock();
1641 assert_eq!(*inner, None, "resolved link address was set more than once");
1642 *inner = Some(result);
1643 }
1644 }
1645
1646 impl<S, Meta, DeviceId> UseDelegateNudContext for FakeCoreCtx<S, Meta, DeviceId> where
1647 S: UseDelegateNudContext
1648 {
1649 }
1650 impl<I: Ip, S, Meta, DeviceId> DelegateNudContext<I> for FakeCoreCtx<S, Meta, DeviceId>
1651 where
1652 S: DelegateNudContext<I>,
1653 {
1654 type Delegate<T> = S::Delegate<T>;
1655 }
1656}
1657
1658#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1659enum NudEvent {
1660 RetransmitMulticastProbe,
1661 ReachableTime,
1662 DelayFirstProbe,
1663 RetransmitUnicastProbe,
1664}
1665
1666#[derive(GenericOverIp, Copy, Clone, Debug, Eq, PartialEq, Hash)]
1668#[generic_over_ip(I, Ip)]
1669pub struct NudTimerId<I: Ip, L: LinkDevice, D: WeakDeviceIdentifier> {
1670 device_id: D,
1671 timer_type: NudTimerType,
1672 _marker: PhantomData<(I, L)>,
1673}
1674
1675#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1676enum NudTimerType {
1677 Neighbor,
1678 GarbageCollection,
1679}
1680
1681#[derive(Debug)]
1683pub(crate) struct TimerHeap<I: Ip, BT: TimerBindingsTypes + InstantBindingsTypes> {
1684 gc: BT::Timer,
1685 neighbor: LocalTimerHeap<SpecifiedAddr<I::Addr>, NudEvent, BT>,
1686}
1687
1688impl<I: Ip, BC: TimerContext> TimerHeap<I, BC> {
1689 fn new<
1690 DeviceId: WeakDeviceIdentifier,
1691 L: LinkDevice,
1692 CC: CoreTimerContext<NudTimerId<I, L, DeviceId>, BC>,
1693 >(
1694 bindings_ctx: &mut BC,
1695 device_id: DeviceId,
1696 ) -> Self {
1697 Self {
1698 neighbor: LocalTimerHeap::new_with_context::<_, CC>(
1699 bindings_ctx,
1700 NudTimerId {
1701 device_id: device_id.clone(),
1702 timer_type: NudTimerType::Neighbor,
1703 _marker: PhantomData,
1704 },
1705 ),
1706 gc: CC::new_timer(
1707 bindings_ctx,
1708 NudTimerId {
1709 device_id,
1710 timer_type: NudTimerType::GarbageCollection,
1711 _marker: PhantomData,
1712 },
1713 ),
1714 }
1715 }
1716
1717 fn schedule_neighbor(
1718 &mut self,
1719 bindings_ctx: &mut BC,
1720 after: NonZeroDuration,
1721 neighbor: SpecifiedAddr<I::Addr>,
1722 event: NudEvent,
1723 ) {
1724 let Self { neighbor: heap, gc: _ } = self;
1725 assert_eq!(heap.schedule_after(bindings_ctx, neighbor, event, after.get()), None);
1726 }
1727
1728 fn schedule_neighbor_at(
1729 &mut self,
1730 bindings_ctx: &mut BC,
1731 at: BC::Instant,
1732 neighbor: SpecifiedAddr<I::Addr>,
1733 event: NudEvent,
1734 ) {
1735 let Self { neighbor: heap, gc: _ } = self;
1736 assert_eq!(heap.schedule_instant(bindings_ctx, neighbor, event, at), None);
1737 }
1738
1739 fn cancel_neighbor(
1741 &mut self,
1742 bindings_ctx: &mut BC,
1743 neighbor: SpecifiedAddr<I::Addr>,
1744 ) -> Option<NudEvent> {
1745 let Self { neighbor: heap, gc: _ } = self;
1746 heap.cancel(bindings_ctx, &neighbor).map(|(_instant, v)| v)
1747 }
1748
1749 fn pop_neighbor(
1750 &mut self,
1751 bindings_ctx: &mut BC,
1752 ) -> Option<(SpecifiedAddr<I::Addr>, NudEvent)> {
1753 let Self { neighbor: heap, gc: _ } = self;
1754 heap.pop(bindings_ctx)
1755 }
1756
1757 fn maybe_schedule_gc(
1760 &mut self,
1761 bindings_ctx: &mut BC,
1762 num_entries: usize,
1763 last_gc: &Option<BC::Instant>,
1764 ) {
1765 let Self { gc, neighbor: _ } = self;
1766 if num_entries > MAX_ENTRIES && bindings_ctx.scheduled_instant(gc).is_none() {
1767 let instant = if let Some(last_gc) = last_gc {
1768 last_gc.panicking_add(MIN_GARBAGE_COLLECTION_INTERVAL.get())
1769 } else {
1770 bindings_ctx.now()
1771 };
1772 assert_eq!(bindings_ctx.schedule_timer_instant(instant, gc), None);
1776 }
1777 }
1778}
1779
1780#[derive(Debug)]
1782pub struct NudState<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> {
1783 neighbors: HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>>,
1785 last_gc: Option<BT::Instant>,
1786 timer_heap: TimerHeap<I, BT>,
1787}
1788
1789impl<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> NudState<I, D, BT> {
1790 #[cfg(any(test, feature = "testutils"))]
1792 pub fn neighbors(&self) -> &HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>> {
1793 &self.neighbors
1794 }
1795
1796 fn entry_and_timer_heap(
1797 &mut self,
1798 addr: SpecifiedAddr<I::Addr>,
1799 ) -> (Entry<'_, SpecifiedAddr<I::Addr>, NeighborState<D, BT>>, &mut TimerHeap<I, BT>) {
1800 let Self { neighbors, timer_heap, .. } = self;
1801 (neighbors.entry(addr), timer_heap)
1802 }
1803}
1804
1805impl<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D> + TimerContext> NudState<I, D, BC> {
1806 pub fn new<
1808 DeviceId: WeakDeviceIdentifier,
1809 CC: CoreTimerContext<NudTimerId<I, D, DeviceId>, BC>,
1810 >(
1811 bindings_ctx: &mut BC,
1812 device_id: DeviceId,
1813 ) -> Self {
1814 Self {
1815 neighbors: Default::default(),
1816 last_gc: None,
1817 timer_heap: TimerHeap::new::<_, _, CC>(bindings_ctx, device_id),
1818 }
1819 }
1820}
1821
1822pub trait NudBindingsContext<I: Ip, D: LinkDevice, DeviceId>:
1824 TimerContext
1825 + LinkResolutionContext<D>
1826 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1827 + NudBindingsTypes<D>
1828{
1829}
1830
1831impl<
1832 I: Ip,
1833 D: LinkDevice,
1834 DeviceId,
1835 BC: TimerContext
1836 + LinkResolutionContext<D>
1837 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1838 + NudBindingsTypes<D>,
1839> NudBindingsContext<I, D, DeviceId> for BC
1840{
1841}
1842
1843pub trait NudBindingsTypes<D: LinkDevice>:
1845 LinkResolutionContext<D> + InstantBindingsTypes + TimerBindingsTypes + TxMetadataBindingsTypes
1846{
1847}
1848
1849impl<BT, D> NudBindingsTypes<D> for BT
1850where
1851 D: LinkDevice,
1852 BT: LinkResolutionContext<D>
1853 + InstantBindingsTypes
1854 + TimerBindingsTypes
1855 + TxMetadataBindingsTypes,
1856{
1857}
1858
1859pub trait LinkResolutionContext<D: LinkDevice> {
1861 type Notifier: LinkResolutionNotifier<D>;
1864}
1865
1866pub trait LinkResolutionNotifier<D: LinkDevice>: Debug + Sized + Send {
1869 type Observer;
1872
1873 fn new() -> (Self, Self::Observer);
1875
1876 fn notify(self, result: Result<D::Address, AddressResolutionFailed>);
1879}
1880
1881pub trait NudContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
1883 type ConfigCtx<'a>: NudConfigContext<I>;
1885 type SenderCtx<'a>: NudSenderContext<I, D, BC, DeviceId = Self::DeviceId>;
1887
1888 fn with_nud_state_mut_and_sender_ctx<
1891 O,
1892 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1893 >(
1894 &mut self,
1895 device_id: &Self::DeviceId,
1896 cb: F,
1897 ) -> O;
1898
1899 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1902 &mut self,
1903 device_id: &Self::DeviceId,
1904 cb: F,
1905 ) -> O;
1906
1907 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1909 &mut self,
1910 device_id: &Self::DeviceId,
1911 cb: F,
1912 ) -> O;
1913
1914 fn send_neighbor_solicitation(
1919 &mut self,
1920 bindings_ctx: &mut BC,
1921 device_id: &Self::DeviceId,
1922 lookup_addr: SpecifiedAddr<I::Addr>,
1923 remote_link_addr: Option<D::Address>,
1924 );
1925}
1926
1927pub trait UseDelegateNudContext {}
1930
1931pub trait DelegateNudContext<I: Ip>: UseDelegateNudContext + Sized {
1937 type Delegate<T>: ref_cast::RefCast<From = T>;
1939 fn wrap(&mut self) -> &mut Self::Delegate<Self> {
1941 <Self::Delegate<Self> as ref_cast::RefCast>::ref_cast_mut(self)
1942 }
1943}
1944
1945impl<I, D, BC, CC> NudContext<I, D, BC> for CC
1946where
1947 I: Ip,
1948 D: LinkDevice,
1949 BC: NudBindingsTypes<D>,
1950 CC: DelegateNudContext<I, Delegate<CC>: NudContext<I, D, BC, DeviceId = CC::DeviceId>>
1951 + UseDelegateNudContext
1954 + DeviceIdContext<D>,
1955{
1956 type ConfigCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::ConfigCtx<'a>;
1957 type SenderCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::SenderCtx<'a>;
1958 fn with_nud_state_mut_and_sender_ctx<
1959 O,
1960 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1961 >(
1962 &mut self,
1963 device_id: &Self::DeviceId,
1964 cb: F,
1965 ) -> O {
1966 self.wrap().with_nud_state_mut_and_sender_ctx(device_id, cb)
1967 }
1968
1969 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1970 &mut self,
1971 device_id: &Self::DeviceId,
1972 cb: F,
1973 ) -> O {
1974 self.wrap().with_nud_state_mut(device_id, cb)
1975 }
1976 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1977 &mut self,
1978 device_id: &Self::DeviceId,
1979 cb: F,
1980 ) -> O {
1981 self.wrap().with_nud_state(device_id, cb)
1982 }
1983 fn send_neighbor_solicitation(
1984 &mut self,
1985 bindings_ctx: &mut BC,
1986 device_id: &Self::DeviceId,
1987 lookup_addr: SpecifiedAddr<I::Addr>,
1988 remote_link_addr: Option<D::Address>,
1989 ) {
1990 self.wrap().send_neighbor_solicitation(
1991 bindings_ctx,
1992 device_id,
1993 lookup_addr,
1994 remote_link_addr,
1995 )
1996 }
1997}
1998
1999pub trait NudIcmpIpExt: packet_formats::ip::IpExt {
2001 type Metadata;
2004
2005 fn extract_metadata<B: SplitByteSlice>(packet: &Self::Packet<B>) -> Self::Metadata;
2007}
2008
2009impl NudIcmpIpExt for Ipv4 {
2010 type Metadata = Ipv4FragmentType;
2011
2012 fn extract_metadata<B: SplitByteSlice>(packet: &Ipv4Packet<B>) -> Self::Metadata {
2013 packet.fragment_type()
2014 }
2015}
2016
2017impl NudIcmpIpExt for Ipv6 {
2018 type Metadata = ();
2019
2020 fn extract_metadata<B: SplitByteSlice>(_packet: &Ipv6Packet<B>) -> Self::Metadata {
2021 ()
2022 }
2023}
2024
2025pub trait NudIcmpContext<I: NudIcmpIpExt, D: LinkDevice, BC>: DeviceIdContext<D> {
2028 fn send_icmp_dest_unreachable(
2035 &mut self,
2036 bindings_ctx: &mut BC,
2037 frame: Buf<Vec<u8>>,
2038 device_id: Option<&Self::DeviceId>,
2039 original_src_ip: SocketIpAddr<I::Addr>,
2040 original_dst_ip: SocketIpAddr<I::Addr>,
2041 header_len: usize,
2042 proto: I::Proto,
2043 metadata: I::Metadata,
2044 );
2045}
2046
2047#[derive(Clone, Debug)]
2049pub struct NudUserConfig {
2050 pub max_unicast_solicitations: NonZeroU16,
2055 pub max_multicast_solicitations: NonZeroU16,
2060 pub base_reachable_time: NonZeroDuration,
2066 pub retrans_timer: NonZeroDuration,
2072}
2073
2074impl Default for NudUserConfig {
2075 fn default() -> Self {
2076 NudUserConfig {
2077 max_unicast_solicitations: DEFAULT_MAX_UNICAST_SOLICIT,
2078 max_multicast_solicitations: DEFAULT_MAX_MULTICAST_SOLICIT,
2079 base_reachable_time: DEFAULT_BASE_REACHABLE_TIME,
2080 retrans_timer: DEFAULT_RETRANS_TIMER,
2081 }
2082 }
2083}
2084
2085#[allow(missing_docs)]
2089#[derive(Clone, Debug, Eq, PartialEq, Default)]
2090pub struct NudUserConfigUpdate {
2091 pub max_unicast_solicitations: Option<NonZeroU16>,
2092 pub max_multicast_solicitations: Option<NonZeroU16>,
2093 pub base_reachable_time: Option<NonZeroDuration>,
2094 pub retrans_timer: Option<NonZeroDuration>,
2095}
2096
2097impl NudUserConfigUpdate {
2098 pub fn apply_and_take_previous(mut self, config: &mut NudUserConfig) -> Self {
2101 fn swap_if_set<T>(opt: &mut Option<T>, target: &mut T) {
2102 if let Some(opt) = opt.as_mut() {
2103 core::mem::swap(opt, target)
2104 }
2105 }
2106 let Self {
2107 max_unicast_solicitations,
2108 max_multicast_solicitations,
2109 base_reachable_time,
2110 retrans_timer,
2111 } = &mut self;
2112 swap_if_set(max_unicast_solicitations, &mut config.max_unicast_solicitations);
2113 swap_if_set(max_multicast_solicitations, &mut config.max_multicast_solicitations);
2114 swap_if_set(base_reachable_time, &mut config.base_reachable_time);
2115 swap_if_set(retrans_timer, &mut config.retrans_timer);
2116
2117 self
2118 }
2119}
2120
2121pub trait NudConfigContext<I: Ip> {
2124 fn retransmit_timeout(&mut self) -> NonZeroDuration;
2131
2132 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O;
2134
2135 fn max_unicast_solicit(&mut self) -> NonZeroU16 {
2137 self.with_nud_user_config(|NudUserConfig { max_unicast_solicitations, .. }| {
2138 *max_unicast_solicitations
2139 })
2140 }
2141
2142 fn max_multicast_solicit(&mut self) -> NonZeroU16 {
2144 self.with_nud_user_config(|NudUserConfig { max_multicast_solicitations, .. }| {
2145 *max_multicast_solicitations
2146 })
2147 }
2148
2149 fn base_reachable_time(&mut self) -> NonZeroDuration {
2152 self.with_nud_user_config(|NudUserConfig { base_reachable_time, .. }| *base_reachable_time)
2153 }
2154}
2155
2156pub trait NudSenderContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>:
2159 NudConfigContext<I> + DeviceIdContext<D>
2160{
2161 fn send_ip_packet_to_neighbor_link_addr<S>(
2163 &mut self,
2164 bindings_ctx: &mut BC,
2165 neighbor_link_addr: D::Address,
2166 body: S,
2167 meta: BC::TxMetadata,
2168 ) -> Result<(), SendFrameError<S>>
2169 where
2170 S: Serializer,
2171 S::Buffer: BufferMut;
2172}
2173
2174pub trait NudIpHandler<I: Ip, BC>: DeviceIdContext<AnyDevice> {
2176 fn handle_neighbor_probe(
2181 &mut self,
2182 bindings_ctx: &mut BC,
2183 device_id: &Self::DeviceId,
2184 neighbor: SpecifiedAddr<I::Addr>,
2185 link_addr: &[u8],
2186 );
2187
2188 fn handle_neighbor_confirmation(
2192 &mut self,
2193 bindings_ctx: &mut BC,
2194 device_id: &Self::DeviceId,
2195 neighbor: SpecifiedAddr<I::Addr>,
2196 link_addr: Option<&[u8]>,
2197 flags: ConfirmationFlags,
2198 );
2199
2200 fn flush_neighbor_table(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2202}
2203
2204#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2206pub enum LinkResolutionResult<A: LinkAddress, Observer> {
2207 Resolved(A),
2209 Pending(Observer),
2211}
2212
2213pub trait NudHandler<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
2215 fn handle_neighbor_update(
2218 &mut self,
2219 bindings_ctx: &mut BC,
2220 device_id: &Self::DeviceId,
2221 neighbor: SpecifiedAddr<I::Addr>,
2226 source: DynamicNeighborUpdateSource<D::Address>,
2227 );
2228
2229 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2231
2232 fn send_ip_packet_to_neighbor<S>(
2237 &mut self,
2238 bindings_ctx: &mut BC,
2239 device_id: &Self::DeviceId,
2240 neighbor: SpecifiedAddr<I::Addr>,
2241 body: S,
2242 meta: BC::TxMetadata,
2243 ) -> Result<(), SendFrameError<S>>
2244 where
2245 S: Serializer,
2246 S::Buffer: BufferMut;
2247}
2248
2249enum TransmitProbe<A> {
2250 Multicast,
2251 Unicast(A),
2252}
2253
2254impl<
2255 I: NudIcmpIpExt,
2256 D: LinkDevice,
2257 BC: NudBindingsContext<I, D, CC::DeviceId>,
2258 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2259> HandleableTimer<CC, BC> for NudTimerId<I, D, CC::WeakDeviceId>
2260{
2261 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
2262 let Self { device_id, timer_type, _marker: PhantomData } = self;
2263 let Some(device_id) = device_id.upgrade() else {
2264 return;
2265 };
2266 match timer_type {
2267 NudTimerType::Neighbor => handle_neighbor_timer(core_ctx, bindings_ctx, device_id),
2268 NudTimerType::GarbageCollection => collect_garbage(core_ctx, bindings_ctx, device_id),
2269 }
2270 }
2271}
2272
2273fn handle_neighbor_timer<I, D, CC, BC>(
2274 core_ctx: &mut CC,
2275 bindings_ctx: &mut BC,
2276 device_id: CC::DeviceId,
2277) where
2278 I: NudIcmpIpExt,
2279 D: LinkDevice,
2280 BC: NudBindingsContext<I, D, CC::DeviceId>,
2281 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2282{
2283 enum Action<L, A, M> {
2284 TransmitProbe { probe: TransmitProbe<L>, to: A },
2285 SendIcmpDestUnreachable(VecDeque<(Buf<Vec<u8>>, M)>),
2286 }
2287 let action = core_ctx.with_nud_state_mut(
2288 &device_id,
2289 |NudState { neighbors, last_gc, timer_heap }, core_ctx| {
2290 let (lookup_addr, event) = timer_heap.pop_neighbor(bindings_ctx)?;
2291 let num_entries = neighbors.len();
2292 let mut entry = match neighbors.entry(lookup_addr) {
2293 Entry::Occupied(entry) => entry,
2294 Entry::Vacant(_) => panic!("timer fired for invalid entry"),
2295 };
2296
2297 match entry.get_mut() {
2298 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)) => {
2299 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2300
2301 if incomplete.schedule_timer_if_should_retransmit(
2302 core_ctx,
2303 bindings_ctx,
2304 timer_heap,
2305 lookup_addr,
2306 ) {
2307 Some(Action::TransmitProbe {
2308 probe: TransmitProbe::Multicast,
2309 to: lookup_addr,
2310 })
2311 } else {
2312 debug!("neighbor resolution failed for {lookup_addr}; removing entry");
2320 let Incomplete {
2321 transmit_counter: _,
2322 ref mut pending_frames,
2323 notifiers: _,
2324 _marker,
2325 } = assert_matches!(
2326 entry.remove(),
2327 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete))
2328 => incomplete
2329 );
2330 let pending_frames = core::mem::take(pending_frames);
2331 bindings_ctx.on_event(Event::removed(
2332 &device_id,
2333 lookup_addr,
2334 bindings_ctx.now(),
2335 ));
2336 Some(Action::SendIcmpDestUnreachable(pending_frames))
2337 }
2338 }
2339 NeighborState::Dynamic(DynamicNeighborState::Probe(probe)) => {
2340 assert_eq!(event, NudEvent::RetransmitUnicastProbe);
2341
2342 let Probe { link_address, transmit_counter: _ } = probe;
2343 let link_address = *link_address;
2344 if probe.schedule_timer_if_should_retransmit(
2345 core_ctx,
2346 bindings_ctx,
2347 timer_heap,
2348 lookup_addr,
2349 ) {
2350 Some(Action::TransmitProbe {
2351 probe: TransmitProbe::Unicast(link_address),
2352 to: lookup_addr,
2353 })
2354 } else {
2355 let unreachable =
2356 probe.enter_unreachable(bindings_ctx, timer_heap, num_entries, last_gc);
2357 *entry.get_mut() =
2358 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable));
2359 let event_state = entry.get_mut().to_event_state();
2360 let event = Event::changed(
2361 &device_id,
2362 event_state,
2363 lookup_addr,
2364 bindings_ctx.now(),
2365 );
2366 bindings_ctx.on_event(event);
2367 None
2368 }
2369 }
2370 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable)) => {
2371 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2372 unreachable
2373 .handle_timer(core_ctx, bindings_ctx, timer_heap, &device_id, lookup_addr)
2374 .map(|probe| Action::TransmitProbe { probe, to: lookup_addr })
2375 }
2376 NeighborState::Dynamic(DynamicNeighborState::Reachable(Reachable {
2377 link_address,
2378 last_confirmed_at,
2379 })) => {
2380 assert_eq!(event, NudEvent::ReachableTime);
2381 let link_address = *link_address;
2382
2383 let expiration =
2384 last_confirmed_at.saturating_add(core_ctx.base_reachable_time().get());
2385 if expiration > bindings_ctx.now() {
2386 timer_heap.schedule_neighbor_at(
2387 bindings_ctx,
2388 expiration,
2389 lookup_addr,
2390 NudEvent::ReachableTime,
2391 );
2392 } else {
2393 *entry.get_mut() =
2401 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2402 link_address,
2403 }));
2404 let event_state = entry.get_mut().to_event_state();
2405 let event = Event::changed(
2406 &device_id,
2407 event_state,
2408 lookup_addr,
2409 bindings_ctx.now(),
2410 );
2411 bindings_ctx.on_event(event);
2412
2413 timer_heap.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
2417 }
2418
2419 None
2420 }
2421 NeighborState::Dynamic(DynamicNeighborState::Delay(delay)) => {
2422 assert_eq!(event, NudEvent::DelayFirstProbe);
2423
2424 let probe @ Probe { link_address, transmit_counter: _ } =
2431 delay.enter_probe(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2432 *entry.get_mut() = NeighborState::Dynamic(DynamicNeighborState::Probe(probe));
2433 let event_state = entry.get_mut().to_event_state();
2434 bindings_ctx.on_event(Event::changed(
2435 &device_id,
2436 event_state,
2437 lookup_addr,
2438 bindings_ctx.now(),
2439 ));
2440
2441 Some(Action::TransmitProbe {
2442 probe: TransmitProbe::Unicast(link_address),
2443 to: lookup_addr,
2444 })
2445 }
2446 state @ (NeighborState::Static(_)
2447 | NeighborState::Dynamic(DynamicNeighborState::Stale(_))) => {
2448 panic!("timer unexpectedly fired in state {state:?}")
2449 }
2450 }
2451 },
2452 );
2453
2454 match action {
2455 Some(Action::SendIcmpDestUnreachable(mut pending_frames)) => {
2456 for (mut frame, meta) in pending_frames.drain(..) {
2457 core::mem::drop(meta);
2460
2461 let Some((packet, original_src_ip, original_dst_ip)) = frame
2465 .parse_mut::<I::Packet<_>>()
2466 .map_err(|e| {
2467 warn!("not sending ICMP dest unreachable due to parsing error: {:?}", e);
2468 })
2469 .ok()
2470 .and_then(|packet| {
2471 let original_src_ip = SocketIpAddr::new(packet.src_ip())?;
2472 let original_dst_ip = SocketIpAddr::new(packet.dst_ip())?;
2473 Some((packet, original_src_ip, original_dst_ip))
2474 })
2475 .or_else(|| {
2476 core_ctx.counters().icmp_dest_unreachable_dropped.increment();
2477 None
2478 })
2479 else {
2480 continue;
2481 };
2482 let header_metadata = I::extract_metadata(&packet);
2483 let header_len = packet.parse_metadata().header_len();
2484 let proto = packet.proto();
2485 let metadata = packet.parse_metadata();
2486 core::mem::drop(packet);
2487 frame.undo_parse(metadata);
2488 core_ctx.send_icmp_dest_unreachable(
2489 bindings_ctx,
2490 frame,
2491 original_src_ip.as_ref().must_have_zone().then_some(&device_id),
2503 original_src_ip,
2504 original_dst_ip,
2505 header_len,
2506 proto,
2507 header_metadata,
2508 );
2509 }
2510 }
2511 Some(Action::TransmitProbe { probe, to }) => {
2512 let remote_link_addr = match probe {
2513 TransmitProbe::Multicast => None,
2514 TransmitProbe::Unicast(link_addr) => Some(link_addr),
2515 };
2516 core_ctx.send_neighbor_solicitation(bindings_ctx, &device_id, to, remote_link_addr);
2517 }
2518 None => {}
2519 }
2520}
2521
2522impl<I: Ip, D: LinkDevice, BC: NudBindingsContext<I, D, CC::DeviceId>, CC: NudContext<I, D, BC>>
2523 NudHandler<I, D, BC> for CC
2524{
2525 fn handle_neighbor_update(
2526 &mut self,
2527 bindings_ctx: &mut BC,
2528 device_id: &CC::DeviceId,
2529 neighbor: SpecifiedAddr<I::Addr>,
2530 source: DynamicNeighborUpdateSource<D::Address>,
2531 ) {
2532 debug!("received neighbor {:?} from {}", source, neighbor);
2533 self.with_nud_state_mut_and_sender_ctx(
2534 device_id,
2535 |NudState { neighbors, last_gc, timer_heap }, core_ctx| {
2536 let num_entries = neighbors.len();
2537 match neighbors.entry(neighbor) {
2538 Entry::Vacant(e) => match source {
2539 DynamicNeighborUpdateSource::Probe { link_address } => {
2540 insert_new_entry(
2548 bindings_ctx,
2549 device_id,
2550 e,
2551 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2552 link_address,
2553 })),
2554 );
2555
2556 timer_heap.maybe_schedule_gc(bindings_ctx, neighbors.len(), last_gc);
2559 }
2560 DynamicNeighborUpdateSource::Confirmation { .. } => {}
2569 },
2570 Entry::Occupied(e) => match e.into_mut() {
2571 NeighborState::Dynamic(e) => match source {
2572 DynamicNeighborUpdateSource::Probe { link_address } => e.handle_probe(
2573 core_ctx,
2574 bindings_ctx,
2575 timer_heap,
2576 device_id,
2577 neighbor,
2578 link_address,
2579 num_entries,
2580 last_gc,
2581 ),
2582 DynamicNeighborUpdateSource::Confirmation { link_address, flags } => e
2583 .handle_confirmation(
2584 core_ctx,
2585 bindings_ctx,
2586 timer_heap,
2587 device_id,
2588 neighbor,
2589 link_address,
2590 flags,
2591 num_entries,
2592 last_gc,
2593 ),
2594 },
2595 NeighborState::Static(_) => {}
2596 },
2597 }
2598 },
2599 );
2600 }
2601
2602 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
2603 self.with_nud_state_mut(
2604 device_id,
2605 |NudState { neighbors, last_gc: _, timer_heap }, _config| {
2606 neighbors.drain().for_each(|(neighbor, state)| {
2607 match state {
2608 NeighborState::Dynamic(mut entry) => {
2609 entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
2610 }
2611 NeighborState::Static(_) => {}
2612 }
2613 bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
2614 });
2615 },
2616 );
2617 }
2618
2619 fn send_ip_packet_to_neighbor<S>(
2620 &mut self,
2621 bindings_ctx: &mut BC,
2622 device_id: &Self::DeviceId,
2623 lookup_addr: SpecifiedAddr<I::Addr>,
2624 body: S,
2625 meta: BC::TxMetadata,
2626 ) -> Result<(), SendFrameError<S>>
2627 where
2628 S: Serializer,
2629 S::Buffer: BufferMut,
2630 {
2631 let do_multicast_solicit = self.with_nud_state_mut_and_sender_ctx(
2632 device_id,
2633 |state, core_ctx| -> Result<_, SendFrameError<S>> {
2634 let (entry, timer_heap) = state.entry_and_timer_heap(lookup_addr);
2635 match entry {
2636 Entry::Vacant(e) => {
2637 let incomplete = Incomplete::new_with_packet(
2638 core_ctx,
2639 bindings_ctx,
2640 timer_heap,
2641 lookup_addr,
2642 body,
2643 meta,
2644 )
2645 .map_err(|e| e.err_into())?;
2646 insert_new_entry(
2647 bindings_ctx,
2648 device_id,
2649 e,
2650 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)),
2651 );
2652 Ok(true)
2653 }
2654 Entry::Occupied(e) => {
2655 match e.into_mut() {
2656 NeighborState::Static(link_address) => {
2657 core_ctx.send_ip_packet_to_neighbor_link_addr(
2665 bindings_ctx,
2666 *link_address,
2667 body,
2668 meta,
2669 )?;
2670
2671 Ok(false)
2672 }
2673 NeighborState::Dynamic(e) => {
2674 let do_multicast_solicit = e.handle_packet_queued_to_send(
2675 core_ctx,
2676 bindings_ctx,
2677 timer_heap,
2678 device_id,
2679 lookup_addr,
2680 body,
2681 meta,
2682 )?;
2683
2684 Ok(do_multicast_solicit)
2685 }
2686 }
2687 }
2688 }
2689 },
2690 )?;
2691
2692 if do_multicast_solicit {
2693 self.send_neighbor_solicitation(
2694 bindings_ctx,
2695 &device_id,
2696 lookup_addr,
2697 None,
2698 );
2699 }
2700
2701 Ok(())
2702 }
2703}
2704
2705fn insert_new_entry<
2706 I: Ip,
2707 D: LinkDevice,
2708 DeviceId: DeviceIdentifier,
2709 BC: NudBindingsContext<I, D, DeviceId>,
2710>(
2711 bindings_ctx: &mut BC,
2712 device_id: &DeviceId,
2713 vacant: hash_map::VacantEntry<'_, SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2714 entry: NeighborState<D, BC>,
2715) {
2716 let lookup_addr = *vacant.key();
2717 let state = vacant.insert(entry);
2718 let event = Event::added(device_id, state.to_event_state(), lookup_addr, bindings_ctx.now());
2719 bindings_ctx.on_event(event);
2720}
2721
2722pub fn confirm_reachable<I, D, CC, BC>(
2725 core_ctx: &mut CC,
2726 bindings_ctx: &mut BC,
2727 device_id: &CC::DeviceId,
2728 neighbor: SpecifiedAddr<I::Addr>,
2729) where
2730 I: Ip,
2731 D: LinkDevice,
2732 BC: NudBindingsContext<I, D, CC::DeviceId>,
2733 CC: NudContext<I, D, BC>,
2734{
2735 core_ctx.with_nud_state_mut_and_sender_ctx(
2736 device_id,
2737 |NudState { neighbors, last_gc: _, timer_heap }, core_ctx| {
2738 match neighbors.entry(neighbor) {
2739 Entry::Vacant(_) => {
2740 debug!(
2741 "got an upper-layer confirmation for non-existent neighbor entry {}",
2742 neighbor
2743 );
2744 }
2745 Entry::Occupied(e) => match e.into_mut() {
2746 NeighborState::Static(_) => {}
2747 NeighborState::Dynamic(e) => {
2748 let link_address = match e {
2758 DynamicNeighborState::Incomplete(_) => return,
2759 DynamicNeighborState::Reachable(Reachable {
2760 link_address,
2761 last_confirmed_at: _,
2762 })
2763 | DynamicNeighborState::Stale(Stale { link_address })
2764 | DynamicNeighborState::Delay(Delay { link_address })
2765 | DynamicNeighborState::Probe(Probe {
2766 link_address,
2767 transmit_counter: _,
2768 })
2769 | DynamicNeighborState::Unreachable(Unreachable {
2770 link_address,
2771 mode: _,
2772 }) => *link_address,
2773 };
2774 e.enter_reachable(
2775 core_ctx,
2776 bindings_ctx,
2777 timer_heap,
2778 device_id,
2779 neighbor,
2780 link_address,
2781 );
2782 }
2783 },
2784 }
2785 },
2786 );
2787}
2788
2789fn collect_garbage<I, D, CC, BC>(core_ctx: &mut CC, bindings_ctx: &mut BC, device_id: CC::DeviceId)
2800where
2801 I: Ip,
2802 D: LinkDevice,
2803 BC: NudBindingsContext<I, D, CC::DeviceId>,
2804 CC: NudContext<I, D, BC>,
2805{
2806 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, last_gc, timer_heap }, _| {
2807 let max_to_remove = neighbors.len().saturating_sub(MAX_ENTRIES);
2808 if max_to_remove == 0 {
2809 return;
2810 }
2811
2812 *last_gc = Some(bindings_ctx.now());
2813
2814 fn gc_priority<D: LinkDevice, BT: NudBindingsTypes<D>>(
2823 state: &DynamicNeighborState<D, BT>,
2824 ) -> usize {
2825 match state {
2826 DynamicNeighborState::Incomplete(_)
2827 | DynamicNeighborState::Reachable(_)
2828 | DynamicNeighborState::Delay(_)
2829 | DynamicNeighborState::Probe(_) => unreachable!(
2830 "the netstack should only ever discard STALE or UNREACHABLE entries; \
2831 found {:?}",
2832 state,
2833 ),
2834 DynamicNeighborState::Stale(_) => 0,
2835 DynamicNeighborState::Unreachable(Unreachable {
2836 link_address: _,
2837 mode: UnreachableMode::Backoff { probes_sent: _, packet_sent: _ },
2838 }) => 1,
2839 DynamicNeighborState::Unreachable(Unreachable {
2840 link_address: _,
2841 mode: UnreachableMode::WaitingForPacketSend,
2842 }) => 2,
2843 }
2844 }
2845
2846 struct SortEntry<'a, K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> {
2847 key: K,
2848 state: &'a mut DynamicNeighborState<D, BT>,
2849 }
2850
2851 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialEq for SortEntry<'_, K, D, BT> {
2852 fn eq(&self, other: &Self) -> bool {
2853 self.key == other.key && gc_priority(self.state) == gc_priority(other.state)
2854 }
2855 }
2856 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Eq for SortEntry<'_, K, D, BT> {}
2857 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Ord for SortEntry<'_, K, D, BT> {
2858 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2859 gc_priority(self.state).cmp(&gc_priority(other.state)).reverse()
2863 }
2864 }
2865 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialOrd for SortEntry<'_, K, D, BT> {
2866 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
2867 Some(self.cmp(&other))
2868 }
2869 }
2870
2871 let mut entries_to_remove = BinaryHeap::with_capacity(max_to_remove);
2872 for (ip, neighbor) in neighbors.iter_mut() {
2873 match neighbor {
2874 NeighborState::Static(_) => {
2875 continue;
2877 }
2878 NeighborState::Dynamic(state) => {
2879 match state {
2880 DynamicNeighborState::Incomplete(_)
2881 | DynamicNeighborState::Reachable(_)
2882 | DynamicNeighborState::Delay(_)
2883 | DynamicNeighborState::Probe(_) => {
2884 continue;
2886 }
2887 DynamicNeighborState::Stale(_) | DynamicNeighborState::Unreachable(_) => {
2888 if entries_to_remove.len() < max_to_remove {
2890 entries_to_remove.push(SortEntry { key: ip, state });
2891 continue;
2892 }
2893 let minimum = entries_to_remove
2897 .peek()
2898 .expect("heap should have at least 1 entry");
2899 let candidate = SortEntry { key: ip, state };
2900 if &candidate > minimum {
2901 let _: SortEntry<'_, _, _, _> = entries_to_remove.pop().unwrap();
2902 entries_to_remove.push(candidate);
2903 }
2904 }
2905 }
2906 }
2907 }
2908 }
2909
2910 let entries_to_remove = entries_to_remove
2911 .into_iter()
2912 .map(|SortEntry { key: neighbor, state }| {
2913 state.cancel_timer(bindings_ctx, timer_heap, *neighbor);
2914 *neighbor
2915 })
2916 .collect::<Vec<_>>();
2917
2918 for neighbor in entries_to_remove {
2919 assert_matches!(neighbors.remove(&neighbor), Some(_));
2920 bindings_ctx.on_event(Event::removed(&device_id, neighbor, bindings_ctx.now()));
2921 }
2922 })
2923}
2924
2925#[cfg(test)]
2926mod tests {
2927 use alloc::vec;
2928
2929 use ip_test_macro::ip_test;
2930 use net_declare::{net_ip_v4, net_ip_v6};
2931 use net_types::ip::{Ipv4Addr, Ipv6Addr};
2932 use netstack3_base::testutil::{
2933 FakeBindingsCtx, FakeCoreCtx, FakeInstant, FakeLinkAddress, FakeLinkDevice,
2934 FakeLinkDeviceId, FakeTimerCtxExt as _, FakeTxMetadata, FakeWeakDeviceId,
2935 };
2936 use netstack3_base::{
2937 CtxPair, InstantContext, IntoCoreTimerCtx, SendFrameContext as _, SendFrameErrorReason,
2938 };
2939 use netstack3_hashmap::HashSet;
2940 use test_case::test_case;
2941
2942 use super::*;
2943 use crate::internal::device::nud::api::NeighborApi;
2944
2945 struct FakeNudContext<I: Ip, D: LinkDevice> {
2946 state: NudState<I, D, FakeBindingsCtxImpl<I>>,
2947 counters: NudCounters<I>,
2948 }
2949
2950 struct FakeConfigContext {
2951 retrans_timer: NonZeroDuration,
2952 nud_config: NudUserConfig,
2953 }
2954
2955 struct FakeCoreCtxImpl<I: Ip> {
2956 nud: FakeNudContext<I, FakeLinkDevice>,
2957 inner: FakeInnerCtxImpl<I>,
2958 }
2959
2960 type FakeInnerCtxImpl<I> =
2961 FakeCoreCtx<FakeConfigContext, FakeNudMessageMeta<I>, FakeLinkDeviceId>;
2962
2963 #[derive(Debug, PartialEq, Eq)]
2964 enum FakeNudMessageMeta<I: Ip> {
2965 NeighborSolicitation {
2966 lookup_addr: SpecifiedAddr<I::Addr>,
2967 remote_link_addr: Option<FakeLinkAddress>,
2968 },
2969 IpFrame {
2970 dst_link_address: FakeLinkAddress,
2971 },
2972 IcmpDestUnreachable,
2973 }
2974
2975 type FakeBindingsCtxImpl<I> = FakeBindingsCtx<
2976 NudTimerId<I, FakeLinkDevice, FakeWeakDeviceId<FakeLinkDeviceId>>,
2977 Event<FakeLinkAddress, FakeLinkDeviceId, I, FakeInstant>,
2978 (),
2979 (),
2980 >;
2981
2982 impl<I: Ip> FakeCoreCtxImpl<I> {
2983 fn new(bindings_ctx: &mut FakeBindingsCtxImpl<I>) -> Self {
2984 Self {
2985 nud: {
2986 FakeNudContext {
2987 state: NudState::new::<_, IntoCoreTimerCtx>(
2988 bindings_ctx,
2989 FakeWeakDeviceId(FakeLinkDeviceId),
2990 ),
2991 counters: Default::default(),
2992 }
2993 },
2994 inner: FakeInnerCtxImpl::with_state(FakeConfigContext {
2995 retrans_timer: ONE_SECOND,
2996 nud_config: NudUserConfig {
3000 max_unicast_solicitations: NonZeroU16::new(4).unwrap(),
3001 max_multicast_solicitations: NonZeroU16::new(5).unwrap(),
3002 base_reachable_time: NonZeroDuration::from_secs(23).unwrap(),
3003 retrans_timer: NonZeroDuration::from_secs(3).unwrap(),
3004 },
3005 }),
3006 }
3007 }
3008 }
3009
3010 fn new_context<I: Ip>() -> CtxPair<FakeCoreCtxImpl<I>, FakeBindingsCtxImpl<I>> {
3011 CtxPair::with_default_bindings_ctx(|bindings_ctx| FakeCoreCtxImpl::<I>::new(bindings_ctx))
3012 }
3013
3014 impl<I: Ip> DeviceIdContext<FakeLinkDevice> for FakeCoreCtxImpl<I> {
3015 type DeviceId = FakeLinkDeviceId;
3016 type WeakDeviceId = FakeWeakDeviceId<FakeLinkDeviceId>;
3017 }
3018
3019 impl<I: Ip> NudContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeCoreCtxImpl<I> {
3020 type ConfigCtx<'a> = FakeConfigContext;
3021
3022 type SenderCtx<'a> = FakeInnerCtxImpl<I>;
3023
3024 fn with_nud_state_mut_and_sender_ctx<
3025 O,
3026 F: FnOnce(
3027 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3028 &mut Self::SenderCtx<'_>,
3029 ) -> O,
3030 >(
3031 &mut self,
3032 _device_id: &Self::DeviceId,
3033 cb: F,
3034 ) -> O {
3035 cb(&mut self.nud.state, &mut self.inner)
3036 }
3037
3038 fn with_nud_state_mut<
3039 O,
3040 F: FnOnce(
3041 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3042 &mut Self::ConfigCtx<'_>,
3043 ) -> O,
3044 >(
3045 &mut self,
3046 &FakeLinkDeviceId: &FakeLinkDeviceId,
3047 cb: F,
3048 ) -> O {
3049 cb(&mut self.nud.state, &mut self.inner.state)
3050 }
3051
3052 fn with_nud_state<
3053 O,
3054 F: FnOnce(&NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>) -> O,
3055 >(
3056 &mut self,
3057 &FakeLinkDeviceId: &FakeLinkDeviceId,
3058 cb: F,
3059 ) -> O {
3060 cb(&self.nud.state)
3061 }
3062
3063 fn send_neighbor_solicitation(
3064 &mut self,
3065 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3066 &FakeLinkDeviceId: &FakeLinkDeviceId,
3067 lookup_addr: SpecifiedAddr<I::Addr>,
3068 remote_link_addr: Option<FakeLinkAddress>,
3069 ) {
3070 self.inner
3071 .send_frame(
3072 bindings_ctx,
3073 FakeNudMessageMeta::NeighborSolicitation { lookup_addr, remote_link_addr },
3074 Buf::new(Vec::new(), ..),
3075 )
3076 .unwrap()
3077 }
3078 }
3079
3080 impl<I: NudIcmpIpExt> NudIcmpContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>
3081 for FakeCoreCtxImpl<I>
3082 {
3083 fn send_icmp_dest_unreachable(
3084 &mut self,
3085 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3086 frame: Buf<Vec<u8>>,
3087 _device_id: Option<&Self::DeviceId>,
3088 _original_src_ip: SocketIpAddr<I::Addr>,
3089 _original_dst_ip: SocketIpAddr<I::Addr>,
3090 _header_len: usize,
3091 _proto: I::Proto,
3092 _metadata: I::Metadata,
3093 ) {
3094 self.inner
3095 .send_frame(bindings_ctx, FakeNudMessageMeta::IcmpDestUnreachable, frame)
3096 .unwrap()
3097 }
3098 }
3099
3100 impl<I: Ip> CounterContext<NudCounters<I>> for FakeCoreCtxImpl<I> {
3101 fn counters(&self) -> &NudCounters<I> {
3102 &self.nud.counters
3103 }
3104 }
3105
3106 impl<I: Ip> NudConfigContext<I> for FakeConfigContext {
3107 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3108 self.retrans_timer
3109 }
3110
3111 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3112 cb(&self.nud_config)
3113 }
3114 }
3115
3116 impl<I: Ip> NudSenderContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeInnerCtxImpl<I> {
3117 fn send_ip_packet_to_neighbor_link_addr<S>(
3118 &mut self,
3119 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3120 dst_link_address: FakeLinkAddress,
3121 body: S,
3122 _tx_meta: FakeTxMetadata,
3123 ) -> Result<(), SendFrameError<S>>
3124 where
3125 S: Serializer,
3126 S::Buffer: BufferMut,
3127 {
3128 self.send_frame(bindings_ctx, FakeNudMessageMeta::IpFrame { dst_link_address }, body)
3129 }
3130 }
3131
3132 impl<I: Ip> NudConfigContext<I> for FakeInnerCtxImpl<I> {
3133 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3134 <FakeConfigContext as NudConfigContext<I>>::retransmit_timeout(&mut self.state)
3135 }
3136
3137 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3138 <FakeConfigContext as NudConfigContext<I>>::with_nud_user_config(&mut self.state, cb)
3139 }
3140 }
3141
3142 const ONE_SECOND: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
3143
3144 #[track_caller]
3145 fn check_lookup_has<I: Ip>(
3146 core_ctx: &mut FakeCoreCtxImpl<I>,
3147 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3148 lookup_addr: SpecifiedAddr<I::Addr>,
3149 expected_link_addr: FakeLinkAddress,
3150 ) {
3151 let entry = assert_matches!(
3152 core_ctx.nud.state.neighbors.get(&lookup_addr),
3153 Some(entry @ (
3154 NeighborState::Dynamic(
3155 DynamicNeighborState::Reachable (Reachable { link_address, last_confirmed_at: _ })
3156 | DynamicNeighborState::Stale (Stale { link_address })
3157 | DynamicNeighborState::Delay (Delay { link_address })
3158 | DynamicNeighborState::Probe (Probe { link_address, transmit_counter: _ })
3159 | DynamicNeighborState::Unreachable (Unreachable { link_address, mode: _ })
3160 )
3161 | NeighborState::Static(link_address)
3162 )) => {
3163 assert_eq!(link_address, &expected_link_addr);
3164 entry
3165 }
3166 );
3167 match entry {
3168 NeighborState::Dynamic(DynamicNeighborState::Incomplete { .. }) => {
3169 unreachable!("entry must be static, REACHABLE, or STALE")
3170 }
3171 NeighborState::Dynamic(DynamicNeighborState::Reachable { .. }) => {
3172 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3173 bindings_ctx,
3174 [(
3175 lookup_addr,
3176 NudEvent::ReachableTime,
3177 core_ctx.inner.base_reachable_time().get(),
3178 )],
3179 );
3180 }
3181 NeighborState::Dynamic(DynamicNeighborState::Delay { .. }) => {
3182 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3183 bindings_ctx,
3184 [(lookup_addr, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
3185 );
3186 }
3187 NeighborState::Dynamic(DynamicNeighborState::Probe { .. }) => {
3188 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3189 bindings_ctx,
3190 [(
3191 lookup_addr,
3192 NudEvent::RetransmitUnicastProbe,
3193 core_ctx.inner.state.retrans_timer.get(),
3194 )],
3195 );
3196 }
3197 NeighborState::Dynamic(DynamicNeighborState::Unreachable(Unreachable {
3198 link_address: _,
3199 mode,
3200 })) => {
3201 let instant = match mode {
3202 UnreachableMode::WaitingForPacketSend => None,
3203 mode @ UnreachableMode::Backoff { .. } => {
3204 let duration =
3205 mode.next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state);
3206 Some(bindings_ctx.now() + duration.get())
3207 }
3208 };
3209 if let Some(instant) = instant {
3210 core_ctx.nud.state.timer_heap.neighbor.assert_timers([(
3211 lookup_addr,
3212 NudEvent::RetransmitUnicastProbe,
3213 instant,
3214 )]);
3215 }
3216 }
3217 NeighborState::Dynamic(DynamicNeighborState::Stale { .. })
3218 | NeighborState::Static(_) => bindings_ctx.timers.assert_no_timers_installed(),
3219 }
3220 }
3221
3222 trait TestIpExt: NudIcmpIpExt {
3223 const LOOKUP_ADDR1: SpecifiedAddr<Self::Addr>;
3224 const LOOKUP_ADDR2: SpecifiedAddr<Self::Addr>;
3225 const LOOKUP_ADDR3: SpecifiedAddr<Self::Addr>;
3226 }
3227
3228 impl TestIpExt for Ipv4 {
3229 const LOOKUP_ADDR1: SpecifiedAddr<Ipv4Addr> =
3231 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.1")) };
3232 const LOOKUP_ADDR2: SpecifiedAddr<Ipv4Addr> =
3233 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.2")) };
3234 const LOOKUP_ADDR3: SpecifiedAddr<Ipv4Addr> =
3235 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.3")) };
3236 }
3237
3238 impl TestIpExt for Ipv6 {
3239 const LOOKUP_ADDR1: SpecifiedAddr<Ipv6Addr> =
3241 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::1")) };
3242 const LOOKUP_ADDR2: SpecifiedAddr<Ipv6Addr> =
3243 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::2")) };
3244 const LOOKUP_ADDR3: SpecifiedAddr<Ipv6Addr> =
3245 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::3")) };
3246 }
3247
3248 const LINK_ADDR1: FakeLinkAddress = FakeLinkAddress([1]);
3249 const LINK_ADDR2: FakeLinkAddress = FakeLinkAddress([2]);
3250 const LINK_ADDR3: FakeLinkAddress = FakeLinkAddress([3]);
3251
3252 impl<I: Ip, L: LinkDevice> NudTimerId<I, L, FakeWeakDeviceId<FakeLinkDeviceId>> {
3253 fn neighbor() -> Self {
3254 Self {
3255 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3256 timer_type: NudTimerType::Neighbor,
3257 _marker: PhantomData,
3258 }
3259 }
3260
3261 fn garbage_collection() -> Self {
3262 Self {
3263 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3264 timer_type: NudTimerType::GarbageCollection,
3265 _marker: PhantomData,
3266 }
3267 }
3268 }
3269
3270 fn queue_ip_packet_to_unresolved_neighbor<I: TestIpExt>(
3271 core_ctx: &mut FakeCoreCtxImpl<I>,
3272 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3273 neighbor: SpecifiedAddr<I::Addr>,
3274 pending_frames: &mut VecDeque<Buf<Vec<u8>>>,
3275 body: u8,
3276 expect_event: bool,
3277 ) {
3278 let body = [body];
3279 assert_eq!(
3280 NudHandler::send_ip_packet_to_neighbor(
3281 core_ctx,
3282 bindings_ctx,
3283 &FakeLinkDeviceId,
3284 neighbor,
3285 Buf::new(body, ..),
3286 FakeTxMetadata::default(),
3287 ),
3288 Ok(())
3289 );
3290
3291 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
3292
3293 pending_frames.push_back(Buf::new(body.to_vec(), ..));
3294
3295 assert_neighbor_state_with_ip(
3296 core_ctx,
3297 bindings_ctx,
3298 neighbor,
3299 DynamicNeighborState::Incomplete(Incomplete {
3300 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
3301 pending_frames: pending_frames
3302 .iter()
3303 .cloned()
3304 .map(|buf| (buf, FakeTxMetadata::default()))
3305 .collect(),
3306 notifiers: Vec::new(),
3307 _marker: PhantomData,
3308 }),
3309 expect_event.then_some(ExpectedEvent::Added),
3310 );
3311
3312 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3313 bindings_ctx,
3314 [(neighbor, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
3315 );
3316 }
3317
3318 fn init_incomplete_neighbor_with_ip<I: TestIpExt>(
3319 core_ctx: &mut FakeCoreCtxImpl<I>,
3320 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3321 ip_address: SpecifiedAddr<I::Addr>,
3322 take_probe: bool,
3323 ) -> VecDeque<Buf<Vec<u8>>> {
3324 let mut pending_frames = VecDeque::new();
3325 queue_ip_packet_to_unresolved_neighbor(
3326 core_ctx,
3327 bindings_ctx,
3328 ip_address,
3329 &mut pending_frames,
3330 1,
3331 true, );
3333 if take_probe {
3334 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, None);
3335 }
3336 pending_frames
3337 }
3338
3339 fn init_incomplete_neighbor<I: TestIpExt>(
3340 core_ctx: &mut FakeCoreCtxImpl<I>,
3341 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3342 take_probe: bool,
3343 ) -> VecDeque<Buf<Vec<u8>>> {
3344 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, take_probe)
3345 }
3346
3347 fn init_stale_neighbor_with_ip<I: TestIpExt>(
3348 core_ctx: &mut FakeCoreCtxImpl<I>,
3349 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3350 ip_address: SpecifiedAddr<I::Addr>,
3351 link_address: FakeLinkAddress,
3352 ) {
3353 NudHandler::handle_neighbor_update(
3354 core_ctx,
3355 bindings_ctx,
3356 &FakeLinkDeviceId,
3357 ip_address,
3358 DynamicNeighborUpdateSource::Probe { link_address },
3359 );
3360 assert_neighbor_state_with_ip(
3361 core_ctx,
3362 bindings_ctx,
3363 ip_address,
3364 DynamicNeighborState::Stale(Stale { link_address }),
3365 Some(ExpectedEvent::Added),
3366 );
3367 }
3368
3369 fn init_stale_neighbor<I: TestIpExt>(
3370 core_ctx: &mut FakeCoreCtxImpl<I>,
3371 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3372 link_address: FakeLinkAddress,
3373 ) {
3374 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3375 }
3376
3377 fn init_reachable_neighbor_with_ip<I: TestIpExt>(
3378 core_ctx: &mut FakeCoreCtxImpl<I>,
3379 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3380 ip_address: SpecifiedAddr<I::Addr>,
3381 link_address: FakeLinkAddress,
3382 ) {
3383 let queued_frame =
3384 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, true);
3385 NudHandler::handle_neighbor_update(
3386 core_ctx,
3387 bindings_ctx,
3388 &FakeLinkDeviceId,
3389 ip_address,
3390 DynamicNeighborUpdateSource::Confirmation {
3391 link_address: Some(link_address),
3392 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
3393 },
3394 );
3395 assert_neighbor_state_with_ip(
3396 core_ctx,
3397 bindings_ctx,
3398 ip_address,
3399 DynamicNeighborState::Reachable(Reachable {
3400 link_address,
3401 last_confirmed_at: bindings_ctx.now(),
3402 }),
3403 Some(ExpectedEvent::Changed),
3404 );
3405 assert_pending_frame_sent(core_ctx, queued_frame, link_address);
3406 }
3407
3408 fn init_reachable_neighbor<I: TestIpExt>(
3409 core_ctx: &mut FakeCoreCtxImpl<I>,
3410 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3411 link_address: FakeLinkAddress,
3412 ) {
3413 init_reachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3414 }
3415
3416 fn init_delay_neighbor_with_ip<I: TestIpExt>(
3417 core_ctx: &mut FakeCoreCtxImpl<I>,
3418 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3419 ip_address: SpecifiedAddr<I::Addr>,
3420 link_address: FakeLinkAddress,
3421 ) {
3422 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3423 assert_eq!(
3424 NudHandler::send_ip_packet_to_neighbor(
3425 core_ctx,
3426 bindings_ctx,
3427 &FakeLinkDeviceId,
3428 ip_address,
3429 Buf::new([1], ..),
3430 FakeTxMetadata::default(),
3431 ),
3432 Ok(())
3433 );
3434 assert_neighbor_state_with_ip(
3435 core_ctx,
3436 bindings_ctx,
3437 ip_address,
3438 DynamicNeighborState::Delay(Delay { link_address }),
3439 Some(ExpectedEvent::Changed),
3440 );
3441 assert_eq!(
3442 core_ctx.inner.take_frames(),
3443 vec![(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![1])],
3444 );
3445 }
3446
3447 fn init_delay_neighbor<I: TestIpExt>(
3448 core_ctx: &mut FakeCoreCtxImpl<I>,
3449 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3450 link_address: FakeLinkAddress,
3451 ) {
3452 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3453 }
3454
3455 fn init_probe_neighbor_with_ip<I: TestIpExt>(
3456 core_ctx: &mut FakeCoreCtxImpl<I>,
3457 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3458 ip_address: SpecifiedAddr<I::Addr>,
3459 link_address: FakeLinkAddress,
3460 take_probe: bool,
3461 ) {
3462 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3463 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3464 core_ctx.nud.state.timer_heap.neighbor.assert_top(&ip_address, &NudEvent::DelayFirstProbe);
3465 assert_eq!(
3466 bindings_ctx.trigger_timers_for(DELAY_FIRST_PROBE_TIME.into(), core_ctx),
3467 [NudTimerId::neighbor()]
3468 );
3469 assert_neighbor_state_with_ip(
3470 core_ctx,
3471 bindings_ctx,
3472 ip_address,
3473 DynamicNeighborState::Probe(Probe {
3474 link_address,
3475 transmit_counter: NonZeroU16::new(max_unicast_solicit - 1),
3476 }),
3477 Some(ExpectedEvent::Changed),
3478 );
3479 if take_probe {
3480 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3481 }
3482 }
3483
3484 fn init_probe_neighbor<I: TestIpExt>(
3485 core_ctx: &mut FakeCoreCtxImpl<I>,
3486 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3487 link_address: FakeLinkAddress,
3488 take_probe: bool,
3489 ) {
3490 init_probe_neighbor_with_ip(
3491 core_ctx,
3492 bindings_ctx,
3493 I::LOOKUP_ADDR1,
3494 link_address,
3495 take_probe,
3496 );
3497 }
3498
3499 fn init_unreachable_neighbor_with_ip<I: TestIpExt>(
3500 core_ctx: &mut FakeCoreCtxImpl<I>,
3501 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3502 ip_address: SpecifiedAddr<I::Addr>,
3503 link_address: FakeLinkAddress,
3504 ) {
3505 init_probe_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address, false);
3506 let retransmit_timeout = core_ctx.inner.retransmit_timeout();
3507 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3508 for _ in 0..max_unicast_solicit {
3509 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3510 assert_eq!(
3511 bindings_ctx.trigger_timers_for(retransmit_timeout.into(), core_ctx),
3512 [NudTimerId::neighbor()]
3513 );
3514 }
3515 assert_neighbor_state_with_ip(
3516 core_ctx,
3517 bindings_ctx,
3518 ip_address,
3519 DynamicNeighborState::Unreachable(Unreachable {
3520 link_address,
3521 mode: UnreachableMode::WaitingForPacketSend,
3522 }),
3523 Some(ExpectedEvent::Changed),
3524 );
3525 }
3526
3527 fn init_unreachable_neighbor<I: TestIpExt>(
3528 core_ctx: &mut FakeCoreCtxImpl<I>,
3529 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3530 link_address: FakeLinkAddress,
3531 ) {
3532 init_unreachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3533 }
3534
3535 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
3536 enum InitialState {
3537 Incomplete,
3538 Stale,
3539 Reachable,
3540 Delay,
3541 Probe,
3542 Unreachable,
3543 }
3544
3545 fn init_neighbor_in_state<I: TestIpExt>(
3546 core_ctx: &mut FakeCoreCtxImpl<I>,
3547 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3548 state: InitialState,
3549 ) -> DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>> {
3550 match state {
3551 InitialState::Incomplete => {
3552 let _: VecDeque<Buf<Vec<u8>>> =
3553 init_incomplete_neighbor(core_ctx, bindings_ctx, true);
3554 }
3555 InitialState::Reachable => {
3556 init_reachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3557 }
3558 InitialState::Stale => {
3559 init_stale_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3560 }
3561 InitialState::Delay => {
3562 init_delay_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3563 }
3564 InitialState::Probe => {
3565 init_probe_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, true);
3566 }
3567 InitialState::Unreachable => {
3568 init_unreachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3569 }
3570 }
3571 assert_matches!(core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
3572 Some(NeighborState::Dynamic(state)) => state.clone()
3573 )
3574 }
3575
3576 #[track_caller]
3577 fn init_static_neighbor_with_ip<I: TestIpExt>(
3578 core_ctx: &mut FakeCoreCtxImpl<I>,
3579 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3580 ip_address: SpecifiedAddr<I::Addr>,
3581 link_address: FakeLinkAddress,
3582 expected_event: ExpectedEvent,
3583 ) {
3584 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3585 NeighborApi::new(&mut ctx)
3586 .insert_static_entry(&FakeLinkDeviceId, *ip_address, link_address)
3587 .unwrap();
3588 assert_eq!(
3589 ctx.bindings_ctx.take_events(),
3590 [Event {
3591 device: FakeLinkDeviceId,
3592 addr: ip_address,
3593 kind: match expected_event {
3594 ExpectedEvent::Added => EventKind::Added(EventState::Static(link_address)),
3595 ExpectedEvent::Changed => EventKind::Changed(EventState::Static(link_address)),
3596 },
3597 at: ctx.bindings_ctx.now(),
3598 }],
3599 );
3600 }
3601
3602 #[track_caller]
3603 fn init_static_neighbor<I: TestIpExt>(
3604 core_ctx: &mut FakeCoreCtxImpl<I>,
3605 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3606 link_address: FakeLinkAddress,
3607 expected_event: ExpectedEvent,
3608 ) {
3609 init_static_neighbor_with_ip(
3610 core_ctx,
3611 bindings_ctx,
3612 I::LOOKUP_ADDR1,
3613 link_address,
3614 expected_event,
3615 );
3616 }
3617
3618 #[track_caller]
3619 fn delete_neighbor<I: TestIpExt>(
3620 core_ctx: &mut FakeCoreCtxImpl<I>,
3621 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3622 ) {
3623 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3624 NeighborApi::new(&mut ctx)
3625 .remove_entry(&FakeLinkDeviceId, *I::LOOKUP_ADDR1)
3626 .expect("neighbor entry should exist");
3627 assert_eq!(
3628 ctx.bindings_ctx.take_events(),
3629 [Event::removed(&FakeLinkDeviceId, I::LOOKUP_ADDR1, ctx.bindings_ctx.now())],
3630 );
3631 }
3632
3633 #[track_caller]
3634 fn assert_neighbor_state<I: TestIpExt>(
3635 core_ctx: &FakeCoreCtxImpl<I>,
3636 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3637 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3638 event_kind: Option<ExpectedEvent>,
3639 ) {
3640 assert_neighbor_state_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, state, event_kind);
3641 }
3642
3643 #[derive(Clone, Copy, Debug)]
3644 enum ExpectedEvent {
3645 Added,
3646 Changed,
3647 }
3648
3649 #[track_caller]
3650 fn assert_neighbor_state_with_ip<I: TestIpExt>(
3651 core_ctx: &FakeCoreCtxImpl<I>,
3652 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3653 neighbor: SpecifiedAddr<I::Addr>,
3654 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3655 expected_event: Option<ExpectedEvent>,
3656 ) {
3657 if let Some(expected_event) = expected_event {
3658 let event_state = EventState::Dynamic(state.to_event_dynamic_state());
3659 assert_eq!(
3660 bindings_ctx.take_events(),
3661 [Event {
3662 device: FakeLinkDeviceId,
3663 addr: neighbor,
3664 kind: match expected_event {
3665 ExpectedEvent::Added => EventKind::Added(event_state),
3666 ExpectedEvent::Changed => EventKind::Changed(event_state),
3667 },
3668 at: bindings_ctx.now(),
3669 }],
3670 );
3671 }
3672
3673 assert_eq!(
3674 core_ctx.nud.state.neighbors.get(&neighbor),
3675 Some(&NeighborState::Dynamic(state))
3676 );
3677 }
3678
3679 #[track_caller]
3680 fn assert_pending_frame_sent<I: TestIpExt>(
3681 core_ctx: &mut FakeCoreCtxImpl<I>,
3682 pending_frames: VecDeque<Buf<Vec<u8>>>,
3683 link_address: FakeLinkAddress,
3684 ) {
3685 assert_eq!(
3686 core_ctx.inner.take_frames(),
3687 pending_frames
3688 .into_iter()
3689 .map(|f| (
3690 FakeNudMessageMeta::IpFrame { dst_link_address: link_address },
3691 f.as_ref().to_vec(),
3692 ))
3693 .collect::<Vec<_>>()
3694 );
3695 }
3696
3697 #[track_caller]
3698 fn assert_neighbor_probe_sent_for_ip<I: TestIpExt>(
3699 core_ctx: &mut FakeCoreCtxImpl<I>,
3700 ip_address: SpecifiedAddr<I::Addr>,
3701 link_address: Option<FakeLinkAddress>,
3702 ) {
3703 assert_eq!(
3704 core_ctx.inner.take_frames(),
3705 [(
3706 FakeNudMessageMeta::NeighborSolicitation {
3707 lookup_addr: ip_address,
3708 remote_link_addr: link_address,
3709 },
3710 Vec::new()
3711 )]
3712 );
3713 }
3714
3715 #[track_caller]
3716 fn assert_neighbor_probe_sent<I: TestIpExt>(
3717 core_ctx: &mut FakeCoreCtxImpl<I>,
3718 link_address: Option<FakeLinkAddress>,
3719 ) {
3720 assert_neighbor_probe_sent_for_ip(core_ctx, I::LOOKUP_ADDR1, link_address);
3721 }
3722
3723 #[track_caller]
3724 fn assert_neighbor_removed_with_ip<I: TestIpExt>(
3725 core_ctx: &mut FakeCoreCtxImpl<I>,
3726 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3727 neighbor: SpecifiedAddr<I::Addr>,
3728 ) {
3729 super::testutil::assert_neighbor_unknown(core_ctx, FakeLinkDeviceId, neighbor);
3730 assert_eq!(
3731 bindings_ctx.take_events(),
3732 [Event::removed(&FakeLinkDeviceId, neighbor, bindings_ctx.now())],
3733 );
3734 }
3735
3736 #[ip_test(I)]
3737 fn serialization_failure_doesnt_schedule_timer<I: TestIpExt>() {
3738 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3739
3740 let packet = Buf::new([0; 2], ..).with_size_limit(1);
3743
3744 let err = assert_matches!(
3745 NudHandler::send_ip_packet_to_neighbor(
3746 &mut core_ctx,
3747 &mut bindings_ctx,
3748 &FakeLinkDeviceId,
3749 I::LOOKUP_ADDR1,
3750 packet,
3751 FakeTxMetadata::default(),
3752 ),
3753 Err(ErrorAndSerializer { error, serializer: _ }) => error
3754 );
3755 assert_eq!(err, SendFrameErrorReason::SizeConstraintsViolation);
3756
3757 super::testutil::assert_neighbor_unknown(&mut core_ctx, FakeLinkDeviceId, I::LOOKUP_ADDR1);
3760 assert_eq!(core_ctx.inner.take_frames(), []);
3761 bindings_ctx.timers.assert_no_timers_installed();
3762 }
3763
3764 #[ip_test(I)]
3765 fn incomplete_to_stale_on_probe<I: TestIpExt>() {
3766 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3767
3768 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3770
3771 NudHandler::handle_neighbor_update(
3773 &mut core_ctx,
3774 &mut bindings_ctx,
3775 &FakeLinkDeviceId,
3776 I::LOOKUP_ADDR1,
3777 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
3778 );
3779
3780 assert_neighbor_state(
3782 &core_ctx,
3783 &mut bindings_ctx,
3784 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3785 Some(ExpectedEvent::Changed),
3786 );
3787 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3788 }
3789
3790 #[ip_test(I)]
3791 #[test_case(true, true; "solicited override")]
3792 #[test_case(true, false; "solicited non-override")]
3793 #[test_case(false, true; "unsolicited override")]
3794 #[test_case(false, false; "unsolicited non-override")]
3795 fn incomplete_on_confirmation<I: TestIpExt>(solicited_flag: bool, override_flag: bool) {
3796 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3797
3798 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3800
3801 NudHandler::handle_neighbor_update(
3803 &mut core_ctx,
3804 &mut bindings_ctx,
3805 &FakeLinkDeviceId,
3806 I::LOOKUP_ADDR1,
3807 DynamicNeighborUpdateSource::Confirmation {
3808 link_address: Some(LINK_ADDR1),
3809 flags: ConfirmationFlags { solicited_flag, override_flag },
3810 },
3811 );
3812
3813 let expected_state = if solicited_flag {
3814 DynamicNeighborState::Reachable(Reachable {
3815 link_address: LINK_ADDR1,
3816 last_confirmed_at: bindings_ctx.now(),
3817 })
3818 } else {
3819 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 })
3820 };
3821 assert_neighbor_state(
3822 &core_ctx,
3823 &mut bindings_ctx,
3824 expected_state,
3825 Some(ExpectedEvent::Changed),
3826 );
3827 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3828 }
3829
3830 #[ip_test(I)]
3831 fn reachable_to_stale_on_timeout<I: TestIpExt>() {
3832 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3833
3834 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
3836
3837 assert_eq!(
3839 bindings_ctx
3840 .trigger_timers_for(core_ctx.inner.base_reachable_time().into(), &mut core_ctx,),
3841 [NudTimerId::neighbor()]
3842 );
3843 assert_neighbor_state(
3844 &core_ctx,
3845 &mut bindings_ctx,
3846 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3847 Some(ExpectedEvent::Changed),
3848 );
3849 }
3850
3851 #[ip_test(I)]
3852 #[test_case(InitialState::Reachable, true; "reachable with different address")]
3853 #[test_case(InitialState::Reachable, false; "reachable with same address")]
3854 #[test_case(InitialState::Stale, true; "stale with different address")]
3855 #[test_case(InitialState::Stale, false; "stale with same address")]
3856 #[test_case(InitialState::Delay, true; "delay with different address")]
3857 #[test_case(InitialState::Delay, false; "delay with same address")]
3858 #[test_case(InitialState::Probe, true; "probe with different address")]
3859 #[test_case(InitialState::Probe, false; "probe with same address")]
3860 #[test_case(InitialState::Unreachable, true; "unreachable with different address")]
3861 #[test_case(InitialState::Unreachable, false; "unreachable with same address")]
3862 fn transition_to_stale_on_probe_with_different_address<I: TestIpExt>(
3863 initial_state: InitialState,
3864 update_link_address: bool,
3865 ) {
3866 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3867
3868 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3870
3871 NudHandler::handle_neighbor_update(
3873 &mut core_ctx,
3874 &mut bindings_ctx,
3875 &FakeLinkDeviceId,
3876 I::LOOKUP_ADDR1,
3877 DynamicNeighborUpdateSource::Probe {
3878 link_address: if update_link_address { LINK_ADDR2 } else { LINK_ADDR1 },
3879 },
3880 );
3881
3882 let expected_state = if update_link_address {
3888 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 })
3889 } else {
3890 initial_state
3891 };
3892 assert_neighbor_state(
3893 &core_ctx,
3894 &mut bindings_ctx,
3895 expected_state,
3896 update_link_address.then_some(ExpectedEvent::Changed),
3897 );
3898 }
3899
3900 #[ip_test(I)]
3901 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
3902 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
3903 #[test_case(InitialState::Stale, true; "stale with override flag set")]
3904 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
3905 #[test_case(InitialState::Delay, true; "delay with override flag set")]
3906 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
3907 #[test_case(InitialState::Probe, true; "probe with override flag set")]
3908 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
3909 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
3910 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
3911 fn transition_to_reachable_on_solicited_confirmation_same_address<I: TestIpExt>(
3912 initial_state: InitialState,
3913 override_flag: bool,
3914 ) {
3915 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3916
3917 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3919
3920 NudHandler::handle_neighbor_update(
3922 &mut core_ctx,
3923 &mut bindings_ctx,
3924 &FakeLinkDeviceId,
3925 I::LOOKUP_ADDR1,
3926 DynamicNeighborUpdateSource::Confirmation {
3927 link_address: Some(LINK_ADDR1),
3928 flags: ConfirmationFlags { solicited_flag: true, override_flag },
3929 },
3930 );
3931
3932 let now = bindings_ctx.now();
3934 assert_neighbor_state(
3935 &core_ctx,
3936 &mut bindings_ctx,
3937 DynamicNeighborState::Reachable(Reachable {
3938 link_address: LINK_ADDR1,
3939 last_confirmed_at: now,
3940 }),
3941 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
3942 );
3943 }
3944
3945 #[ip_test(I)]
3946 #[test_case(InitialState::Reachable; "reachable")]
3947 #[test_case(InitialState::Stale; "stale")]
3948 #[test_case(InitialState::Delay; "delay")]
3949 #[test_case(InitialState::Probe; "probe")]
3950 #[test_case(InitialState::Unreachable; "unreachable")]
3951 fn transition_to_stale_on_unsolicited_override_confirmation_with_different_address<
3952 I: TestIpExt,
3953 >(
3954 initial_state: InitialState,
3955 ) {
3956 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3957
3958 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3960
3961 NudHandler::handle_neighbor_update(
3963 &mut core_ctx,
3964 &mut bindings_ctx,
3965 &FakeLinkDeviceId,
3966 I::LOOKUP_ADDR1,
3967 DynamicNeighborUpdateSource::Confirmation {
3968 link_address: Some(LINK_ADDR2),
3969 flags: ConfirmationFlags { solicited_flag: false, override_flag: true },
3970 },
3971 );
3972
3973 assert_neighbor_state(
3975 &core_ctx,
3976 &mut bindings_ctx,
3977 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
3978 Some(ExpectedEvent::Changed),
3979 );
3980 }
3981
3982 #[ip_test(I)]
3983 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
3984 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
3985 #[test_case(InitialState::Stale, true; "stale with override flag set")]
3986 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
3987 #[test_case(InitialState::Delay, true; "delay with override flag set")]
3988 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
3989 #[test_case(InitialState::Probe, true; "probe with override flag set")]
3990 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
3991 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
3992 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
3993 fn noop_on_unsolicited_confirmation_with_same_address<I: TestIpExt>(
3994 initial_state: InitialState,
3995 override_flag: bool,
3996 ) {
3997 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3998
3999 let expected_state =
4001 init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4002
4003 NudHandler::handle_neighbor_update(
4005 &mut core_ctx,
4006 &mut bindings_ctx,
4007 &FakeLinkDeviceId,
4008 I::LOOKUP_ADDR1,
4009 DynamicNeighborUpdateSource::Confirmation {
4010 link_address: Some(LINK_ADDR1),
4011 flags: ConfirmationFlags { solicited_flag: false, override_flag },
4012 },
4013 );
4014
4015 assert_neighbor_state(&core_ctx, &mut bindings_ctx, expected_state, None);
4017 }
4018
4019 #[ip_test(I)]
4020 #[test_case(InitialState::Reachable; "reachable")]
4021 #[test_case(InitialState::Stale; "stale")]
4022 #[test_case(InitialState::Delay; "delay")]
4023 #[test_case(InitialState::Probe; "probe")]
4024 #[test_case(InitialState::Unreachable; "unreachable")]
4025 fn transition_to_reachable_on_solicited_override_confirmation_with_different_address<
4026 I: TestIpExt,
4027 >(
4028 initial_state: InitialState,
4029 ) {
4030 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4031
4032 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4034
4035 NudHandler::handle_neighbor_update(
4037 &mut core_ctx,
4038 &mut bindings_ctx,
4039 &FakeLinkDeviceId,
4040 I::LOOKUP_ADDR1,
4041 DynamicNeighborUpdateSource::Confirmation {
4042 link_address: Some(LINK_ADDR2),
4043 flags: ConfirmationFlags { solicited_flag: true, override_flag: true },
4044 },
4045 );
4046
4047 let now = bindings_ctx.now();
4049 assert_neighbor_state(
4050 &core_ctx,
4051 &mut bindings_ctx,
4052 DynamicNeighborState::Reachable(Reachable {
4053 link_address: LINK_ADDR2,
4054 last_confirmed_at: now,
4055 }),
4056 Some(ExpectedEvent::Changed),
4057 );
4058 }
4059
4060 #[ip_test(I)]
4061 fn reachable_to_reachable_on_probe_with_same_address<I: TestIpExt>() {
4062 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4063
4064 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4066
4067 NudHandler::handle_neighbor_update(
4069 &mut core_ctx,
4070 &mut bindings_ctx,
4071 &FakeLinkDeviceId,
4072 I::LOOKUP_ADDR1,
4073 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
4074 );
4075
4076 let now = bindings_ctx.now();
4078 assert_neighbor_state(
4079 &core_ctx,
4080 &mut bindings_ctx,
4081 DynamicNeighborState::Reachable(Reachable {
4082 link_address: LINK_ADDR1,
4083 last_confirmed_at: now,
4084 }),
4085 None,
4086 );
4087 }
4088
4089 #[ip_test(I)]
4090 #[test_case(true; "solicited")]
4091 #[test_case(false; "unsolicited")]
4092 fn reachable_to_stale_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4093 solicited_flag: bool,
4094 ) {
4095 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4096
4097 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4099
4100 NudHandler::handle_neighbor_update(
4102 &mut core_ctx,
4103 &mut bindings_ctx,
4104 &FakeLinkDeviceId,
4105 I::LOOKUP_ADDR1,
4106 DynamicNeighborUpdateSource::Confirmation {
4107 link_address: Some(LINK_ADDR2),
4108 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4109 },
4110 );
4111
4112 assert_neighbor_state(
4115 &core_ctx,
4116 &mut bindings_ctx,
4117 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4118 Some(ExpectedEvent::Changed),
4119 );
4120 }
4121
4122 #[ip_test(I)]
4123 #[test_case(InitialState::Stale, true; "stale solicited")]
4124 #[test_case(InitialState::Stale, false; "stale unsolicited")]
4125 #[test_case(InitialState::Delay, true; "delay solicited")]
4126 #[test_case(InitialState::Delay, false; "delay unsolicited")]
4127 #[test_case(InitialState::Probe, true; "probe solicited")]
4128 #[test_case(InitialState::Probe, false; "probe unsolicited")]
4129 #[test_case(InitialState::Unreachable, true; "unreachable solicited")]
4130 #[test_case(InitialState::Unreachable, false; "unreachable unsolicited")]
4131 fn noop_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4132 initial_state: InitialState,
4133 solicited_flag: bool,
4134 ) {
4135 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4136
4137 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4139
4140 NudHandler::handle_neighbor_update(
4142 &mut core_ctx,
4143 &mut bindings_ctx,
4144 &FakeLinkDeviceId,
4145 I::LOOKUP_ADDR1,
4146 DynamicNeighborUpdateSource::Confirmation {
4147 link_address: Some(LINK_ADDR2),
4148 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4149 },
4150 );
4151
4152 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial_state, None);
4155 }
4156
4157 #[ip_test(I)]
4158 fn stale_to_delay_on_packet_sent<I: TestIpExt>() {
4159 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4160
4161 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4163
4164 let body = 1;
4166 assert_eq!(
4167 NudHandler::send_ip_packet_to_neighbor(
4168 &mut core_ctx,
4169 &mut bindings_ctx,
4170 &FakeLinkDeviceId,
4171 I::LOOKUP_ADDR1,
4172 Buf::new([body], ..),
4173 FakeTxMetadata::default(),
4174 ),
4175 Ok(())
4176 );
4177
4178 assert_neighbor_state(
4180 &core_ctx,
4181 &mut bindings_ctx,
4182 DynamicNeighborState::Delay(Delay { link_address: LINK_ADDR1 }),
4183 Some(ExpectedEvent::Changed),
4184 );
4185 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4186 &mut bindings_ctx,
4187 [(I::LOOKUP_ADDR1, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
4188 );
4189 assert_pending_frame_sent(
4190 &mut core_ctx,
4191 VecDeque::from([Buf::new(vec![body], ..)]),
4192 LINK_ADDR1,
4193 );
4194 }
4195
4196 #[ip_test(I)]
4197 #[test_case(InitialState::Delay,
4198 NudEvent::DelayFirstProbe;
4199 "delay to probe")]
4200 #[test_case(InitialState::Probe,
4201 NudEvent::RetransmitUnicastProbe;
4202 "probe retransmit unicast probe")]
4203 fn delay_or_probe_to_probe_on_timeout<I: TestIpExt>(
4204 initial_state: InitialState,
4205 expected_initial_event: NudEvent,
4206 ) {
4207 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4208
4209 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4211
4212 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4213
4214 let (time, transmit_counter) = match initial_state {
4220 InitialState::Delay => {
4221 (DELAY_FIRST_PROBE_TIME, NonZeroU16::new(max_unicast_solicit - 1))
4222 }
4223 InitialState::Probe => {
4224 (core_ctx.inner.state.retrans_timer, NonZeroU16::new(max_unicast_solicit - 2))
4225 }
4226 other => unreachable!("test only covers DELAY and PROBE, got {:?}", other),
4227 };
4228 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4229 &mut bindings_ctx,
4230 [(I::LOOKUP_ADDR1, expected_initial_event, time.get())],
4231 );
4232 assert_eq!(
4233 bindings_ctx.trigger_timers_for(time.into(), &mut core_ctx,),
4234 [NudTimerId::neighbor()]
4235 );
4236 assert_neighbor_state(
4237 &core_ctx,
4238 &mut bindings_ctx,
4239 DynamicNeighborState::Probe(Probe { link_address: LINK_ADDR1, transmit_counter }),
4240 (initial_state != InitialState::Probe).then_some(ExpectedEvent::Changed),
4241 );
4242 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4243 &mut bindings_ctx,
4244 [(
4245 I::LOOKUP_ADDR1,
4246 NudEvent::RetransmitUnicastProbe,
4247 core_ctx.inner.state.retrans_timer.get(),
4248 )],
4249 );
4250 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4251 }
4252
4253 #[ip_test(I)]
4254 fn unreachable_probes_with_exponential_backoff_while_packets_sent<I: TestIpExt>() {
4255 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4256
4257 init_unreachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4258
4259 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4260 let timer_id = NudTimerId::neighbor();
4261
4262 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), []);
4264 assert_eq!(core_ctx.inner.take_frames(), []);
4265
4266 const BODY: u8 = 0x33;
4268 assert_eq!(
4269 NudHandler::send_ip_packet_to_neighbor(
4270 &mut core_ctx,
4271 &mut bindings_ctx,
4272 &FakeLinkDeviceId,
4273 I::LOOKUP_ADDR1,
4274 Buf::new([BODY], ..),
4275 FakeTxMetadata::default(),
4276 ),
4277 Ok(())
4278 );
4279 assert_eq!(
4280 core_ctx.inner.take_frames(),
4281 [
4282 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4283 (
4284 FakeNudMessageMeta::NeighborSolicitation {
4285 lookup_addr: I::LOOKUP_ADDR1,
4286 remote_link_addr: None,
4287 },
4288 Vec::new()
4289 )
4290 ]
4291 );
4292
4293 let next_backoff_timer = |core_ctx: &mut FakeCoreCtxImpl<I>, probes_sent| {
4294 UnreachableMode::Backoff {
4295 probes_sent: NonZeroU32::new(probes_sent).unwrap(),
4296 packet_sent: false,
4297 }
4298 .next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state)
4299 .get()
4300 };
4301
4302 const ITERATIONS: u8 = 2;
4303 for i in 1..ITERATIONS {
4304 let probes_sent = u32::from(i);
4305
4306 assert_eq!(
4309 NudHandler::send_ip_packet_to_neighbor(
4310 &mut core_ctx,
4311 &mut bindings_ctx,
4312 &FakeLinkDeviceId,
4313 I::LOOKUP_ADDR1,
4314 Buf::new([BODY + i], ..),
4315 FakeTxMetadata::default(),
4316 ),
4317 Ok(())
4318 );
4319 assert_eq!(
4320 core_ctx.inner.take_frames(),
4321 [(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY + i])]
4322 );
4323
4324 assert_eq!(
4329 bindings_ctx.trigger_timers_for(
4330 next_backoff_timer(&mut core_ctx, probes_sent),
4331 &mut core_ctx,
4332 ),
4333 [timer_id]
4334 );
4335 assert_neighbor_probe_sent(&mut core_ctx, None);
4336 bindings_ctx.timers.assert_timers_installed([(
4337 timer_id,
4338 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, probes_sent + 1),
4339 )]);
4340 }
4341
4342 let current_timer = next_backoff_timer(&mut core_ctx, u32::from(ITERATIONS));
4345 assert_eq!(bindings_ctx.trigger_timers_for(current_timer, &mut core_ctx,), [timer_id]);
4346 assert_eq!(core_ctx.inner.take_frames(), []);
4347 bindings_ctx.timers.assert_no_timers_installed();
4348
4349 assert_eq!(
4352 NudHandler::send_ip_packet_to_neighbor(
4353 &mut core_ctx,
4354 &mut bindings_ctx,
4355 &FakeLinkDeviceId,
4356 I::LOOKUP_ADDR1,
4357 Buf::new([BODY], ..),
4358 FakeTxMetadata::default(),
4359 ),
4360 Ok(())
4361 );
4362 assert_eq!(
4363 core_ctx.inner.take_frames(),
4364 [
4365 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4366 (
4367 FakeNudMessageMeta::NeighborSolicitation {
4368 lookup_addr: I::LOOKUP_ADDR1,
4369 remote_link_addr: None,
4370 },
4371 Vec::new()
4372 )
4373 ]
4374 );
4375 bindings_ctx.timers.assert_timers_installed([(
4376 timer_id,
4377 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, 1),
4378 )]);
4379 }
4380
4381 #[ip_test(I)]
4382 #[test_case(true; "solicited confirmation")]
4383 #[test_case(false; "unsolicited confirmation")]
4384 fn confirmation_should_not_create_entry<I: TestIpExt>(solicited_flag: bool) {
4385 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4386
4387 let link_address = Some(FakeLinkAddress([1]));
4388 NudHandler::handle_neighbor_update(
4389 &mut core_ctx,
4390 &mut bindings_ctx,
4391 &FakeLinkDeviceId,
4392 I::LOOKUP_ADDR1,
4393 DynamicNeighborUpdateSource::Confirmation {
4394 link_address,
4395 flags: ConfirmationFlags { solicited_flag, override_flag: false },
4396 },
4397 );
4398 assert_eq!(core_ctx.nud.state.neighbors, HashMap::new());
4399 }
4400
4401 #[ip_test(I)]
4402 #[test_case(true; "set_with_dynamic")]
4403 #[test_case(false; "set_with_static")]
4404 fn pending_frames<I: TestIpExt>(dynamic: bool) {
4405 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4406 assert_eq!(core_ctx.inner.take_frames(), []);
4407
4408 const MAX_PENDING_FRAMES_U8: u8 = MAX_PENDING_FRAMES as u8;
4412 let expected_pending_frames = (0..MAX_PENDING_FRAMES_U8)
4413 .map(|i| (Buf::new(vec![i], ..), FakeTxMetadata::default()))
4414 .collect::<VecDeque<_>>();
4415
4416 for (body, meta) in expected_pending_frames.iter() {
4417 assert_eq!(
4418 NudHandler::send_ip_packet_to_neighbor(
4419 &mut core_ctx,
4420 &mut bindings_ctx,
4421 &FakeLinkDeviceId,
4422 I::LOOKUP_ADDR1,
4423 body.clone(),
4424 meta.clone(),
4425 ),
4426 Ok(())
4427 );
4428 }
4429 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4430 assert_neighbor_probe_sent(&mut core_ctx, None);
4432 assert_neighbor_state(
4433 &core_ctx,
4434 &mut bindings_ctx,
4435 DynamicNeighborState::Incomplete(Incomplete {
4436 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4437 pending_frames: expected_pending_frames.clone(),
4438 notifiers: Vec::new(),
4439 _marker: PhantomData,
4440 }),
4441 Some(ExpectedEvent::Added),
4442 );
4443
4444 assert_eq!(
4446 NudHandler::send_ip_packet_to_neighbor(
4447 &mut core_ctx,
4448 &mut bindings_ctx,
4449 &FakeLinkDeviceId,
4450 I::LOOKUP_ADDR1,
4451 Buf::new([123], ..),
4452 FakeTxMetadata::default(),
4453 ),
4454 Ok(())
4455 );
4456 assert_eq!(core_ctx.inner.take_frames(), []);
4457 assert_neighbor_state(
4458 &core_ctx,
4459 &mut bindings_ctx,
4460 DynamicNeighborState::Incomplete(Incomplete {
4461 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4462 pending_frames: expected_pending_frames.clone(),
4463 notifiers: Vec::new(),
4464 _marker: PhantomData,
4465 }),
4466 None,
4467 );
4468
4469 if dynamic {
4471 NudHandler::handle_neighbor_update(
4472 &mut core_ctx,
4473 &mut bindings_ctx,
4474 &FakeLinkDeviceId,
4475 I::LOOKUP_ADDR1,
4476 DynamicNeighborUpdateSource::Confirmation {
4477 link_address: Some(LINK_ADDR1),
4478 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4479 },
4480 );
4481 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4482 &mut bindings_ctx,
4483 [(
4484 I::LOOKUP_ADDR1,
4485 NudEvent::ReachableTime,
4486 core_ctx.inner.base_reachable_time().get(),
4487 )],
4488 );
4489 let last_confirmed_at = bindings_ctx.now();
4490 assert_neighbor_state(
4491 &core_ctx,
4492 &mut bindings_ctx,
4493 DynamicNeighborState::Reachable(Reachable {
4494 link_address: LINK_ADDR1,
4495 last_confirmed_at,
4496 }),
4497 Some(ExpectedEvent::Changed),
4498 );
4499 } else {
4500 init_static_neighbor(
4501 &mut core_ctx,
4502 &mut bindings_ctx,
4503 LINK_ADDR1,
4504 ExpectedEvent::Changed,
4505 );
4506 bindings_ctx.timers.assert_no_timers_installed();
4507 }
4508 assert_eq!(
4509 core_ctx.inner.take_frames(),
4510 expected_pending_frames
4511 .into_iter()
4512 .map(|(p, FakeTxMetadata)| (
4513 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4514 p.as_ref().to_vec()
4515 ))
4516 .collect::<Vec<_>>()
4517 );
4518 }
4519
4520 #[ip_test(I)]
4521 fn static_neighbor<I: TestIpExt>() {
4522 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4523
4524 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4525 bindings_ctx.timers.assert_no_timers_installed();
4526 assert_eq!(core_ctx.inner.take_frames(), []);
4527 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4528
4529 NudHandler::handle_neighbor_update(
4531 &mut core_ctx,
4532 &mut bindings_ctx,
4533 &FakeLinkDeviceId,
4534 I::LOOKUP_ADDR1,
4535 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4536 );
4537 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4538
4539 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4540
4541 let neighbors = &core_ctx.nud.state.neighbors;
4542 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4543 }
4544
4545 #[ip_test(I)]
4546 fn dynamic_neighbor<I: TestIpExt>() {
4547 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4548
4549 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4550 bindings_ctx.timers.assert_no_timers_installed();
4551 assert_eq!(core_ctx.inner.take_frames(), []);
4552 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4553
4554 NudHandler::handle_neighbor_update(
4556 &mut core_ctx,
4557 &mut bindings_ctx,
4558 &FakeLinkDeviceId,
4559 I::LOOKUP_ADDR1,
4560 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4561 );
4562 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR2);
4563 assert_eq!(core_ctx.inner.take_frames(), []);
4564 assert_neighbor_state(
4565 &core_ctx,
4566 &mut bindings_ctx,
4567 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4568 Some(ExpectedEvent::Changed),
4569 );
4570
4571 init_static_neighbor_with_ip(
4573 &mut core_ctx,
4574 &mut bindings_ctx,
4575 I::LOOKUP_ADDR1,
4576 LINK_ADDR3,
4577 ExpectedEvent::Changed,
4578 );
4579 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR3);
4580 assert_eq!(core_ctx.inner.take_frames(), []);
4581 }
4582
4583 #[ip_test(I)]
4584 fn send_solicitation_on_lookup<I: TestIpExt>() {
4585 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4586 bindings_ctx.timers.assert_no_timers_installed();
4587 assert_eq!(core_ctx.inner.take_frames(), []);
4588
4589 let mut pending_frames = VecDeque::new();
4590
4591 queue_ip_packet_to_unresolved_neighbor(
4592 &mut core_ctx,
4593 &mut bindings_ctx,
4594 I::LOOKUP_ADDR1,
4595 &mut pending_frames,
4596 1,
4597 true, );
4599 assert_neighbor_probe_sent(&mut core_ctx, None);
4600
4601 queue_ip_packet_to_unresolved_neighbor(
4602 &mut core_ctx,
4603 &mut bindings_ctx,
4604 I::LOOKUP_ADDR1,
4605 &mut pending_frames,
4606 2,
4607 false, );
4609 assert_eq!(core_ctx.inner.take_frames(), []);
4610
4611 NudHandler::handle_neighbor_update(
4613 &mut core_ctx,
4614 &mut bindings_ctx,
4615 &FakeLinkDeviceId,
4616 I::LOOKUP_ADDR1,
4617 DynamicNeighborUpdateSource::Confirmation {
4618 link_address: Some(LINK_ADDR1),
4619 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4620 },
4621 );
4622 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4623
4624 let now = bindings_ctx.now();
4625 assert_neighbor_state(
4626 &core_ctx,
4627 &mut bindings_ctx,
4628 DynamicNeighborState::Reachable(Reachable {
4629 link_address: LINK_ADDR1,
4630 last_confirmed_at: now,
4631 }),
4632 Some(ExpectedEvent::Changed),
4633 );
4634 assert_eq!(
4635 core_ctx.inner.take_frames(),
4636 pending_frames
4637 .into_iter()
4638 .map(|f| (
4639 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4640 f.as_ref().to_vec(),
4641 ))
4642 .collect::<Vec<_>>()
4643 );
4644 }
4645
4646 #[ip_test(I)]
4647 fn solicitation_failure_in_incomplete<I: TestIpExt>() {
4648 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4649 bindings_ctx.timers.assert_no_timers_installed();
4650 assert_eq!(core_ctx.inner.take_frames(), []);
4651
4652 let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
4653
4654 let timer_id = NudTimerId::neighbor();
4655
4656 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4657 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4658
4659 for i in 1..=max_multicast_solicit {
4660 assert_neighbor_state(
4661 &core_ctx,
4662 &mut bindings_ctx,
4663 DynamicNeighborState::Incomplete(Incomplete {
4664 transmit_counter: NonZeroU16::new(max_multicast_solicit - i),
4665 pending_frames: pending_frames
4666 .iter()
4667 .cloned()
4668 .map(|b| (b, FakeTxMetadata::default()))
4669 .collect(),
4670 notifiers: Vec::new(),
4671 _marker: PhantomData,
4672 }),
4673 None,
4674 );
4675
4676 bindings_ctx
4677 .timers
4678 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4679 assert_neighbor_probe_sent(&mut core_ctx, None);
4680
4681 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4682 }
4683
4684 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1);
4686 bindings_ctx.timers.assert_no_timers_installed();
4687
4688 assert_eq!(core_ctx.inner.take_frames(), []);
4692 assert_eq!(core_ctx.counters().as_ref().icmp_dest_unreachable_dropped.get(), 1);
4693 }
4694
4695 #[ip_test(I)]
4696 fn solicitation_failure_in_probe<I: TestIpExt>() {
4697 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4698 bindings_ctx.timers.assert_no_timers_installed();
4699 assert_eq!(core_ctx.inner.take_frames(), []);
4700
4701 init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
4702
4703 let timer_id = NudTimerId::neighbor();
4704 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4705 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4706 for i in 1..=max_unicast_solicit {
4707 assert_neighbor_state(
4708 &core_ctx,
4709 &mut bindings_ctx,
4710 DynamicNeighborState::Probe(Probe {
4711 transmit_counter: NonZeroU16::new(max_unicast_solicit - i),
4712 link_address: LINK_ADDR1,
4713 }),
4714 None,
4715 );
4716
4717 bindings_ctx
4718 .timers
4719 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4720 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4721
4722 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4723 }
4724
4725 assert_neighbor_state(
4726 &core_ctx,
4727 &mut bindings_ctx,
4728 DynamicNeighborState::Unreachable(Unreachable {
4729 link_address: LINK_ADDR1,
4730 mode: UnreachableMode::WaitingForPacketSend,
4731 }),
4732 Some(ExpectedEvent::Changed),
4733 );
4734 bindings_ctx.timers.assert_no_timers_installed();
4735 assert_eq!(core_ctx.inner.take_frames(), []);
4736 }
4737
4738 #[ip_test(I)]
4739 fn flush_entries<I: TestIpExt>() {
4740 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4741 bindings_ctx.timers.assert_no_timers_installed();
4742 assert_eq!(core_ctx.inner.take_frames(), []);
4743
4744 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4745 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR2, LINK_ADDR2);
4746 let pending_frames = init_incomplete_neighbor_with_ip(
4747 &mut core_ctx,
4748 &mut bindings_ctx,
4749 I::LOOKUP_ADDR3,
4750 true,
4751 );
4752 let pending_frames =
4753 pending_frames.into_iter().map(|b| (b, FakeTxMetadata::default())).collect();
4754
4755 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4756 assert_eq!(
4757 core_ctx.nud.state.neighbors,
4758 HashMap::from([
4759 (I::LOOKUP_ADDR1, NeighborState::Static(LINK_ADDR1)),
4760 (
4761 I::LOOKUP_ADDR2,
4762 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
4763 link_address: LINK_ADDR2,
4764 })),
4765 ),
4766 (
4767 I::LOOKUP_ADDR3,
4768 NeighborState::Dynamic(DynamicNeighborState::Incomplete(Incomplete {
4769 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4770 pending_frames,
4771 notifiers: Vec::new(),
4772 _marker: PhantomData,
4773 })),
4774 ),
4775 ]),
4776 );
4777 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4778 &mut bindings_ctx,
4779 [(I::LOOKUP_ADDR3, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
4780 );
4781
4782 NudHandler::flush(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId);
4784 let neighbors = &core_ctx.nud.state.neighbors;
4785 assert!(neighbors.is_empty(), "neighbor table should be empty: {:?}", neighbors);
4786 assert_eq!(
4787 bindings_ctx.take_events().into_iter().collect::<HashSet<_>>(),
4788 [I::LOOKUP_ADDR1, I::LOOKUP_ADDR2, I::LOOKUP_ADDR3]
4789 .into_iter()
4790 .map(|addr| { Event::removed(&FakeLinkDeviceId, addr, bindings_ctx.now()) })
4791 .collect(),
4792 );
4793 bindings_ctx.timers.assert_no_timers_installed();
4794 }
4795
4796 #[ip_test(I)]
4797 fn delete_dynamic_entry<I: TestIpExt>() {
4798 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4799 bindings_ctx.timers.assert_no_timers_installed();
4800 assert_eq!(core_ctx.inner.take_frames(), []);
4801
4802 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4803 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4804
4805 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4806
4807 let neighbors = &core_ctx.nud.state.neighbors;
4809 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4810 bindings_ctx.timers.assert_no_timers_installed();
4811 }
4812
4813 #[ip_test(I)]
4814 #[test_case(InitialState::Reachable; "reachable neighbor")]
4815 #[test_case(InitialState::Stale; "stale neighbor")]
4816 #[test_case(InitialState::Delay; "delay neighbor")]
4817 #[test_case(InitialState::Probe; "probe neighbor")]
4818 #[test_case(InitialState::Unreachable; "unreachable neighbor")]
4819 fn resolve_cached_linked_addr<I: TestIpExt>(initial_state: InitialState) {
4820 let mut ctx = new_context::<I>();
4821 ctx.bindings_ctx.timers.assert_no_timers_installed();
4822 assert_eq!(ctx.core_ctx.inner.take_frames(), []);
4823
4824 let _ = init_neighbor_in_state(&mut ctx.core_ctx, &mut ctx.bindings_ctx, initial_state);
4825
4826 let link_addr = assert_matches!(
4827 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4828 &FakeLinkDeviceId,
4829 &I::LOOKUP_ADDR1,
4830 ),
4831 LinkResolutionResult::Resolved(addr) => addr
4832 );
4833 assert_eq!(link_addr, LINK_ADDR1);
4834 if initial_state == InitialState::Stale {
4835 assert_eq!(
4836 ctx.bindings_ctx.take_events(),
4837 [Event::changed(
4838 &FakeLinkDeviceId,
4839 EventState::Dynamic(EventDynamicState::Delay(LINK_ADDR1)),
4840 I::LOOKUP_ADDR1,
4841 ctx.bindings_ctx.now(),
4842 )],
4843 );
4844 }
4845 }
4846
4847 enum ResolutionSuccess {
4848 Confirmation,
4849 StaticEntryAdded,
4850 }
4851
4852 #[ip_test(I)]
4853 #[test_case(ResolutionSuccess::Confirmation; "incomplete entry timed out")]
4854 #[test_case(ResolutionSuccess::StaticEntryAdded; "incomplete entry removed from table")]
4855 fn dynamic_neighbor_resolution_success<I: TestIpExt>(reason: ResolutionSuccess) {
4856 let mut ctx = new_context::<I>();
4857
4858 let observers = (0..10)
4859 .map(|_| {
4860 let observer = assert_matches!(
4861 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4862 &FakeLinkDeviceId,
4863 &I::LOOKUP_ADDR1,
4864 ),
4865 LinkResolutionResult::Pending(observer) => observer
4866 );
4867 assert_eq!(*observer.lock(), None);
4868 observer
4869 })
4870 .collect::<Vec<_>>();
4871 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
4872 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4873
4874 assert_neighbor_state(
4877 core_ctx,
4878 bindings_ctx,
4879 DynamicNeighborState::Incomplete(Incomplete {
4880 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4881 pending_frames: VecDeque::new(),
4882 notifiers: Vec::new(),
4884 _marker: PhantomData,
4885 }),
4886 Some(ExpectedEvent::Added),
4887 );
4888 assert_neighbor_probe_sent(core_ctx, None);
4889
4890 match reason {
4891 ResolutionSuccess::Confirmation => {
4892 NudHandler::handle_neighbor_update(
4894 core_ctx,
4895 bindings_ctx,
4896 &FakeLinkDeviceId,
4897 I::LOOKUP_ADDR1,
4898 DynamicNeighborUpdateSource::Confirmation {
4899 link_address: Some(LINK_ADDR1),
4900 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4901 },
4902 );
4903 let now = bindings_ctx.now();
4904 assert_neighbor_state(
4905 core_ctx,
4906 bindings_ctx,
4907 DynamicNeighborState::Reachable(Reachable {
4908 link_address: LINK_ADDR1,
4909 last_confirmed_at: now,
4910 }),
4911 Some(ExpectedEvent::Changed),
4912 );
4913 }
4914 ResolutionSuccess::StaticEntryAdded => {
4915 init_static_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, ExpectedEvent::Changed);
4916 assert_eq!(
4917 core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
4918 Some(&NeighborState::Static(LINK_ADDR1))
4919 );
4920 }
4921 }
4922
4923 for observer in observers {
4925 assert_eq!(*observer.lock(), Some(Ok(LINK_ADDR1)));
4926 }
4927 }
4928
4929 enum ResolutionFailure {
4930 Timeout,
4931 Removed,
4932 }
4933
4934 #[ip_test(I)]
4935 #[test_case(ResolutionFailure::Timeout; "incomplete entry timed out")]
4936 #[test_case(ResolutionFailure::Removed; "incomplete entry removed from table")]
4937 fn dynamic_neighbor_resolution_failure<I: TestIpExt>(reason: ResolutionFailure) {
4938 let mut ctx = new_context::<I>();
4939
4940 let observers = (0..10)
4941 .map(|_| {
4942 let observer = assert_matches!(
4943 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4944 &FakeLinkDeviceId,
4945 &I::LOOKUP_ADDR1,
4946 ),
4947 LinkResolutionResult::Pending(observer) => observer
4948 );
4949 assert_eq!(*observer.lock(), None);
4950 observer
4951 })
4952 .collect::<Vec<_>>();
4953
4954 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
4955 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4956
4957 assert_neighbor_state(
4960 core_ctx,
4961 bindings_ctx,
4962 DynamicNeighborState::Incomplete(Incomplete {
4963 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4964 pending_frames: VecDeque::new(),
4965 notifiers: Vec::new(),
4967 _marker: PhantomData,
4968 }),
4969 Some(ExpectedEvent::Added),
4970 );
4971 assert_neighbor_probe_sent(core_ctx, None);
4972
4973 match reason {
4974 ResolutionFailure::Timeout => {
4975 for _ in 1..=max_multicast_solicit {
4978 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4979 assert_eq!(
4980 bindings_ctx.trigger_timers_for(retrans_timer, core_ctx),
4981 [NudTimerId::neighbor()]
4982 );
4983 }
4984 }
4985 ResolutionFailure::Removed => {
4986 NudHandler::flush(core_ctx, bindings_ctx, &FakeLinkDeviceId);
4988 }
4989 }
4990
4991 assert_neighbor_removed_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1);
4992 for observer in observers {
4994 assert_eq!(*observer.lock(), Some(Err(AddressResolutionFailed)));
4995 }
4996 }
4997
4998 #[ip_test(I)]
4999 #[test_case(InitialState::Incomplete, false; "incomplete neighbor")]
5000 #[test_case(InitialState::Reachable, true; "reachable neighbor")]
5001 #[test_case(InitialState::Stale, true; "stale neighbor")]
5002 #[test_case(InitialState::Delay, true; "delay neighbor")]
5003 #[test_case(InitialState::Probe, true; "probe neighbor")]
5004 #[test_case(InitialState::Unreachable, true; "unreachable neighbor")]
5005 fn upper_layer_confirmation<I: TestIpExt>(
5006 initial_state: InitialState,
5007 should_transition_to_reachable: bool,
5008 ) {
5009 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5010 let base_reachable_time = core_ctx.inner.base_reachable_time().get();
5011
5012 let initial = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
5013
5014 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5015
5016 if !should_transition_to_reachable {
5017 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial, None);
5018 return;
5019 }
5020
5021 let now = bindings_ctx.now();
5023 assert_neighbor_state(
5024 &core_ctx,
5025 &mut bindings_ctx,
5026 DynamicNeighborState::Reachable(Reachable {
5027 link_address: LINK_ADDR1,
5028 last_confirmed_at: now,
5029 }),
5030 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
5031 );
5032 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5033 &mut bindings_ctx,
5034 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time)],
5035 );
5036
5037 bindings_ctx.timers.instant.sleep(base_reachable_time / 2);
5041 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5042 let now = bindings_ctx.now();
5043 assert_neighbor_state(
5044 &core_ctx,
5045 &mut bindings_ctx,
5046 DynamicNeighborState::Reachable(Reachable {
5047 link_address: LINK_ADDR1,
5048 last_confirmed_at: now,
5049 }),
5050 None,
5051 );
5052 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5053 &mut bindings_ctx,
5054 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5055 );
5056
5057 assert_eq!(
5060 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5061 [NudTimerId::neighbor()]
5062 );
5063 let now = bindings_ctx.now();
5064 assert_neighbor_state(
5065 &core_ctx,
5066 &mut bindings_ctx,
5067 DynamicNeighborState::Reachable(Reachable {
5068 link_address: LINK_ADDR1,
5069 last_confirmed_at: now - base_reachable_time / 2,
5070 }),
5071 None,
5072 );
5073
5074 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5075 &mut bindings_ctx,
5076 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5077 );
5078
5079 assert_eq!(
5082 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5083 [NudTimerId::neighbor()]
5084 );
5085 assert_neighbor_state(
5086 &core_ctx,
5087 &mut bindings_ctx,
5088 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
5089 Some(ExpectedEvent::Changed),
5090 );
5091 bindings_ctx.timers.assert_no_timers_installed();
5092 }
5093
5094 fn generate_ip_addr<I: Ip>(i: usize) -> SpecifiedAddr<I::Addr> {
5095 I::map_ip_out(
5096 i,
5097 |i| {
5098 let start = u32::from_be_bytes(net_ip_v4!("192.168.0.1").ipv4_bytes());
5099 let bytes = (start + u32::try_from(i).unwrap()).to_be_bytes();
5100 SpecifiedAddr::new(Ipv4Addr::new(bytes)).unwrap()
5101 },
5102 |i| {
5103 let start = u128::from_be_bytes(net_ip_v6!("fe80::1").ipv6_bytes());
5104 let bytes = (start + u128::try_from(i).unwrap()).to_be_bytes();
5105 SpecifiedAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap()
5106 },
5107 )
5108 }
5109
5110 #[ip_test(I)]
5111 fn garbage_collection_retains_static_entries<I: TestIpExt>() {
5112 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5113
5114 for i in 0..MAX_ENTRIES * 2 {
5118 if i % 2 == 0 {
5119 init_stale_neighbor_with_ip(
5120 &mut core_ctx,
5121 &mut bindings_ctx,
5122 generate_ip_addr::<I>(i),
5123 LINK_ADDR1,
5124 );
5125 } else {
5126 init_static_neighbor_with_ip(
5127 &mut core_ctx,
5128 &mut bindings_ctx,
5129 generate_ip_addr::<I>(i),
5130 LINK_ADDR1,
5131 ExpectedEvent::Added,
5132 );
5133 }
5134 }
5135 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES * 2);
5136
5137 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5139 for event in bindings_ctx.take_events() {
5140 assert_matches!(event, Event {
5141 device,
5142 addr: _,
5143 kind,
5144 at,
5145 } => {
5146 assert_eq!(kind, EventKind::Removed);
5147 assert_eq!(device, FakeLinkDeviceId);
5148 assert_eq!(at, bindings_ctx.now());
5149 });
5150 }
5151 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5152 for (_, neighbor) in core_ctx.nud.state.neighbors {
5153 assert_matches!(neighbor, NeighborState::Static(_));
5154 }
5155 }
5156
5157 #[ip_test(I)]
5158 fn garbage_collection_retains_in_use_entries<I: TestIpExt>() {
5159 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5160
5161 for i in 0..MAX_ENTRIES - 1 {
5163 init_static_neighbor_with_ip(
5164 &mut core_ctx,
5165 &mut bindings_ctx,
5166 generate_ip_addr::<I>(i),
5167 LINK_ADDR1,
5168 ExpectedEvent::Added,
5169 );
5170 }
5171
5172 let stale_entry = generate_ip_addr::<I>(MAX_ENTRIES - 1);
5174 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry, LINK_ADDR1);
5175 let reachable_entry = generate_ip_addr::<I>(MAX_ENTRIES);
5177 init_reachable_neighbor_with_ip(
5178 &mut core_ctx,
5179 &mut bindings_ctx,
5180 reachable_entry,
5181 LINK_ADDR1,
5182 );
5183
5184 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5186 super::testutil::assert_dynamic_neighbor_state(
5187 &mut core_ctx,
5188 FakeLinkDeviceId,
5189 reachable_entry,
5190 DynamicNeighborState::Reachable(Reachable {
5191 link_address: LINK_ADDR1,
5192 last_confirmed_at: bindings_ctx.now(),
5193 }),
5194 );
5195 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry);
5196 }
5197
5198 #[ip_test(I)]
5199 fn garbage_collection_triggered_on_new_stale_entry<I: TestIpExt>() {
5200 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5201 core_ctx.nud.state.last_gc = Some(bindings_ctx.now());
5203
5204 for i in 0..MAX_ENTRIES {
5206 init_static_neighbor_with_ip(
5207 &mut core_ctx,
5208 &mut bindings_ctx,
5209 generate_ip_addr::<I>(i),
5210 LINK_ADDR1,
5211 ExpectedEvent::Added,
5212 );
5213 }
5214
5215 init_stale_neighbor_with_ip(
5218 &mut core_ctx,
5219 &mut bindings_ctx,
5220 generate_ip_addr::<I>(MAX_ENTRIES + 1),
5221 LINK_ADDR1,
5222 );
5223 let expected_gc_time = bindings_ctx.now() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5224 bindings_ctx
5225 .timers
5226 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5227
5228 bindings_ctx.timers.instant.sleep(ONE_SECOND.get());
5232 init_stale_neighbor_with_ip(
5233 &mut core_ctx,
5234 &mut bindings_ctx,
5235 generate_ip_addr::<I>(MAX_ENTRIES + 2),
5236 LINK_ADDR1,
5237 );
5238 bindings_ctx
5239 .timers
5240 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5241 }
5242
5243 #[ip_test(I)]
5244 fn garbage_collection_triggered_on_transition_to_unreachable<I: TestIpExt>() {
5245 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5246 core_ctx.nud.state.last_gc = Some(bindings_ctx.now());
5248
5249 for i in 0..MAX_ENTRIES {
5251 init_static_neighbor_with_ip(
5252 &mut core_ctx,
5253 &mut bindings_ctx,
5254 generate_ip_addr::<I>(i),
5255 LINK_ADDR1,
5256 ExpectedEvent::Added,
5257 );
5258 }
5259 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5260
5261 init_unreachable_neighbor_with_ip(
5264 &mut core_ctx,
5265 &mut bindings_ctx,
5266 generate_ip_addr::<I>(MAX_ENTRIES),
5267 LINK_ADDR1,
5268 );
5269 let expected_gc_time =
5270 core_ctx.nud.state.last_gc.unwrap() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5271 bindings_ctx
5272 .timers
5273 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5274
5275 init_unreachable_neighbor_with_ip(
5278 &mut core_ctx,
5279 &mut bindings_ctx,
5280 generate_ip_addr::<I>(MAX_ENTRIES + 1),
5281 LINK_ADDR1,
5282 );
5283 bindings_ctx
5284 .timers
5285 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5286 }
5287
5288 #[ip_test(I)]
5289 fn garbage_collection_not_triggered_on_new_incomplete_entry<I: TestIpExt>() {
5290 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5291
5292 for i in 0..MAX_ENTRIES {
5294 init_static_neighbor_with_ip(
5295 &mut core_ctx,
5296 &mut bindings_ctx,
5297 generate_ip_addr::<I>(i),
5298 LINK_ADDR1,
5299 ExpectedEvent::Added,
5300 );
5301 }
5302 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5303
5304 let _: VecDeque<Buf<Vec<u8>>> = init_incomplete_neighbor_with_ip(
5305 &mut core_ctx,
5306 &mut bindings_ctx,
5307 generate_ip_addr::<I>(MAX_ENTRIES),
5308 true,
5309 );
5310 assert_eq!(
5311 bindings_ctx.timers.scheduled_instant(&mut core_ctx.nud.state.timer_heap.gc),
5312 None
5313 );
5314 }
5315
5316 #[ip_test(I)]
5317 fn confirmation_processed_even_if_no_target_link_layer_addr<I: TestIpExt>() {
5318 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5319
5320 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
5322
5323 NudHandler::handle_neighbor_update(
5327 &mut core_ctx,
5328 &mut bindings_ctx,
5329 &FakeLinkDeviceId,
5330 I::LOOKUP_ADDR1,
5331 DynamicNeighborUpdateSource::Confirmation {
5332 link_address: None,
5333 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
5334 },
5335 );
5336 let now = bindings_ctx.now();
5337 assert_neighbor_state(
5338 &core_ctx,
5339 &mut bindings_ctx,
5340 DynamicNeighborState::Reachable(Reachable {
5341 link_address: LINK_ADDR1,
5342 last_confirmed_at: now,
5343 }),
5344 Some(ExpectedEvent::Changed),
5345 );
5346 }
5347
5348 #[ip_test(I)]
5349 #[test_case(InitialState::Stale; "stale")]
5350 #[test_case(InitialState::Reachable; "reachable")]
5351 #[test_case(InitialState::Delay; "delay")]
5352 #[test_case(InitialState::Unreachable; "unreachable")]
5353 fn enter_probe_from_dynamic_state<I: TestIpExt>(initial: InitialState) {
5354 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5355
5356 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial);
5357
5358 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5359 let result = neighbor.enter_probe(
5360 &mut core_ctx.inner.state,
5361 &mut bindings_ctx,
5362 &mut core_ctx.nud.state.timer_heap,
5363 I::LOOKUP_ADDR1,
5364 &FakeLinkDeviceId,
5365 );
5366
5367 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5368 assert_matches!(result, Ok(Some(LINK_ADDR1)));
5369 assert_neighbor_state(
5370 &core_ctx,
5371 &mut bindings_ctx,
5372 DynamicNeighborState::Probe(Probe {
5373 link_address: LINK_ADDR1,
5374 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5375 }),
5376 Some(ExpectedEvent::Changed),
5377 );
5378 }
5379
5380 #[ip_test(I)]
5381 fn enter_probe_from_static_state<I: TestIpExt>() {
5382 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5383
5384 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
5385
5386 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5387 let result = neighbor.enter_probe(
5388 &mut core_ctx.inner.state,
5389 &mut bindings_ctx,
5390 &mut core_ctx.nud.state.timer_heap,
5391 I::LOOKUP_ADDR1,
5392 &FakeLinkDeviceId,
5393 );
5394
5395 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5396 assert_matches!(result, Ok(Some(LINK_ADDR1)));
5397 assert_neighbor_state(
5398 &core_ctx,
5399 &mut bindings_ctx,
5400 DynamicNeighborState::Probe(Probe {
5401 link_address: LINK_ADDR1,
5402 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5403 }),
5404 Some(ExpectedEvent::Changed),
5405 );
5406 }
5407
5408 #[ip_test(I)]
5409 fn enter_probe_from_probe_state<I: TestIpExt>() {
5410 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5411
5412 init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
5413
5414 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5415 let result = neighbor.enter_probe(
5416 &mut core_ctx.inner.state,
5417 &mut bindings_ctx,
5418 &mut core_ctx.nud.state.timer_heap,
5419 I::LOOKUP_ADDR1,
5420 &FakeLinkDeviceId,
5421 );
5422
5423 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5424 assert_matches!(result, Ok(None)); assert_neighbor_state(
5426 &core_ctx,
5427 &mut bindings_ctx,
5428 DynamicNeighborState::Probe(Probe {
5429 link_address: LINK_ADDR1,
5430 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5431 }),
5432 None, );
5434 }
5435
5436 #[ip_test(I)]
5437 fn enter_probe_from_incomplete_state<I: TestIpExt>() {
5438 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5439
5440 let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
5441
5442 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5443 let result = neighbor.enter_probe(
5444 &mut core_ctx.inner.state,
5445 &mut bindings_ctx,
5446 &mut core_ctx.nud.state.timer_heap,
5447 I::LOOKUP_ADDR1,
5448 &FakeLinkDeviceId,
5449 );
5450
5451 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5452 assert_matches!(result, Err(EnterProbeError::LinkAddressUnknown));
5453 assert_neighbor_state(
5454 &core_ctx,
5455 &mut bindings_ctx,
5456 DynamicNeighborState::Incomplete(Incomplete {
5457 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5458 pending_frames: pending_frames
5459 .iter()
5460 .cloned()
5461 .map(|b| (b, FakeTxMetadata::default()))
5462 .collect(),
5463 notifiers: Vec::new(),
5464 _marker: PhantomData,
5465 }),
5466 None, );
5468 }
5469}