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 zerocopy::SplitByteSlice;
38
39pub(crate) mod api;
40
41pub(crate) const DEFAULT_MAX_MULTICAST_SOLICIT: NonZeroU16 = NonZeroU16::new(3).unwrap();
46
47const DEFAULT_MAX_UNICAST_SOLICIT: NonZeroU16 = NonZeroU16::new(3).unwrap();
52
53const MAX_RETRANS_TIMER: NonZeroDuration = NonZeroDuration::from_secs(60).unwrap();
58
59pub const DEFAULT_RETRANS_TIMER: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
66
67const BACKOFF_MULTIPLE: NonZeroU32 = NonZeroU32::new(3).unwrap();
72
73const MAX_PENDING_FRAMES: usize = 10;
74
75const DEFAULT_BASE_REACHABLE_TIME: NonZeroDuration = NonZeroDuration::from_secs(30).unwrap();
80
81const DELAY_FIRST_PROBE_TIME: NonZeroDuration = NonZeroDuration::from_secs(5).unwrap();
86
87pub const MAX_ENTRIES: usize = 512;
92
93const MIN_GARBAGE_COLLECTION_INTERVAL: NonZeroDuration = NonZeroDuration::from_secs(30).unwrap();
96
97#[derive(Default)]
99pub struct NudCountersInner {
100 pub icmp_dest_unreachable_dropped: Counter,
102}
103
104pub type NudCounters<I> = IpMarked<I, NudCountersInner>;
106
107#[derive(Debug, Copy, Clone)]
109pub struct ConfirmationFlags {
110 pub solicited_flag: bool,
112 pub override_flag: bool,
114}
115
116#[derive(Debug, Copy, Clone)]
118pub enum DynamicNeighborUpdateSource<A> {
119 Probe {
123 link_address: A,
126 },
127
128 Confirmation {
132 link_address: Option<A>,
135 flags: ConfirmationFlags,
137 },
138}
139
140#[derive(Derivative)]
142#[derivative(Debug(bound = ""))]
143#[cfg_attr(
144 any(test, feature = "testutils"),
145 derivative(
146 Clone(bound = "BT::TxMetadata: Clone"),
147 PartialEq(bound = "BT::TxMetadata: PartialEq"),
148 Eq(bound = "BT::TxMetadata: Eq")
149 )
150)]
151#[allow(missing_docs)]
152pub enum NeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
153 Dynamic(DynamicNeighborState<D, BT>),
154 Static(D::Address),
155}
156
157#[derive(Derivative)]
164#[derivative(Debug(bound = ""))]
165#[cfg_attr(
166 any(test, feature = "testutils"),
167 derivative(
168 Clone(bound = "BT::TxMetadata: Clone"),
169 PartialEq(bound = "BT::TxMetadata: PartialEq"),
170 Eq(bound = "BT::TxMetadata: Eq")
171 )
172)]
173pub enum DynamicNeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
174 Incomplete(Incomplete<D, BT::Notifier, BT::TxMetadata>),
180
181 Reachable(Reachable<D, BT::Instant>),
186
187 Stale(Stale<D>),
198
199 Delay(Delay<D>),
211
212 Probe(Probe<D>),
216
217 Unreachable(Unreachable<D>),
224}
225
226#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
241pub enum EventDynamicState<L: LinkUnicastAddress> {
242 Incomplete,
245 Reachable(L),
248 Stale(L),
256 Delay(L),
264 Probe(L),
269 Unreachable(L),
273}
274
275#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
283pub enum EventState<L: LinkUnicastAddress> {
284 Dynamic(EventDynamicState<L>),
286 Static(L),
288}
289
290#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
292pub enum EventKind<L: LinkUnicastAddress> {
293 Added(EventState<L>),
295 Changed(EventState<L>),
297 Removed,
299}
300
301#[derive(Debug, Eq, Hash, PartialEq, GenericOverIp)]
303#[generic_over_ip(I, Ip)]
304pub struct Event<L: LinkUnicastAddress, DeviceId, I: Ip, Instant> {
305 pub device: DeviceId,
307 pub addr: SpecifiedAddr<I::Addr>,
309 pub kind: EventKind<L>,
311 pub at: Instant,
313}
314
315impl<L: LinkUnicastAddress, DeviceId, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
316 pub fn map_device<N, F: FnOnce(DeviceId) -> N>(self, map: F) -> Event<L, N, I, Instant> {
318 let Self { device, kind, addr, at } = self;
319 Event { device: map(device), kind, addr, at }
320 }
321}
322
323impl<L: LinkUnicastAddress, DeviceId: Clone, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
324 fn changed(
325 device: &DeviceId,
326 event_state: EventState<L>,
327 addr: SpecifiedAddr<I::Addr>,
328 at: Instant,
329 ) -> Self {
330 Self { device: device.clone(), kind: EventKind::Changed(event_state), addr, at }
331 }
332
333 fn added(
334 device: &DeviceId,
335 event_state: EventState<L>,
336 addr: SpecifiedAddr<I::Addr>,
337 at: Instant,
338 ) -> Self {
339 Self { device: device.clone(), kind: EventKind::Added(event_state), addr, at }
340 }
341
342 fn removed(device: &DeviceId, addr: SpecifiedAddr<I::Addr>, at: Instant) -> Self {
343 Self { device: device.clone(), kind: EventKind::Removed, addr, at }
344 }
345}
346
347fn schedule_timer_if_should_retransmit<I, D, DeviceId, CC, BC>(
348 core_ctx: &mut CC,
349 bindings_ctx: &mut BC,
350 timers: &mut TimerHeap<I, BC>,
351 neighbor: SpecifiedAddr<I::Addr>,
352 event: NudEvent,
353 counter: &mut Option<NonZeroU16>,
354) -> bool
355where
356 I: Ip,
357 D: LinkDevice,
358 DeviceId: StrongDeviceIdentifier,
359 BC: NudBindingsContext<I, D, DeviceId>,
360 CC: NudConfigContext<I>,
361{
362 match counter {
363 Some(c) => {
364 *counter = NonZeroU16::new(c.get() - 1);
365 let retransmit_timeout = core_ctx.retransmit_timeout();
366 timers.schedule_neighbor(bindings_ctx, retransmit_timeout, neighbor, event);
367 true
368 }
369 None => false,
370 }
371}
372
373#[derive(Debug, Derivative)]
375#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq(bound = "M: PartialEq"), Eq))]
376pub struct Incomplete<D: LinkDevice, N: LinkResolutionNotifier<D>, M> {
377 transmit_counter: Option<NonZeroU16>,
378 pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
379 #[derivative(PartialEq = "ignore")]
380 notifiers: Vec<N>,
381 _marker: PhantomData<D>,
382}
383
384#[cfg(any(test, feature = "testutils"))]
385impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M: Clone> Clone for Incomplete<D, N, M> {
386 fn clone(&self) -> Self {
387 let Self { transmit_counter, pending_frames, notifiers: _, _marker } = self;
391 Self {
392 transmit_counter: transmit_counter.clone(),
393 pending_frames: pending_frames.clone(),
394 notifiers: Vec::new(),
395 _marker: PhantomData,
396 }
397 }
398}
399
400impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Drop for Incomplete<D, N, M> {
401 fn drop(&mut self) {
402 let Self { transmit_counter: _, pending_frames: _, notifiers, _marker } = self;
403 for notifier in notifiers.drain(..) {
404 notifier.notify(Err(AddressResolutionFailed));
405 }
406 }
407}
408
409impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Incomplete<D, N, M> {
410 #[cfg(any(test, feature = "testutils"))]
413 pub fn new_with_pending_frames_and_transmit_counter(
414 pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
415 transmit_counter: Option<NonZeroU16>,
416 ) -> Self {
417 Self {
418 transmit_counter,
419 pending_frames,
420 notifiers: Default::default(),
421 _marker: PhantomData,
422 }
423 }
424
425 fn new_with_packet<I, CC, BC, DeviceId, B, S>(
426 core_ctx: &mut CC,
427 bindings_ctx: &mut BC,
428 timers: &mut TimerHeap<I, BC>,
429 neighbor: SpecifiedAddr<I::Addr>,
430 packet: S,
431 meta: M,
432 ) -> Result<Self, ErrorAndSerializer<SerializeError<Never>, S>>
433 where
434 I: Ip,
435 D: LinkDevice,
436 BC: NudBindingsContext<I, D, DeviceId>,
437 CC: NudConfigContext<I>,
438 DeviceId: StrongDeviceIdentifier,
439 B: BufferMut,
440 S: Serializer<Buffer = B>,
441 {
442 let packet = packet
446 .serialize_vec_outer()
447 .map_err(|(error, serializer)| ErrorAndSerializer { error, serializer })?
448 .map_a(|b| Buf::new(b.as_ref().to_vec(), ..))
449 .into_inner();
450
451 let mut this = Incomplete {
452 transmit_counter: Some(core_ctx.max_multicast_solicit()),
453 pending_frames: VecDeque::from([(packet, meta)]),
454 notifiers: Vec::new(),
455 _marker: PhantomData,
456 };
457 assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
461
462 Ok(this)
463 }
464
465 fn new_with_notifier<I, CC, BC, DeviceId>(
466 core_ctx: &mut CC,
467 bindings_ctx: &mut BC,
468 timers: &mut TimerHeap<I, BC>,
469 neighbor: SpecifiedAddr<I::Addr>,
470 notifier: BC::Notifier,
471 ) -> Self
472 where
473 I: Ip,
474 D: LinkDevice,
475 BC: NudBindingsContext<I, D, DeviceId, Notifier = N>,
476 CC: NudConfigContext<I>,
477 DeviceId: StrongDeviceIdentifier,
478 {
479 let mut this = Incomplete {
480 transmit_counter: Some(core_ctx.max_multicast_solicit()),
481 pending_frames: VecDeque::new(),
482 notifiers: [notifier].into(),
483 _marker: PhantomData,
484 };
485 assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
489
490 this
491 }
492
493 fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
494 &mut self,
495 core_ctx: &mut CC,
496 bindings_ctx: &mut BC,
497 timers: &mut TimerHeap<I, BC>,
498 neighbor: SpecifiedAddr<I::Addr>,
499 ) -> bool
500 where
501 I: Ip,
502 D: LinkDevice,
503 DeviceId: StrongDeviceIdentifier,
504 BC: NudBindingsContext<I, D, DeviceId>,
505 CC: NudConfigContext<I>,
506 {
507 let Self { transmit_counter, pending_frames: _, notifiers: _, _marker } = self;
508 schedule_timer_if_should_retransmit(
509 core_ctx,
510 bindings_ctx,
511 timers,
512 neighbor,
513 NudEvent::RetransmitMulticastProbe,
514 transmit_counter,
515 )
516 }
517
518 fn queue_packet<B, S>(
519 &mut self,
520 body: S,
521 meta: M,
522 ) -> Result<(), ErrorAndSerializer<SerializeError<Never>, S>>
523 where
524 B: BufferMut,
525 S: Serializer<Buffer = B>,
526 {
527 let Self { pending_frames, transmit_counter: _, notifiers: _, _marker } = self;
528
529 if pending_frames.len() < MAX_PENDING_FRAMES {
536 pending_frames.push_back((
537 body.serialize_vec_outer()
538 .map_err(|(error, serializer)| ErrorAndSerializer { error, serializer })?
539 .map_a(|b| Buf::new(b.as_ref().to_vec(), ..))
540 .into_inner(),
541 meta,
542 ));
543 }
544 Ok(())
545 }
546
547 fn complete_resolution<I, CC, BC>(
550 &mut self,
551 core_ctx: &mut CC,
552 bindings_ctx: &mut BC,
553 link_address: D::Address,
554 ) where
555 I: Ip,
556 D: LinkDevice,
557 BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata = M>,
558 CC: NudSenderContext<I, D, BC>,
559 {
560 let Self { pending_frames, notifiers, transmit_counter: _, _marker } = self;
561
562 for (body, meta) in pending_frames.drain(..) {
570 core_ctx
574 .send_ip_packet_to_neighbor_link_addr(bindings_ctx, link_address, body, meta)
575 .unwrap_or_else(|err| {
576 error!("failed to send pending IP packet to neighbor {link_address:?} {err:?}")
577 })
578 }
579 for notifier in notifiers.drain(..) {
580 notifier.notify(Ok(link_address));
581 }
582 }
583}
584
585#[derive(Debug, Derivative)]
587#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
588pub struct Reachable<D: LinkDevice, I: Instant> {
589 pub link_address: D::Address,
591 pub last_confirmed_at: I,
593}
594
595#[derive(Debug, Derivative)]
597#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
598pub struct Stale<D: LinkDevice> {
599 pub link_address: D::Address,
601}
602
603impl<D: LinkDevice> Stale<D> {
604 fn enter_delay<I, BC, DeviceId: Clone>(
605 &mut self,
606 bindings_ctx: &mut BC,
607 timers: &mut TimerHeap<I, BC>,
608 neighbor: SpecifiedAddr<I::Addr>,
609 ) -> Delay<D>
610 where
611 I: Ip,
612 BC: NudBindingsContext<I, D, DeviceId>,
613 {
614 let Self { link_address } = *self;
615
616 timers.schedule_neighbor(
619 bindings_ctx,
620 DELAY_FIRST_PROBE_TIME,
621 neighbor,
622 NudEvent::DelayFirstProbe,
623 );
624
625 Delay { link_address }
626 }
627}
628
629#[derive(Debug, Derivative)]
631#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
632pub struct Delay<D: LinkDevice> {
633 pub link_address: D::Address,
635}
636
637impl<D: LinkDevice> Delay<D> {
638 fn enter_probe<I, DeviceId, CC, BC>(
639 &mut self,
640 core_ctx: &mut CC,
641 bindings_ctx: &mut BC,
642 timers: &mut TimerHeap<I, BC>,
643 neighbor: SpecifiedAddr<I::Addr>,
644 ) -> Probe<D>
645 where
646 I: Ip,
647 DeviceId: StrongDeviceIdentifier,
648 BC: NudBindingsContext<I, D, DeviceId>,
649 CC: NudConfigContext<I>,
650 {
651 let Self { link_address } = *self;
652
653 let retransmit_timeout = core_ctx.retransmit_timeout();
657 timers.schedule_neighbor(
658 bindings_ctx,
659 retransmit_timeout,
660 neighbor,
661 NudEvent::RetransmitUnicastProbe,
662 );
663
664 Probe {
665 link_address,
666 transmit_counter: NonZeroU16::new(core_ctx.max_unicast_solicit().get() - 1),
667 }
668 }
669}
670
671#[derive(Debug, Derivative)]
672#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
673pub struct Probe<D: LinkDevice> {
674 link_address: D::Address,
675 transmit_counter: Option<NonZeroU16>,
676}
677
678impl<D: LinkDevice> Probe<D> {
679 fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
680 &mut self,
681 core_ctx: &mut CC,
682 bindings_ctx: &mut BC,
683 timers: &mut TimerHeap<I, BC>,
684 neighbor: SpecifiedAddr<I::Addr>,
685 ) -> bool
686 where
687 I: Ip,
688 DeviceId: StrongDeviceIdentifier,
689 BC: NudBindingsContext<I, D, DeviceId>,
690 CC: NudConfigContext<I>,
691 {
692 let Self { link_address: _, transmit_counter } = self;
693 schedule_timer_if_should_retransmit(
694 core_ctx,
695 bindings_ctx,
696 timers,
697 neighbor,
698 NudEvent::RetransmitUnicastProbe,
699 transmit_counter,
700 )
701 }
702
703 fn enter_unreachable<I, BC, DeviceId>(
704 &mut self,
705 bindings_ctx: &mut BC,
706 timers: &mut TimerHeap<I, BC>,
707 num_entries: usize,
708 last_gc: &mut Option<BC::Instant>,
709 ) -> Unreachable<D>
710 where
711 I: Ip,
712 BC: NudBindingsContext<I, D, DeviceId>,
713 DeviceId: Clone,
714 {
715 timers.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
719
720 let Self { link_address, transmit_counter: _ } = self;
721 Unreachable { link_address: *link_address, mode: UnreachableMode::WaitingForPacketSend }
722 }
723}
724
725#[derive(Debug, Derivative)]
726#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
727pub struct Unreachable<D: LinkDevice> {
728 link_address: D::Address,
729 mode: UnreachableMode,
730}
731
732#[derive(Debug, Clone, Copy, Derivative)]
744#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq, Eq))]
745pub(crate) enum UnreachableMode {
746 WaitingForPacketSend,
747 Backoff { probes_sent: NonZeroU32, packet_sent: bool },
748}
749
750impl UnreachableMode {
751 fn next_backoff_retransmit_timeout<I, CC>(&self, core_ctx: &mut CC) -> NonZeroDuration
757 where
758 I: Ip,
759 CC: NudConfigContext<I>,
760 {
761 let probes_sent = match self {
762 UnreachableMode::Backoff { probes_sent, packet_sent: _ } => probes_sent,
763 UnreachableMode::WaitingForPacketSend => {
764 panic!("cannot calculate exponential backoff in state {self:?}")
765 }
766 };
767 (core_ctx.retransmit_timeout() * BACKOFF_MULTIPLE.saturating_pow(probes_sent.get()))
771 .min(MAX_RETRANS_TIMER)
772 }
773}
774
775impl<D: LinkDevice> Unreachable<D> {
776 fn handle_timer<I, DeviceId, CC, BC>(
777 &mut self,
778 core_ctx: &mut CC,
779 bindings_ctx: &mut BC,
780 timers: &mut TimerHeap<I, BC>,
781 device_id: &DeviceId,
782 neighbor: SpecifiedAddr<I::Addr>,
783 ) -> Option<TransmitProbe<D::Address>>
784 where
785 I: Ip,
786 DeviceId: StrongDeviceIdentifier,
787 BC: NudBindingsContext<I, D, DeviceId>,
788 CC: NudConfigContext<I>,
789 {
790 let Self { link_address: _, mode } = self;
791 match mode {
792 UnreachableMode::WaitingForPacketSend => {
793 panic!(
794 "timer should not have fired in UNREACHABLE while waiting for packet send; got \
795 a retransmit multicast probe event for {neighbor} on {device_id:?}",
796 );
797 }
798 UnreachableMode::Backoff { probes_sent, packet_sent } => {
799 if *packet_sent {
800 *probes_sent = probes_sent.saturating_add(1);
807 *packet_sent = false;
808
809 let duration = mode.next_backoff_retransmit_timeout(core_ctx);
810 timers.schedule_neighbor(
811 bindings_ctx,
812 duration,
813 neighbor,
814 NudEvent::RetransmitMulticastProbe,
815 );
816
817 Some(TransmitProbe::Multicast)
818 } else {
819 *mode = UnreachableMode::WaitingForPacketSend;
820
821 None
822 }
823 }
824 }
825 }
826
827 fn handle_packet_queued_to_send<I, DeviceId, CC, BC>(
832 &mut self,
833 core_ctx: &mut CC,
834 bindings_ctx: &mut BC,
835 timers: &mut TimerHeap<I, BC>,
836 neighbor: SpecifiedAddr<I::Addr>,
837 ) -> bool
838 where
839 I: Ip,
840 DeviceId: StrongDeviceIdentifier,
841 BC: NudBindingsContext<I, D, DeviceId>,
842 CC: NudConfigContext<I>,
843 {
844 let Self { link_address: _, mode } = self;
845 match mode {
846 UnreachableMode::WaitingForPacketSend => {
847 let probes_sent = NonZeroU32::new(1).unwrap();
862 *mode = UnreachableMode::Backoff { probes_sent, packet_sent: false };
863
864 let duration = mode.next_backoff_retransmit_timeout(core_ctx);
865 timers.schedule_neighbor(
866 bindings_ctx,
867 duration,
868 neighbor,
869 NudEvent::RetransmitMulticastProbe,
870 );
871
872 true
874 }
875 UnreachableMode::Backoff { probes_sent: _, packet_sent } => {
876 *packet_sent = true;
880
881 false
882 }
883 }
884 }
885}
886
887impl<D: LinkDevice, BT: NudBindingsTypes<D>> NeighborState<D, BT> {
888 fn to_event_state(&self) -> EventState<D::Address> {
889 match self {
890 NeighborState::Dynamic(dynamic_state) => {
891 EventState::Dynamic(dynamic_state.to_event_dynamic_state())
892 }
893 NeighborState::Static(addr) => EventState::Static(*addr),
894 }
895 }
896}
897
898impl<D: LinkDevice, BC: NudBindingsTypes<D>> DynamicNeighborState<D, BC> {
899 fn cancel_timer<I, DeviceId>(
900 &mut self,
901 bindings_ctx: &mut BC,
902 timers: &mut TimerHeap<I, BC>,
903 neighbor: SpecifiedAddr<I::Addr>,
904 ) where
905 I: Ip,
906 DeviceId: StrongDeviceIdentifier,
907 BC: NudBindingsContext<I, D, DeviceId>,
908 {
909 let expected_event = match self {
910 DynamicNeighborState::Incomplete(Incomplete {
911 transmit_counter: _,
912 pending_frames: _,
913 notifiers: _,
914 _marker,
915 }) => Some(NudEvent::RetransmitMulticastProbe),
916 DynamicNeighborState::Reachable(Reachable {
917 link_address: _,
918 last_confirmed_at: _,
919 }) => Some(NudEvent::ReachableTime),
920 DynamicNeighborState::Stale(Stale { link_address: _ }) => None,
921 DynamicNeighborState::Delay(Delay { link_address: _ }) => {
922 Some(NudEvent::DelayFirstProbe)
923 }
924 DynamicNeighborState::Probe(Probe { link_address: _, transmit_counter: _ }) => {
925 Some(NudEvent::RetransmitUnicastProbe)
926 }
927 DynamicNeighborState::Unreachable(Unreachable { link_address: _, mode }) => {
928 match mode {
931 UnreachableMode::WaitingForPacketSend => None,
932 UnreachableMode::Backoff { probes_sent: _, packet_sent: _ } => {
933 Some(NudEvent::RetransmitMulticastProbe)
934 }
935 }
936 }
937 };
938 assert_eq!(
939 timers.cancel_neighbor(bindings_ctx, neighbor),
940 expected_event,
941 "neighbor {neighbor} ({self:?}) had unexpected timer installed"
942 );
943 }
944
945 fn cancel_timer_and_complete_resolution<I, CC>(
946 mut self,
947 core_ctx: &mut CC,
948 bindings_ctx: &mut BC,
949 timers: &mut TimerHeap<I, BC>,
950 neighbor: SpecifiedAddr<I::Addr>,
951 link_address: D::Address,
952 ) where
953 I: Ip,
954 BC: NudBindingsContext<I, D, CC::DeviceId>,
955 CC: NudSenderContext<I, D, BC>,
956 {
957 self.cancel_timer(bindings_ctx, timers, neighbor);
958
959 match self {
960 DynamicNeighborState::Incomplete(mut incomplete) => {
961 incomplete.complete_resolution(core_ctx, bindings_ctx, link_address);
962 }
963 DynamicNeighborState::Reachable(_)
964 | DynamicNeighborState::Stale(_)
965 | DynamicNeighborState::Delay(_)
966 | DynamicNeighborState::Probe(_)
967 | DynamicNeighborState::Unreachable(_) => {}
968 }
969 }
970
971 fn to_event_dynamic_state(&self) -> EventDynamicState<D::Address> {
972 match self {
973 Self::Incomplete(_) => EventDynamicState::Incomplete,
974 Self::Reachable(Reachable { link_address, last_confirmed_at: _ }) => {
975 EventDynamicState::Reachable(*link_address)
976 }
977 Self::Stale(Stale { link_address }) => EventDynamicState::Stale(*link_address),
978 Self::Delay(Delay { link_address }) => EventDynamicState::Delay(*link_address),
979 Self::Probe(Probe { link_address, transmit_counter: _ }) => {
980 EventDynamicState::Probe(*link_address)
981 }
982 Self::Unreachable(Unreachable { link_address, mode: _ }) => {
983 EventDynamicState::Unreachable(*link_address)
984 }
985 }
986 }
987
988 fn enter_reachable<I, CC>(
990 &mut self,
991 core_ctx: &mut CC,
992 bindings_ctx: &mut BC,
993 timers: &mut TimerHeap<I, BC>,
994 device_id: &CC::DeviceId,
995 neighbor: SpecifiedAddr<I::Addr>,
996 link_address: D::Address,
997 ) where
998 I: Ip,
999 BC: NudBindingsContext<I, D, CC::DeviceId>,
1000 CC: NudSenderContext<I, D, BC>,
1001 {
1002 let now = bindings_ctx.now();
1005 match self {
1006 DynamicNeighborState::Reachable(Reachable {
1011 link_address: current,
1012 last_confirmed_at,
1013 }) if *current == link_address => {
1014 *last_confirmed_at = now;
1015 return;
1016 }
1017 DynamicNeighborState::Incomplete(_)
1018 | DynamicNeighborState::Reachable(_)
1019 | DynamicNeighborState::Stale(_)
1020 | DynamicNeighborState::Delay(_)
1021 | DynamicNeighborState::Probe(_)
1022 | DynamicNeighborState::Unreachable(_) => {}
1023 }
1024 let previous = core::mem::replace(
1025 self,
1026 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: now }),
1027 );
1028 let event_dynamic_state = self.to_event_dynamic_state();
1029 debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1030 let event_state = EventState::Dynamic(event_dynamic_state);
1031 bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1032 previous.cancel_timer_and_complete_resolution(
1033 core_ctx,
1034 bindings_ctx,
1035 timers,
1036 neighbor,
1037 link_address,
1038 );
1039 timers.schedule_neighbor(
1040 bindings_ctx,
1041 core_ctx.base_reachable_time(),
1042 neighbor,
1043 NudEvent::ReachableTime,
1044 );
1045 }
1046
1047 fn enter_stale<I, CC>(
1055 &mut self,
1056 core_ctx: &mut CC,
1057 bindings_ctx: &mut BC,
1058 timers: &mut TimerHeap<I, BC>,
1059 device_id: &CC::DeviceId,
1060 neighbor: SpecifiedAddr<I::Addr>,
1061 link_address: D::Address,
1062 num_entries: usize,
1063 last_gc: &mut Option<BC::Instant>,
1064 ) where
1065 I: Ip,
1066 BC: NudBindingsContext<I, D, CC::DeviceId>,
1067 CC: NudSenderContext<I, D, BC>,
1068 {
1069 let previous =
1072 core::mem::replace(self, DynamicNeighborState::Stale(Stale { link_address }));
1073 let event_dynamic_state = self.to_event_dynamic_state();
1074 debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1075 let event_state = EventState::Dynamic(event_dynamic_state);
1076 bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1077 previous.cancel_timer_and_complete_resolution(
1078 core_ctx,
1079 bindings_ctx,
1080 timers,
1081 neighbor,
1082 link_address,
1083 );
1084
1085 timers.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
1089
1090 }
1093
1094 fn resolve_link_addr<I, DeviceId, CC>(
1100 &mut self,
1101 core_ctx: &mut CC,
1102 bindings_ctx: &mut BC,
1103 timers: &mut TimerHeap<I, BC>,
1104 device_id: &DeviceId,
1105 neighbor: SpecifiedAddr<I::Addr>,
1106 ) -> (
1107 LinkResolutionResult<
1108 D::Address,
1109 <<BC as LinkResolutionContext<D>>::Notifier as LinkResolutionNotifier<D>>::Observer,
1110 >,
1111 bool,
1112 )
1113 where
1114 I: Ip,
1115 DeviceId: StrongDeviceIdentifier,
1116 BC: NudBindingsContext<I, D, DeviceId>,
1117 CC: NudConfigContext<I>,
1118 {
1119 match self {
1120 DynamicNeighborState::Incomplete(Incomplete {
1121 notifiers,
1122 transmit_counter: _,
1123 pending_frames: _,
1124 _marker,
1125 }) => {
1126 let (notifier, observer) = BC::Notifier::new();
1127 notifiers.push(notifier);
1128
1129 (LinkResolutionResult::Pending(observer), false)
1130 }
1131 DynamicNeighborState::Stale(entry) => {
1132 let delay @ Delay { link_address } =
1141 entry.enter_delay(bindings_ctx, timers, neighbor);
1142 *self = DynamicNeighborState::Delay(delay);
1143 let event_state = EventState::Dynamic(self.to_event_dynamic_state());
1144 bindings_ctx.on_event(Event::changed(
1145 device_id,
1146 event_state,
1147 neighbor,
1148 bindings_ctx.now(),
1149 ));
1150
1151 (LinkResolutionResult::Resolved(link_address), false)
1152 }
1153 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1154 | DynamicNeighborState::Delay(Delay { link_address })
1155 | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1156 (LinkResolutionResult::Resolved(*link_address), false)
1157 }
1158 DynamicNeighborState::Unreachable(unreachable) => {
1159 let Unreachable { link_address, mode: _ } = unreachable;
1160 let link_address = *link_address;
1161
1162 let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1164 core_ctx,
1165 bindings_ctx,
1166 timers,
1167 neighbor,
1168 );
1169 (LinkResolutionResult::Resolved(link_address), do_multicast_solicit)
1170 }
1171 }
1172 }
1173
1174 fn handle_packet_queued_to_send<I, CC, S>(
1180 &mut self,
1181 core_ctx: &mut CC,
1182 bindings_ctx: &mut BC,
1183 timers: &mut TimerHeap<I, BC>,
1184 device_id: &CC::DeviceId,
1185 neighbor: SpecifiedAddr<I::Addr>,
1186 body: S,
1187 meta: BC::TxMetadata,
1188 ) -> Result<bool, SendFrameError<S>>
1189 where
1190 I: Ip,
1191 BC: NudBindingsContext<I, D, CC::DeviceId>,
1192 CC: NudSenderContext<I, D, BC>,
1193 S: Serializer,
1194 S::Buffer: BufferMut,
1195 {
1196 match self {
1197 DynamicNeighborState::Incomplete(incomplete) => {
1198 incomplete.queue_packet(body, meta).map(|()| false).map_err(|e| e.err_into())
1199 }
1200 DynamicNeighborState::Stale(entry) => {
1207 let delay @ Delay { link_address } =
1215 entry.enter_delay(bindings_ctx, timers, neighbor);
1216 *self = DynamicNeighborState::Delay(delay);
1217 let event_state = EventState::Dynamic(self.to_event_dynamic_state());
1218 bindings_ctx.on_event(Event::changed(
1219 device_id,
1220 event_state,
1221 neighbor,
1222 bindings_ctx.now(),
1223 ));
1224
1225 core_ctx.send_ip_packet_to_neighbor_link_addr(
1226 bindings_ctx,
1227 link_address,
1228 body,
1229 meta,
1230 )?;
1231
1232 Ok(false)
1233 }
1234 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1235 | DynamicNeighborState::Delay(Delay { link_address })
1236 | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1237 core_ctx.send_ip_packet_to_neighbor_link_addr(
1238 bindings_ctx,
1239 *link_address,
1240 body,
1241 meta,
1242 )?;
1243
1244 Ok(false)
1245 }
1246 DynamicNeighborState::Unreachable(unreachable) => {
1247 let Unreachable { link_address, mode: _ } = unreachable;
1248 core_ctx.send_ip_packet_to_neighbor_link_addr(
1249 bindings_ctx,
1250 *link_address,
1251 body,
1252 meta,
1253 )?;
1254
1255 let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1256 core_ctx,
1257 bindings_ctx,
1258 timers,
1259 neighbor,
1260 );
1261 Ok(do_multicast_solicit)
1262 }
1263 }
1264 }
1265
1266 fn handle_probe<I, CC>(
1267 &mut self,
1268 core_ctx: &mut CC,
1269 bindings_ctx: &mut BC,
1270 timers: &mut TimerHeap<I, BC>,
1271 device_id: &CC::DeviceId,
1272 neighbor: SpecifiedAddr<I::Addr>,
1273 link_address: D::Address,
1274 num_entries: usize,
1275 last_gc: &mut Option<BC::Instant>,
1276 ) where
1277 I: Ip,
1278 BC: NudBindingsContext<I, D, CC::DeviceId>,
1279 CC: NudSenderContext<I, D, BC>,
1280 {
1281 let transition_to_stale = match self {
1290 DynamicNeighborState::Incomplete(_) => true,
1291 DynamicNeighborState::Reachable(Reachable {
1292 link_address: current,
1293 last_confirmed_at: _,
1294 })
1295 | DynamicNeighborState::Stale(Stale { link_address: current })
1296 | DynamicNeighborState::Delay(Delay { link_address: current })
1297 | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1298 | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1299 current != &link_address
1300 }
1301 };
1302 if transition_to_stale {
1303 self.enter_stale(
1304 core_ctx,
1305 bindings_ctx,
1306 timers,
1307 device_id,
1308 neighbor,
1309 link_address,
1310 num_entries,
1311 last_gc,
1312 );
1313 }
1314 }
1315
1316 fn handle_confirmation<I, CC>(
1317 &mut self,
1318 core_ctx: &mut CC,
1319 bindings_ctx: &mut BC,
1320 timers: &mut TimerHeap<I, BC>,
1321 device_id: &CC::DeviceId,
1322 neighbor: SpecifiedAddr<I::Addr>,
1323 link_address: Option<D::Address>,
1324 flags: ConfirmationFlags,
1325 num_entries: usize,
1326 last_gc: &mut Option<BC::Instant>,
1327 ) where
1328 I: Ip,
1329 BC: NudBindingsContext<I, D, CC::DeviceId>,
1330 CC: NudSenderContext<I, D, BC>,
1331 {
1332 let ConfirmationFlags { solicited_flag, override_flag } = flags;
1333 enum NewState<A> {
1334 Reachable { link_address: A },
1335 Stale { link_address: A },
1336 }
1337
1338 let new_state = match self {
1339 DynamicNeighborState::Incomplete(Incomplete {
1340 transmit_counter: _,
1341 pending_frames: _,
1342 notifiers: _,
1343 _marker,
1344 }) => {
1345 link_address.map(|link_address| {
1357 if solicited_flag {
1358 NewState::Reachable { link_address }
1359 } else {
1360 NewState::Stale { link_address }
1361 }
1362 })
1363 }
1364 DynamicNeighborState::Reachable(Reachable {
1365 link_address: current,
1366 last_confirmed_at: _,
1367 })
1368 | DynamicNeighborState::Stale(Stale { link_address: current })
1369 | DynamicNeighborState::Delay(Delay { link_address: current })
1370 | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1371 | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1372 let updated_link_address = link_address
1391 .and_then(|link_address| (current != &link_address).then_some(link_address));
1392
1393 match (solicited_flag, updated_link_address, override_flag) {
1394 (true, _, true) | (true, None, _) => {
1400 Some(NewState::Reachable { link_address: link_address.unwrap_or(*current) })
1401 }
1402 (_, Some(_), false) => match self {
1412 DynamicNeighborState::Reachable(Reachable {
1414 link_address,
1415 last_confirmed_at: _,
1416 }) => Some(NewState::Stale { link_address: *link_address }),
1417 DynamicNeighborState::Stale(_)
1419 | DynamicNeighborState::Delay(_)
1420 | DynamicNeighborState::Probe(_)
1421 | DynamicNeighborState::Unreachable(_) => None,
1422 DynamicNeighborState::Incomplete(_) => unreachable!(),
1424 },
1425 (false, Some(link_address), true) => Some(NewState::Stale { link_address }),
1431 (false, None, _) => None,
1436 }
1437 }
1438 };
1439 match new_state {
1440 Some(NewState::Reachable { link_address }) => self.enter_reachable(
1441 core_ctx,
1442 bindings_ctx,
1443 timers,
1444 device_id,
1445 neighbor,
1446 link_address,
1447 ),
1448 Some(NewState::Stale { link_address }) => self.enter_stale(
1449 core_ctx,
1450 bindings_ctx,
1451 timers,
1452 device_id,
1453 neighbor,
1454 link_address,
1455 num_entries,
1456 last_gc,
1457 ),
1458 None => {}
1459 }
1460 }
1461}
1462
1463#[cfg(any(test, feature = "testutils"))]
1464pub(crate) mod testutil {
1465 use super::*;
1466
1467 use alloc::sync::Arc;
1468
1469 use netstack3_base::sync::Mutex;
1470 use netstack3_base::testutil::{FakeBindingsCtx, FakeCoreCtx};
1471
1472 pub fn assert_dynamic_neighbor_with_addr<
1474 I: Ip,
1475 D: LinkDevice,
1476 BC: NudBindingsContext<I, D, CC::DeviceId>,
1477 CC: NudContext<I, D, BC>,
1478 >(
1479 core_ctx: &mut CC,
1480 device_id: CC::DeviceId,
1481 neighbor: SpecifiedAddr<I::Addr>,
1482 expected_link_addr: D::Address,
1483 ) {
1484 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1485 assert_matches!(
1486 neighbors.get(&neighbor),
1487 Some(NeighborState::Dynamic(
1488 DynamicNeighborState::Reachable(Reachable{ link_address, last_confirmed_at: _ })
1489 | DynamicNeighborState::Stale(Stale{ link_address })
1490 )) => {
1491 assert_eq!(link_address, &expected_link_addr)
1492 }
1493 )
1494 })
1495 }
1496
1497 pub fn assert_dynamic_neighbor_state<I, D, BC, CC>(
1499 core_ctx: &mut CC,
1500 device_id: CC::DeviceId,
1501 neighbor: SpecifiedAddr<I::Addr>,
1502 expected_state: DynamicNeighborState<D, BC>,
1503 ) where
1504 I: Ip,
1505 D: LinkDevice + PartialEq,
1506 BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata: PartialEq>,
1507 CC: NudContext<I, D, BC>,
1508 {
1509 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1510 assert_matches!(
1511 neighbors.get(&neighbor),
1512 Some(NeighborState::Dynamic(state)) => {
1513 assert_eq!(state, &expected_state)
1514 }
1515 )
1516 })
1517 }
1518
1519 pub fn assert_neighbor_unknown<
1521 I: Ip,
1522 D: LinkDevice,
1523 BC: NudBindingsContext<I, D, CC::DeviceId>,
1524 CC: NudContext<I, D, BC>,
1525 >(
1526 core_ctx: &mut CC,
1527 device_id: CC::DeviceId,
1528 neighbor: SpecifiedAddr<I::Addr>,
1529 ) {
1530 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1531 assert_matches!(neighbors.get(&neighbor), None)
1532 })
1533 }
1534
1535 impl<D: LinkDevice, Id, Event: Debug, State, FrameMeta> LinkResolutionContext<D>
1536 for FakeBindingsCtx<Id, Event, State, FrameMeta>
1537 {
1538 type Notifier = FakeLinkResolutionNotifier<D>;
1539 }
1540
1541 #[derive(Debug)]
1543 pub struct FakeLinkResolutionNotifier<D: LinkDevice>(
1544 Arc<Mutex<Option<Result<D::Address, AddressResolutionFailed>>>>,
1545 );
1546
1547 impl<D: LinkDevice> LinkResolutionNotifier<D> for FakeLinkResolutionNotifier<D> {
1548 type Observer = Arc<Mutex<Option<Result<D::Address, AddressResolutionFailed>>>>;
1549
1550 fn new() -> (Self, Self::Observer) {
1551 let inner = Arc::new(Mutex::new(None));
1552 (Self(inner.clone()), inner)
1553 }
1554
1555 fn notify(self, result: Result<D::Address, AddressResolutionFailed>) {
1556 let Self(inner) = self;
1557 let mut inner = inner.lock();
1558 assert_eq!(*inner, None, "resolved link address was set more than once");
1559 *inner = Some(result);
1560 }
1561 }
1562
1563 impl<S, Meta, DeviceId> UseDelegateNudContext for FakeCoreCtx<S, Meta, DeviceId> where
1564 S: UseDelegateNudContext
1565 {
1566 }
1567 impl<I: Ip, S, Meta, DeviceId> DelegateNudContext<I> for FakeCoreCtx<S, Meta, DeviceId>
1568 where
1569 S: DelegateNudContext<I>,
1570 {
1571 type Delegate<T> = S::Delegate<T>;
1572 }
1573}
1574
1575#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1576enum NudEvent {
1577 RetransmitMulticastProbe,
1578 ReachableTime,
1579 DelayFirstProbe,
1580 RetransmitUnicastProbe,
1581}
1582
1583#[derive(GenericOverIp, Copy, Clone, Debug, Eq, PartialEq, Hash)]
1585#[generic_over_ip(I, Ip)]
1586pub struct NudTimerId<I: Ip, L: LinkDevice, D: WeakDeviceIdentifier> {
1587 device_id: D,
1588 timer_type: NudTimerType,
1589 _marker: PhantomData<(I, L)>,
1590}
1591
1592#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1593enum NudTimerType {
1594 Neighbor,
1595 GarbageCollection,
1596}
1597
1598#[derive(Debug)]
1600struct TimerHeap<I: Ip, BT: TimerBindingsTypes + InstantBindingsTypes> {
1601 gc: BT::Timer,
1602 neighbor: LocalTimerHeap<SpecifiedAddr<I::Addr>, NudEvent, BT>,
1603}
1604
1605impl<I: Ip, BC: TimerContext> TimerHeap<I, BC> {
1606 fn new<
1607 DeviceId: WeakDeviceIdentifier,
1608 L: LinkDevice,
1609 CC: CoreTimerContext<NudTimerId<I, L, DeviceId>, BC>,
1610 >(
1611 bindings_ctx: &mut BC,
1612 device_id: DeviceId,
1613 ) -> Self {
1614 Self {
1615 neighbor: LocalTimerHeap::new_with_context::<_, CC>(
1616 bindings_ctx,
1617 NudTimerId {
1618 device_id: device_id.clone(),
1619 timer_type: NudTimerType::Neighbor,
1620 _marker: PhantomData,
1621 },
1622 ),
1623 gc: CC::new_timer(
1624 bindings_ctx,
1625 NudTimerId {
1626 device_id,
1627 timer_type: NudTimerType::GarbageCollection,
1628 _marker: PhantomData,
1629 },
1630 ),
1631 }
1632 }
1633
1634 fn schedule_neighbor(
1635 &mut self,
1636 bindings_ctx: &mut BC,
1637 after: NonZeroDuration,
1638 neighbor: SpecifiedAddr<I::Addr>,
1639 event: NudEvent,
1640 ) {
1641 let Self { neighbor: heap, gc: _ } = self;
1642 assert_eq!(heap.schedule_after(bindings_ctx, neighbor, event, after.get()), None);
1643 }
1644
1645 fn schedule_neighbor_at(
1646 &mut self,
1647 bindings_ctx: &mut BC,
1648 at: BC::Instant,
1649 neighbor: SpecifiedAddr<I::Addr>,
1650 event: NudEvent,
1651 ) {
1652 let Self { neighbor: heap, gc: _ } = self;
1653 assert_eq!(heap.schedule_instant(bindings_ctx, neighbor, event, at), None);
1654 }
1655
1656 fn cancel_neighbor(
1658 &mut self,
1659 bindings_ctx: &mut BC,
1660 neighbor: SpecifiedAddr<I::Addr>,
1661 ) -> Option<NudEvent> {
1662 let Self { neighbor: heap, gc: _ } = self;
1663 heap.cancel(bindings_ctx, &neighbor).map(|(_instant, v)| v)
1664 }
1665
1666 fn pop_neighbor(
1667 &mut self,
1668 bindings_ctx: &mut BC,
1669 ) -> Option<(SpecifiedAddr<I::Addr>, NudEvent)> {
1670 let Self { neighbor: heap, gc: _ } = self;
1671 heap.pop(bindings_ctx)
1672 }
1673
1674 fn maybe_schedule_gc(
1677 &mut self,
1678 bindings_ctx: &mut BC,
1679 num_entries: usize,
1680 last_gc: &Option<BC::Instant>,
1681 ) {
1682 let Self { gc, neighbor: _ } = self;
1683 if num_entries > MAX_ENTRIES && bindings_ctx.scheduled_instant(gc).is_none() {
1684 let instant = if let Some(last_gc) = last_gc {
1685 last_gc.panicking_add(MIN_GARBAGE_COLLECTION_INTERVAL.get())
1686 } else {
1687 bindings_ctx.now()
1688 };
1689 assert_eq!(bindings_ctx.schedule_timer_instant(instant, gc), None);
1693 }
1694 }
1695}
1696
1697#[derive(Debug)]
1699pub struct NudState<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> {
1700 neighbors: HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>>,
1702 last_gc: Option<BT::Instant>,
1703 timer_heap: TimerHeap<I, BT>,
1704}
1705
1706impl<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> NudState<I, D, BT> {
1707 #[cfg(any(test, feature = "testutils"))]
1709 pub fn neighbors(&self) -> &HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>> {
1710 &self.neighbors
1711 }
1712
1713 fn entry_and_timer_heap(
1714 &mut self,
1715 addr: SpecifiedAddr<I::Addr>,
1716 ) -> (Entry<'_, SpecifiedAddr<I::Addr>, NeighborState<D, BT>>, &mut TimerHeap<I, BT>) {
1717 let Self { neighbors, timer_heap, .. } = self;
1718 (neighbors.entry(addr), timer_heap)
1719 }
1720}
1721
1722impl<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D> + TimerContext> NudState<I, D, BC> {
1723 pub fn new<
1725 DeviceId: WeakDeviceIdentifier,
1726 CC: CoreTimerContext<NudTimerId<I, D, DeviceId>, BC>,
1727 >(
1728 bindings_ctx: &mut BC,
1729 device_id: DeviceId,
1730 ) -> Self {
1731 Self {
1732 neighbors: Default::default(),
1733 last_gc: None,
1734 timer_heap: TimerHeap::new::<_, _, CC>(bindings_ctx, device_id),
1735 }
1736 }
1737}
1738
1739pub trait NudBindingsContext<I: Ip, D: LinkDevice, DeviceId>:
1741 TimerContext
1742 + LinkResolutionContext<D>
1743 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1744 + NudBindingsTypes<D>
1745{
1746}
1747
1748impl<
1749 I: Ip,
1750 D: LinkDevice,
1751 DeviceId,
1752 BC: TimerContext
1753 + LinkResolutionContext<D>
1754 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1755 + NudBindingsTypes<D>,
1756> NudBindingsContext<I, D, DeviceId> for BC
1757{
1758}
1759
1760pub trait NudBindingsTypes<D: LinkDevice>:
1762 LinkResolutionContext<D> + InstantBindingsTypes + TimerBindingsTypes + TxMetadataBindingsTypes
1763{
1764}
1765
1766impl<BT, D> NudBindingsTypes<D> for BT
1767where
1768 D: LinkDevice,
1769 BT: LinkResolutionContext<D>
1770 + InstantBindingsTypes
1771 + TimerBindingsTypes
1772 + TxMetadataBindingsTypes,
1773{
1774}
1775
1776pub trait LinkResolutionContext<D: LinkDevice> {
1778 type Notifier: LinkResolutionNotifier<D>;
1781}
1782
1783pub trait LinkResolutionNotifier<D: LinkDevice>: Debug + Sized + Send {
1786 type Observer;
1789
1790 fn new() -> (Self, Self::Observer);
1792
1793 fn notify(self, result: Result<D::Address, AddressResolutionFailed>);
1796}
1797
1798pub trait NudContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
1800 type ConfigCtx<'a>: NudConfigContext<I>;
1802 type SenderCtx<'a>: NudSenderContext<I, D, BC, DeviceId = Self::DeviceId>;
1804
1805 fn with_nud_state_mut_and_sender_ctx<
1808 O,
1809 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1810 >(
1811 &mut self,
1812 device_id: &Self::DeviceId,
1813 cb: F,
1814 ) -> O;
1815
1816 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1819 &mut self,
1820 device_id: &Self::DeviceId,
1821 cb: F,
1822 ) -> O;
1823
1824 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1826 &mut self,
1827 device_id: &Self::DeviceId,
1828 cb: F,
1829 ) -> O;
1830
1831 fn send_neighbor_solicitation(
1836 &mut self,
1837 bindings_ctx: &mut BC,
1838 device_id: &Self::DeviceId,
1839 lookup_addr: SpecifiedAddr<I::Addr>,
1840 remote_link_addr: Option<D::Address>,
1841 );
1842}
1843
1844pub trait UseDelegateNudContext {}
1847
1848pub trait DelegateNudContext<I: Ip>: UseDelegateNudContext + Sized {
1854 type Delegate<T>: ref_cast::RefCast<From = T>;
1856 fn wrap(&mut self) -> &mut Self::Delegate<Self> {
1858 <Self::Delegate<Self> as ref_cast::RefCast>::ref_cast_mut(self)
1859 }
1860}
1861
1862impl<I, D, BC, CC> NudContext<I, D, BC> for CC
1863where
1864 I: Ip,
1865 D: LinkDevice,
1866 BC: NudBindingsTypes<D>,
1867 CC: DelegateNudContext<I, Delegate<CC>: NudContext<I, D, BC, DeviceId = CC::DeviceId>>
1868 + UseDelegateNudContext
1871 + DeviceIdContext<D>,
1872{
1873 type ConfigCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::ConfigCtx<'a>;
1874 type SenderCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::SenderCtx<'a>;
1875 fn with_nud_state_mut_and_sender_ctx<
1876 O,
1877 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1878 >(
1879 &mut self,
1880 device_id: &Self::DeviceId,
1881 cb: F,
1882 ) -> O {
1883 self.wrap().with_nud_state_mut_and_sender_ctx(device_id, cb)
1884 }
1885
1886 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1887 &mut self,
1888 device_id: &Self::DeviceId,
1889 cb: F,
1890 ) -> O {
1891 self.wrap().with_nud_state_mut(device_id, cb)
1892 }
1893 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1894 &mut self,
1895 device_id: &Self::DeviceId,
1896 cb: F,
1897 ) -> O {
1898 self.wrap().with_nud_state(device_id, cb)
1899 }
1900 fn send_neighbor_solicitation(
1901 &mut self,
1902 bindings_ctx: &mut BC,
1903 device_id: &Self::DeviceId,
1904 lookup_addr: SpecifiedAddr<I::Addr>,
1905 remote_link_addr: Option<D::Address>,
1906 ) {
1907 self.wrap().send_neighbor_solicitation(
1908 bindings_ctx,
1909 device_id,
1910 lookup_addr,
1911 remote_link_addr,
1912 )
1913 }
1914}
1915
1916pub trait NudIcmpIpExt: packet_formats::ip::IpExt {
1918 type Metadata;
1921
1922 fn extract_metadata<B: SplitByteSlice>(packet: &Self::Packet<B>) -> Self::Metadata;
1924}
1925
1926impl NudIcmpIpExt for Ipv4 {
1927 type Metadata = (usize, Ipv4FragmentType);
1928
1929 fn extract_metadata<B: SplitByteSlice>(packet: &Ipv4Packet<B>) -> Self::Metadata {
1930 (packet.header_len(), packet.fragment_type())
1931 }
1932}
1933
1934impl NudIcmpIpExt for Ipv6 {
1935 type Metadata = ();
1936
1937 fn extract_metadata<B: SplitByteSlice>(_: &Ipv6Packet<B>) -> () {}
1938}
1939
1940pub trait NudIcmpContext<I: NudIcmpIpExt, D: LinkDevice, BC>: DeviceIdContext<D> {
1943 fn send_icmp_dest_unreachable(
1950 &mut self,
1951 bindings_ctx: &mut BC,
1952 frame: Buf<Vec<u8>>,
1953 device_id: Option<&Self::DeviceId>,
1954 original_src_ip: SocketIpAddr<I::Addr>,
1955 original_dst_ip: SocketIpAddr<I::Addr>,
1956 metadata: I::Metadata,
1957 );
1958}
1959
1960#[derive(Clone, Debug)]
1962pub struct NudUserConfig {
1963 pub max_unicast_solicitations: NonZeroU16,
1968 pub max_multicast_solicitations: NonZeroU16,
1973 pub base_reachable_time: NonZeroDuration,
1979 pub retrans_timer: NonZeroDuration,
1985}
1986
1987impl Default for NudUserConfig {
1988 fn default() -> Self {
1989 NudUserConfig {
1990 max_unicast_solicitations: DEFAULT_MAX_UNICAST_SOLICIT,
1991 max_multicast_solicitations: DEFAULT_MAX_MULTICAST_SOLICIT,
1992 base_reachable_time: DEFAULT_BASE_REACHABLE_TIME,
1993 retrans_timer: DEFAULT_RETRANS_TIMER,
1994 }
1995 }
1996}
1997
1998#[allow(missing_docs)]
2002#[derive(Clone, Debug, Eq, PartialEq, Default)]
2003pub struct NudUserConfigUpdate {
2004 pub max_unicast_solicitations: Option<NonZeroU16>,
2005 pub max_multicast_solicitations: Option<NonZeroU16>,
2006 pub base_reachable_time: Option<NonZeroDuration>,
2007 pub retrans_timer: Option<NonZeroDuration>,
2008}
2009
2010impl NudUserConfigUpdate {
2011 pub fn apply_and_take_previous(mut self, config: &mut NudUserConfig) -> Self {
2014 fn swap_if_set<T>(opt: &mut Option<T>, target: &mut T) {
2015 if let Some(opt) = opt.as_mut() {
2016 core::mem::swap(opt, target)
2017 }
2018 }
2019 let Self {
2020 max_unicast_solicitations,
2021 max_multicast_solicitations,
2022 base_reachable_time,
2023 retrans_timer,
2024 } = &mut self;
2025 swap_if_set(max_unicast_solicitations, &mut config.max_unicast_solicitations);
2026 swap_if_set(max_multicast_solicitations, &mut config.max_multicast_solicitations);
2027 swap_if_set(base_reachable_time, &mut config.base_reachable_time);
2028 swap_if_set(retrans_timer, &mut config.retrans_timer);
2029
2030 self
2031 }
2032}
2033
2034pub trait NudConfigContext<I: Ip> {
2037 fn retransmit_timeout(&mut self) -> NonZeroDuration;
2044
2045 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O;
2047
2048 fn max_unicast_solicit(&mut self) -> NonZeroU16 {
2050 self.with_nud_user_config(|NudUserConfig { max_unicast_solicitations, .. }| {
2051 *max_unicast_solicitations
2052 })
2053 }
2054
2055 fn max_multicast_solicit(&mut self) -> NonZeroU16 {
2057 self.with_nud_user_config(|NudUserConfig { max_multicast_solicitations, .. }| {
2058 *max_multicast_solicitations
2059 })
2060 }
2061
2062 fn base_reachable_time(&mut self) -> NonZeroDuration {
2065 self.with_nud_user_config(|NudUserConfig { base_reachable_time, .. }| *base_reachable_time)
2066 }
2067}
2068
2069pub trait NudSenderContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>:
2072 NudConfigContext<I> + DeviceIdContext<D>
2073{
2074 fn send_ip_packet_to_neighbor_link_addr<S>(
2076 &mut self,
2077 bindings_ctx: &mut BC,
2078 neighbor_link_addr: D::Address,
2079 body: S,
2080 meta: BC::TxMetadata,
2081 ) -> Result<(), SendFrameError<S>>
2082 where
2083 S: Serializer,
2084 S::Buffer: BufferMut;
2085}
2086
2087pub trait NudIpHandler<I: Ip, BC>: DeviceIdContext<AnyDevice> {
2089 fn handle_neighbor_probe(
2094 &mut self,
2095 bindings_ctx: &mut BC,
2096 device_id: &Self::DeviceId,
2097 neighbor: SpecifiedAddr<I::Addr>,
2098 link_addr: &[u8],
2099 );
2100
2101 fn handle_neighbor_confirmation(
2105 &mut self,
2106 bindings_ctx: &mut BC,
2107 device_id: &Self::DeviceId,
2108 neighbor: SpecifiedAddr<I::Addr>,
2109 link_addr: Option<&[u8]>,
2110 flags: ConfirmationFlags,
2111 );
2112
2113 fn flush_neighbor_table(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2115}
2116
2117#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2119pub enum LinkResolutionResult<A: LinkAddress, Observer> {
2120 Resolved(A),
2122 Pending(Observer),
2124}
2125
2126pub trait NudHandler<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
2128 fn handle_neighbor_update(
2131 &mut self,
2132 bindings_ctx: &mut BC,
2133 device_id: &Self::DeviceId,
2134 neighbor: SpecifiedAddr<I::Addr>,
2139 source: DynamicNeighborUpdateSource<D::Address>,
2140 );
2141
2142 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2144
2145 fn send_ip_packet_to_neighbor<S>(
2150 &mut self,
2151 bindings_ctx: &mut BC,
2152 device_id: &Self::DeviceId,
2153 neighbor: SpecifiedAddr<I::Addr>,
2154 body: S,
2155 meta: BC::TxMetadata,
2156 ) -> Result<(), SendFrameError<S>>
2157 where
2158 S: Serializer,
2159 S::Buffer: BufferMut;
2160}
2161
2162enum TransmitProbe<A> {
2163 Multicast,
2164 Unicast(A),
2165}
2166
2167impl<
2168 I: NudIcmpIpExt,
2169 D: LinkDevice,
2170 BC: NudBindingsContext<I, D, CC::DeviceId>,
2171 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2172> HandleableTimer<CC, BC> for NudTimerId<I, D, CC::WeakDeviceId>
2173{
2174 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
2175 let Self { device_id, timer_type, _marker: PhantomData } = self;
2176 let Some(device_id) = device_id.upgrade() else {
2177 return;
2178 };
2179 match timer_type {
2180 NudTimerType::Neighbor => handle_neighbor_timer(core_ctx, bindings_ctx, device_id),
2181 NudTimerType::GarbageCollection => collect_garbage(core_ctx, bindings_ctx, device_id),
2182 }
2183 }
2184}
2185
2186fn handle_neighbor_timer<I, D, CC, BC>(
2187 core_ctx: &mut CC,
2188 bindings_ctx: &mut BC,
2189 device_id: CC::DeviceId,
2190) where
2191 I: NudIcmpIpExt,
2192 D: LinkDevice,
2193 BC: NudBindingsContext<I, D, CC::DeviceId>,
2194 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2195{
2196 enum Action<L, A, M> {
2197 TransmitProbe { probe: TransmitProbe<L>, to: A },
2198 SendIcmpDestUnreachable(VecDeque<(Buf<Vec<u8>>, M)>),
2199 }
2200 let action = core_ctx.with_nud_state_mut(
2201 &device_id,
2202 |NudState { neighbors, last_gc, timer_heap }, core_ctx| {
2203 let (lookup_addr, event) = timer_heap.pop_neighbor(bindings_ctx)?;
2204 let num_entries = neighbors.len();
2205 let mut entry = match neighbors.entry(lookup_addr) {
2206 Entry::Occupied(entry) => entry,
2207 Entry::Vacant(_) => panic!("timer fired for invalid entry"),
2208 };
2209
2210 match entry.get_mut() {
2211 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)) => {
2212 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2213
2214 if incomplete.schedule_timer_if_should_retransmit(
2215 core_ctx,
2216 bindings_ctx,
2217 timer_heap,
2218 lookup_addr,
2219 ) {
2220 Some(Action::TransmitProbe {
2221 probe: TransmitProbe::Multicast,
2222 to: lookup_addr,
2223 })
2224 } else {
2225 debug!("neighbor resolution failed for {lookup_addr}; removing entry");
2233 let Incomplete {
2234 transmit_counter: _,
2235 ref mut pending_frames,
2236 notifiers: _,
2237 _marker,
2238 } = assert_matches!(
2239 entry.remove(),
2240 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete))
2241 => incomplete
2242 );
2243 let pending_frames = core::mem::take(pending_frames);
2244 bindings_ctx.on_event(Event::removed(
2245 &device_id,
2246 lookup_addr,
2247 bindings_ctx.now(),
2248 ));
2249 Some(Action::SendIcmpDestUnreachable(pending_frames))
2250 }
2251 }
2252 NeighborState::Dynamic(DynamicNeighborState::Probe(probe)) => {
2253 assert_eq!(event, NudEvent::RetransmitUnicastProbe);
2254
2255 let Probe { link_address, transmit_counter: _ } = probe;
2256 let link_address = *link_address;
2257 if probe.schedule_timer_if_should_retransmit(
2258 core_ctx,
2259 bindings_ctx,
2260 timer_heap,
2261 lookup_addr,
2262 ) {
2263 Some(Action::TransmitProbe {
2264 probe: TransmitProbe::Unicast(link_address),
2265 to: lookup_addr,
2266 })
2267 } else {
2268 let unreachable =
2269 probe.enter_unreachable(bindings_ctx, timer_heap, num_entries, last_gc);
2270 *entry.get_mut() =
2271 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable));
2272 let event_state = entry.get_mut().to_event_state();
2273 let event = Event::changed(
2274 &device_id,
2275 event_state,
2276 lookup_addr,
2277 bindings_ctx.now(),
2278 );
2279 bindings_ctx.on_event(event);
2280 None
2281 }
2282 }
2283 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable)) => {
2284 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2285 unreachable
2286 .handle_timer(core_ctx, bindings_ctx, timer_heap, &device_id, lookup_addr)
2287 .map(|probe| Action::TransmitProbe { probe, to: lookup_addr })
2288 }
2289 NeighborState::Dynamic(DynamicNeighborState::Reachable(Reachable {
2290 link_address,
2291 last_confirmed_at,
2292 })) => {
2293 assert_eq!(event, NudEvent::ReachableTime);
2294 let link_address = *link_address;
2295
2296 let expiration =
2297 last_confirmed_at.saturating_add(core_ctx.base_reachable_time().get());
2298 if expiration > bindings_ctx.now() {
2299 timer_heap.schedule_neighbor_at(
2300 bindings_ctx,
2301 expiration,
2302 lookup_addr,
2303 NudEvent::ReachableTime,
2304 );
2305 } else {
2306 *entry.get_mut() =
2314 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2315 link_address,
2316 }));
2317 let event_state = entry.get_mut().to_event_state();
2318 let event = Event::changed(
2319 &device_id,
2320 event_state,
2321 lookup_addr,
2322 bindings_ctx.now(),
2323 );
2324 bindings_ctx.on_event(event);
2325
2326 timer_heap.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
2330 }
2331
2332 None
2333 }
2334 NeighborState::Dynamic(DynamicNeighborState::Delay(delay)) => {
2335 assert_eq!(event, NudEvent::DelayFirstProbe);
2336
2337 let probe @ Probe { link_address, transmit_counter: _ } =
2344 delay.enter_probe(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2345 *entry.get_mut() = NeighborState::Dynamic(DynamicNeighborState::Probe(probe));
2346 let event_state = entry.get_mut().to_event_state();
2347 bindings_ctx.on_event(Event::changed(
2348 &device_id,
2349 event_state,
2350 lookup_addr,
2351 bindings_ctx.now(),
2352 ));
2353
2354 Some(Action::TransmitProbe {
2355 probe: TransmitProbe::Unicast(link_address),
2356 to: lookup_addr,
2357 })
2358 }
2359 state @ (NeighborState::Static(_)
2360 | NeighborState::Dynamic(DynamicNeighborState::Stale(_))) => {
2361 panic!("timer unexpectedly fired in state {state:?}")
2362 }
2363 }
2364 },
2365 );
2366
2367 match action {
2368 Some(Action::SendIcmpDestUnreachable(mut pending_frames)) => {
2369 for (mut frame, meta) in pending_frames.drain(..) {
2370 core::mem::drop(meta);
2373
2374 let Some((packet, original_src_ip, original_dst_ip)) = frame
2378 .parse_mut::<I::Packet<_>>()
2379 .map_err(|e| {
2380 warn!("not sending ICMP dest unreachable due to parsing error: {:?}", e);
2381 })
2382 .ok()
2383 .and_then(|packet| {
2384 let original_src_ip = SocketIpAddr::new(packet.src_ip())?;
2385 let original_dst_ip = SocketIpAddr::new(packet.dst_ip())?;
2386 Some((packet, original_src_ip, original_dst_ip))
2387 })
2388 .or_else(|| {
2389 core_ctx.counters().icmp_dest_unreachable_dropped.increment();
2390 None
2391 })
2392 else {
2393 continue;
2394 };
2395 let header_metadata = I::extract_metadata(&packet);
2396 let metadata = packet.parse_metadata();
2397 core::mem::drop(packet);
2398 frame.undo_parse(metadata);
2399 core_ctx.send_icmp_dest_unreachable(
2400 bindings_ctx,
2401 frame,
2402 original_src_ip.as_ref().must_have_zone().then_some(&device_id),
2414 original_src_ip,
2415 original_dst_ip,
2416 header_metadata,
2417 );
2418 }
2419 }
2420 Some(Action::TransmitProbe { probe, to }) => {
2421 let remote_link_addr = match probe {
2422 TransmitProbe::Multicast => None,
2423 TransmitProbe::Unicast(link_addr) => Some(link_addr),
2424 };
2425 core_ctx.send_neighbor_solicitation(bindings_ctx, &device_id, to, remote_link_addr);
2426 }
2427 None => {}
2428 }
2429}
2430
2431impl<I: Ip, D: LinkDevice, BC: NudBindingsContext<I, D, CC::DeviceId>, CC: NudContext<I, D, BC>>
2432 NudHandler<I, D, BC> for CC
2433{
2434 fn handle_neighbor_update(
2435 &mut self,
2436 bindings_ctx: &mut BC,
2437 device_id: &CC::DeviceId,
2438 neighbor: SpecifiedAddr<I::Addr>,
2439 source: DynamicNeighborUpdateSource<D::Address>,
2440 ) {
2441 debug!("received neighbor {:?} from {}", source, neighbor);
2442 self.with_nud_state_mut_and_sender_ctx(
2443 device_id,
2444 |NudState { neighbors, last_gc, timer_heap }, core_ctx| {
2445 let num_entries = neighbors.len();
2446 match neighbors.entry(neighbor) {
2447 Entry::Vacant(e) => match source {
2448 DynamicNeighborUpdateSource::Probe { link_address } => {
2449 insert_new_entry(
2457 bindings_ctx,
2458 device_id,
2459 e,
2460 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2461 link_address,
2462 })),
2463 );
2464
2465 timer_heap.maybe_schedule_gc(bindings_ctx, neighbors.len(), last_gc);
2468 }
2469 DynamicNeighborUpdateSource::Confirmation { .. } => {}
2478 },
2479 Entry::Occupied(e) => match e.into_mut() {
2480 NeighborState::Dynamic(e) => match source {
2481 DynamicNeighborUpdateSource::Probe { link_address } => e.handle_probe(
2482 core_ctx,
2483 bindings_ctx,
2484 timer_heap,
2485 device_id,
2486 neighbor,
2487 link_address,
2488 num_entries,
2489 last_gc,
2490 ),
2491 DynamicNeighborUpdateSource::Confirmation { link_address, flags } => e
2492 .handle_confirmation(
2493 core_ctx,
2494 bindings_ctx,
2495 timer_heap,
2496 device_id,
2497 neighbor,
2498 link_address,
2499 flags,
2500 num_entries,
2501 last_gc,
2502 ),
2503 },
2504 NeighborState::Static(_) => {}
2505 },
2506 }
2507 },
2508 );
2509 }
2510
2511 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
2512 self.with_nud_state_mut(
2513 device_id,
2514 |NudState { neighbors, last_gc: _, timer_heap }, _config| {
2515 neighbors.drain().for_each(|(neighbor, state)| {
2516 match state {
2517 NeighborState::Dynamic(mut entry) => {
2518 entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
2519 }
2520 NeighborState::Static(_) => {}
2521 }
2522 bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
2523 });
2524 },
2525 );
2526 }
2527
2528 fn send_ip_packet_to_neighbor<S>(
2529 &mut self,
2530 bindings_ctx: &mut BC,
2531 device_id: &Self::DeviceId,
2532 lookup_addr: SpecifiedAddr<I::Addr>,
2533 body: S,
2534 meta: BC::TxMetadata,
2535 ) -> Result<(), SendFrameError<S>>
2536 where
2537 S: Serializer,
2538 S::Buffer: BufferMut,
2539 {
2540 let do_multicast_solicit = self.with_nud_state_mut_and_sender_ctx(
2541 device_id,
2542 |state, core_ctx| -> Result<_, SendFrameError<S>> {
2543 let (entry, timer_heap) = state.entry_and_timer_heap(lookup_addr);
2544 match entry {
2545 Entry::Vacant(e) => {
2546 let incomplete = Incomplete::new_with_packet(
2547 core_ctx,
2548 bindings_ctx,
2549 timer_heap,
2550 lookup_addr,
2551 body,
2552 meta,
2553 )
2554 .map_err(|e| e.err_into())?;
2555 insert_new_entry(
2556 bindings_ctx,
2557 device_id,
2558 e,
2559 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)),
2560 );
2561 Ok(true)
2562 }
2563 Entry::Occupied(e) => {
2564 match e.into_mut() {
2565 NeighborState::Static(link_address) => {
2566 core_ctx.send_ip_packet_to_neighbor_link_addr(
2574 bindings_ctx,
2575 *link_address,
2576 body,
2577 meta,
2578 )?;
2579
2580 Ok(false)
2581 }
2582 NeighborState::Dynamic(e) => {
2583 let do_multicast_solicit = e.handle_packet_queued_to_send(
2584 core_ctx,
2585 bindings_ctx,
2586 timer_heap,
2587 device_id,
2588 lookup_addr,
2589 body,
2590 meta,
2591 )?;
2592
2593 Ok(do_multicast_solicit)
2594 }
2595 }
2596 }
2597 }
2598 },
2599 )?;
2600
2601 if do_multicast_solicit {
2602 self.send_neighbor_solicitation(
2603 bindings_ctx,
2604 &device_id,
2605 lookup_addr,
2606 None,
2607 );
2608 }
2609
2610 Ok(())
2611 }
2612}
2613
2614fn insert_new_entry<
2615 I: Ip,
2616 D: LinkDevice,
2617 DeviceId: DeviceIdentifier,
2618 BC: NudBindingsContext<I, D, DeviceId>,
2619>(
2620 bindings_ctx: &mut BC,
2621 device_id: &DeviceId,
2622 vacant: hash_map::VacantEntry<'_, SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2623 entry: NeighborState<D, BC>,
2624) {
2625 let lookup_addr = *vacant.key();
2626 let state = vacant.insert(entry);
2627 let event = Event::added(device_id, state.to_event_state(), lookup_addr, bindings_ctx.now());
2628 bindings_ctx.on_event(event);
2629}
2630
2631pub fn confirm_reachable<I, D, CC, BC>(
2634 core_ctx: &mut CC,
2635 bindings_ctx: &mut BC,
2636 device_id: &CC::DeviceId,
2637 neighbor: SpecifiedAddr<I::Addr>,
2638) where
2639 I: Ip,
2640 D: LinkDevice,
2641 BC: NudBindingsContext<I, D, CC::DeviceId>,
2642 CC: NudContext<I, D, BC>,
2643{
2644 core_ctx.with_nud_state_mut_and_sender_ctx(
2645 device_id,
2646 |NudState { neighbors, last_gc: _, timer_heap }, core_ctx| {
2647 match neighbors.entry(neighbor) {
2648 Entry::Vacant(_) => {
2649 debug!(
2650 "got an upper-layer confirmation for non-existent neighbor entry {}",
2651 neighbor
2652 );
2653 }
2654 Entry::Occupied(e) => match e.into_mut() {
2655 NeighborState::Static(_) => {}
2656 NeighborState::Dynamic(e) => {
2657 let link_address = match e {
2667 DynamicNeighborState::Incomplete(_) => return,
2668 DynamicNeighborState::Reachable(Reachable {
2669 link_address,
2670 last_confirmed_at: _,
2671 })
2672 | DynamicNeighborState::Stale(Stale { link_address })
2673 | DynamicNeighborState::Delay(Delay { link_address })
2674 | DynamicNeighborState::Probe(Probe {
2675 link_address,
2676 transmit_counter: _,
2677 })
2678 | DynamicNeighborState::Unreachable(Unreachable {
2679 link_address,
2680 mode: _,
2681 }) => *link_address,
2682 };
2683 e.enter_reachable(
2684 core_ctx,
2685 bindings_ctx,
2686 timer_heap,
2687 device_id,
2688 neighbor,
2689 link_address,
2690 );
2691 }
2692 },
2693 }
2694 },
2695 );
2696}
2697
2698fn collect_garbage<I, D, CC, BC>(core_ctx: &mut CC, bindings_ctx: &mut BC, device_id: CC::DeviceId)
2709where
2710 I: Ip,
2711 D: LinkDevice,
2712 BC: NudBindingsContext<I, D, CC::DeviceId>,
2713 CC: NudContext<I, D, BC>,
2714{
2715 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, last_gc, timer_heap }, _| {
2716 let max_to_remove = neighbors.len().saturating_sub(MAX_ENTRIES);
2717 if max_to_remove == 0 {
2718 return;
2719 }
2720
2721 *last_gc = Some(bindings_ctx.now());
2722
2723 fn gc_priority<D: LinkDevice, BT: NudBindingsTypes<D>>(
2732 state: &DynamicNeighborState<D, BT>,
2733 ) -> usize {
2734 match state {
2735 DynamicNeighborState::Incomplete(_)
2736 | DynamicNeighborState::Reachable(_)
2737 | DynamicNeighborState::Delay(_)
2738 | DynamicNeighborState::Probe(_) => unreachable!(
2739 "the netstack should only ever discard STALE or UNREACHABLE entries; \
2740 found {:?}",
2741 state,
2742 ),
2743 DynamicNeighborState::Stale(_) => 0,
2744 DynamicNeighborState::Unreachable(Unreachable {
2745 link_address: _,
2746 mode: UnreachableMode::Backoff { probes_sent: _, packet_sent: _ },
2747 }) => 1,
2748 DynamicNeighborState::Unreachable(Unreachable {
2749 link_address: _,
2750 mode: UnreachableMode::WaitingForPacketSend,
2751 }) => 2,
2752 }
2753 }
2754
2755 struct SortEntry<'a, K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> {
2756 key: K,
2757 state: &'a mut DynamicNeighborState<D, BT>,
2758 }
2759
2760 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialEq for SortEntry<'_, K, D, BT> {
2761 fn eq(&self, other: &Self) -> bool {
2762 self.key == other.key && gc_priority(self.state) == gc_priority(other.state)
2763 }
2764 }
2765 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Eq for SortEntry<'_, K, D, BT> {}
2766 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Ord for SortEntry<'_, K, D, BT> {
2767 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2768 gc_priority(self.state).cmp(&gc_priority(other.state)).reverse()
2772 }
2773 }
2774 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialOrd for SortEntry<'_, K, D, BT> {
2775 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
2776 Some(self.cmp(&other))
2777 }
2778 }
2779
2780 let mut entries_to_remove = BinaryHeap::with_capacity(max_to_remove);
2781 for (ip, neighbor) in neighbors.iter_mut() {
2782 match neighbor {
2783 NeighborState::Static(_) => {
2784 continue;
2786 }
2787 NeighborState::Dynamic(state) => {
2788 match state {
2789 DynamicNeighborState::Incomplete(_)
2790 | DynamicNeighborState::Reachable(_)
2791 | DynamicNeighborState::Delay(_)
2792 | DynamicNeighborState::Probe(_) => {
2793 continue;
2795 }
2796 DynamicNeighborState::Stale(_) | DynamicNeighborState::Unreachable(_) => {
2797 if entries_to_remove.len() < max_to_remove {
2799 entries_to_remove.push(SortEntry { key: ip, state });
2800 continue;
2801 }
2802 let minimum = entries_to_remove
2806 .peek()
2807 .expect("heap should have at least 1 entry");
2808 let candidate = SortEntry { key: ip, state };
2809 if &candidate > minimum {
2810 let _: SortEntry<'_, _, _, _> = entries_to_remove.pop().unwrap();
2811 entries_to_remove.push(candidate);
2812 }
2813 }
2814 }
2815 }
2816 }
2817 }
2818
2819 let entries_to_remove = entries_to_remove
2820 .into_iter()
2821 .map(|SortEntry { key: neighbor, state }| {
2822 state.cancel_timer(bindings_ctx, timer_heap, *neighbor);
2823 *neighbor
2824 })
2825 .collect::<Vec<_>>();
2826
2827 for neighbor in entries_to_remove {
2828 assert_matches!(neighbors.remove(&neighbor), Some(_));
2829 bindings_ctx.on_event(Event::removed(&device_id, neighbor, bindings_ctx.now()));
2830 }
2831 })
2832}
2833
2834#[cfg(test)]
2835mod tests {
2836 use alloc::vec;
2837
2838 use ip_test_macro::ip_test;
2839 use net_declare::{net_ip_v4, net_ip_v6};
2840 use net_types::ip::{Ipv4Addr, Ipv6Addr};
2841 use netstack3_base::testutil::{
2842 FakeBindingsCtx, FakeCoreCtx, FakeInstant, FakeLinkAddress, FakeLinkDevice,
2843 FakeLinkDeviceId, FakeTimerCtxExt as _, FakeTxMetadata, FakeWeakDeviceId,
2844 };
2845 use netstack3_base::{
2846 CtxPair, InstantContext, IntoCoreTimerCtx, SendFrameContext as _, SendFrameErrorReason,
2847 };
2848 use netstack3_hashmap::HashSet;
2849 use test_case::test_case;
2850
2851 use super::*;
2852 use crate::internal::device::nud::api::NeighborApi;
2853
2854 struct FakeNudContext<I: Ip, D: LinkDevice> {
2855 state: NudState<I, D, FakeBindingsCtxImpl<I>>,
2856 counters: NudCounters<I>,
2857 }
2858
2859 struct FakeConfigContext {
2860 retrans_timer: NonZeroDuration,
2861 nud_config: NudUserConfig,
2862 }
2863
2864 struct FakeCoreCtxImpl<I: Ip> {
2865 nud: FakeNudContext<I, FakeLinkDevice>,
2866 inner: FakeInnerCtxImpl<I>,
2867 }
2868
2869 type FakeInnerCtxImpl<I> =
2870 FakeCoreCtx<FakeConfigContext, FakeNudMessageMeta<I>, FakeLinkDeviceId>;
2871
2872 #[derive(Debug, PartialEq, Eq)]
2873 enum FakeNudMessageMeta<I: Ip> {
2874 NeighborSolicitation {
2875 lookup_addr: SpecifiedAddr<I::Addr>,
2876 remote_link_addr: Option<FakeLinkAddress>,
2877 },
2878 IpFrame {
2879 dst_link_address: FakeLinkAddress,
2880 },
2881 IcmpDestUnreachable,
2882 }
2883
2884 type FakeBindingsCtxImpl<I> = FakeBindingsCtx<
2885 NudTimerId<I, FakeLinkDevice, FakeWeakDeviceId<FakeLinkDeviceId>>,
2886 Event<FakeLinkAddress, FakeLinkDeviceId, I, FakeInstant>,
2887 (),
2888 (),
2889 >;
2890
2891 impl<I: Ip> FakeCoreCtxImpl<I> {
2892 fn new(bindings_ctx: &mut FakeBindingsCtxImpl<I>) -> Self {
2893 Self {
2894 nud: {
2895 FakeNudContext {
2896 state: NudState::new::<_, IntoCoreTimerCtx>(
2897 bindings_ctx,
2898 FakeWeakDeviceId(FakeLinkDeviceId),
2899 ),
2900 counters: Default::default(),
2901 }
2902 },
2903 inner: FakeInnerCtxImpl::with_state(FakeConfigContext {
2904 retrans_timer: ONE_SECOND,
2905 nud_config: NudUserConfig {
2909 max_unicast_solicitations: NonZeroU16::new(4).unwrap(),
2910 max_multicast_solicitations: NonZeroU16::new(5).unwrap(),
2911 base_reachable_time: NonZeroDuration::from_secs(23).unwrap(),
2912 retrans_timer: NonZeroDuration::from_secs(3).unwrap(),
2913 },
2914 }),
2915 }
2916 }
2917 }
2918
2919 fn new_context<I: Ip>() -> CtxPair<FakeCoreCtxImpl<I>, FakeBindingsCtxImpl<I>> {
2920 CtxPair::with_default_bindings_ctx(|bindings_ctx| FakeCoreCtxImpl::<I>::new(bindings_ctx))
2921 }
2922
2923 impl<I: Ip> DeviceIdContext<FakeLinkDevice> for FakeCoreCtxImpl<I> {
2924 type DeviceId = FakeLinkDeviceId;
2925 type WeakDeviceId = FakeWeakDeviceId<FakeLinkDeviceId>;
2926 }
2927
2928 impl<I: Ip> NudContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeCoreCtxImpl<I> {
2929 type ConfigCtx<'a> = FakeConfigContext;
2930
2931 type SenderCtx<'a> = FakeInnerCtxImpl<I>;
2932
2933 fn with_nud_state_mut_and_sender_ctx<
2934 O,
2935 F: FnOnce(
2936 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
2937 &mut Self::SenderCtx<'_>,
2938 ) -> O,
2939 >(
2940 &mut self,
2941 _device_id: &Self::DeviceId,
2942 cb: F,
2943 ) -> O {
2944 cb(&mut self.nud.state, &mut self.inner)
2945 }
2946
2947 fn with_nud_state_mut<
2948 O,
2949 F: FnOnce(
2950 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
2951 &mut Self::ConfigCtx<'_>,
2952 ) -> O,
2953 >(
2954 &mut self,
2955 &FakeLinkDeviceId: &FakeLinkDeviceId,
2956 cb: F,
2957 ) -> O {
2958 cb(&mut self.nud.state, &mut self.inner.state)
2959 }
2960
2961 fn with_nud_state<
2962 O,
2963 F: FnOnce(&NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>) -> O,
2964 >(
2965 &mut self,
2966 &FakeLinkDeviceId: &FakeLinkDeviceId,
2967 cb: F,
2968 ) -> O {
2969 cb(&self.nud.state)
2970 }
2971
2972 fn send_neighbor_solicitation(
2973 &mut self,
2974 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
2975 &FakeLinkDeviceId: &FakeLinkDeviceId,
2976 lookup_addr: SpecifiedAddr<I::Addr>,
2977 remote_link_addr: Option<FakeLinkAddress>,
2978 ) {
2979 self.inner
2980 .send_frame(
2981 bindings_ctx,
2982 FakeNudMessageMeta::NeighborSolicitation { lookup_addr, remote_link_addr },
2983 Buf::new(Vec::new(), ..),
2984 )
2985 .unwrap()
2986 }
2987 }
2988
2989 impl<I: NudIcmpIpExt> NudIcmpContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>
2990 for FakeCoreCtxImpl<I>
2991 {
2992 fn send_icmp_dest_unreachable(
2993 &mut self,
2994 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
2995 frame: Buf<Vec<u8>>,
2996 _device_id: Option<&Self::DeviceId>,
2997 _original_src_ip: SocketIpAddr<I::Addr>,
2998 _original_dst_ip: SocketIpAddr<I::Addr>,
2999 _header_len: I::Metadata,
3000 ) {
3001 self.inner
3002 .send_frame(bindings_ctx, FakeNudMessageMeta::IcmpDestUnreachable, frame)
3003 .unwrap()
3004 }
3005 }
3006
3007 impl<I: Ip> CounterContext<NudCounters<I>> for FakeCoreCtxImpl<I> {
3008 fn counters(&self) -> &NudCounters<I> {
3009 &self.nud.counters
3010 }
3011 }
3012
3013 impl<I: Ip> NudConfigContext<I> for FakeConfigContext {
3014 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3015 self.retrans_timer
3016 }
3017
3018 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3019 cb(&self.nud_config)
3020 }
3021 }
3022
3023 impl<I: Ip> NudSenderContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeInnerCtxImpl<I> {
3024 fn send_ip_packet_to_neighbor_link_addr<S>(
3025 &mut self,
3026 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3027 dst_link_address: FakeLinkAddress,
3028 body: S,
3029 _tx_meta: FakeTxMetadata,
3030 ) -> Result<(), SendFrameError<S>>
3031 where
3032 S: Serializer,
3033 S::Buffer: BufferMut,
3034 {
3035 self.send_frame(bindings_ctx, FakeNudMessageMeta::IpFrame { dst_link_address }, body)
3036 }
3037 }
3038
3039 impl<I: Ip> NudConfigContext<I> for FakeInnerCtxImpl<I> {
3040 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3041 <FakeConfigContext as NudConfigContext<I>>::retransmit_timeout(&mut self.state)
3042 }
3043
3044 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3045 <FakeConfigContext as NudConfigContext<I>>::with_nud_user_config(&mut self.state, cb)
3046 }
3047 }
3048
3049 const ONE_SECOND: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
3050
3051 #[track_caller]
3052 fn check_lookup_has<I: Ip>(
3053 core_ctx: &mut FakeCoreCtxImpl<I>,
3054 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3055 lookup_addr: SpecifiedAddr<I::Addr>,
3056 expected_link_addr: FakeLinkAddress,
3057 ) {
3058 let entry = assert_matches!(
3059 core_ctx.nud.state.neighbors.get(&lookup_addr),
3060 Some(entry @ (
3061 NeighborState::Dynamic(
3062 DynamicNeighborState::Reachable (Reachable { link_address, last_confirmed_at: _ })
3063 | DynamicNeighborState::Stale (Stale { link_address })
3064 | DynamicNeighborState::Delay (Delay { link_address })
3065 | DynamicNeighborState::Probe (Probe { link_address, transmit_counter: _ })
3066 | DynamicNeighborState::Unreachable (Unreachable { link_address, mode: _ })
3067 )
3068 | NeighborState::Static(link_address)
3069 )) => {
3070 assert_eq!(link_address, &expected_link_addr);
3071 entry
3072 }
3073 );
3074 match entry {
3075 NeighborState::Dynamic(DynamicNeighborState::Incomplete { .. }) => {
3076 unreachable!("entry must be static, REACHABLE, or STALE")
3077 }
3078 NeighborState::Dynamic(DynamicNeighborState::Reachable { .. }) => {
3079 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3080 bindings_ctx,
3081 [(
3082 lookup_addr,
3083 NudEvent::ReachableTime,
3084 core_ctx.inner.base_reachable_time().get(),
3085 )],
3086 );
3087 }
3088 NeighborState::Dynamic(DynamicNeighborState::Delay { .. }) => {
3089 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3090 bindings_ctx,
3091 [(lookup_addr, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
3092 );
3093 }
3094 NeighborState::Dynamic(DynamicNeighborState::Probe { .. }) => {
3095 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3096 bindings_ctx,
3097 [(
3098 lookup_addr,
3099 NudEvent::RetransmitUnicastProbe,
3100 core_ctx.inner.state.retrans_timer.get(),
3101 )],
3102 );
3103 }
3104 NeighborState::Dynamic(DynamicNeighborState::Unreachable(Unreachable {
3105 link_address: _,
3106 mode,
3107 })) => {
3108 let instant = match mode {
3109 UnreachableMode::WaitingForPacketSend => None,
3110 mode @ UnreachableMode::Backoff { .. } => {
3111 let duration =
3112 mode.next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state);
3113 Some(bindings_ctx.now() + duration.get())
3114 }
3115 };
3116 if let Some(instant) = instant {
3117 core_ctx.nud.state.timer_heap.neighbor.assert_timers([(
3118 lookup_addr,
3119 NudEvent::RetransmitUnicastProbe,
3120 instant,
3121 )]);
3122 }
3123 }
3124 NeighborState::Dynamic(DynamicNeighborState::Stale { .. })
3125 | NeighborState::Static(_) => bindings_ctx.timers.assert_no_timers_installed(),
3126 }
3127 }
3128
3129 trait TestIpExt: NudIcmpIpExt {
3130 const LOOKUP_ADDR1: SpecifiedAddr<Self::Addr>;
3131 const LOOKUP_ADDR2: SpecifiedAddr<Self::Addr>;
3132 const LOOKUP_ADDR3: SpecifiedAddr<Self::Addr>;
3133 }
3134
3135 impl TestIpExt for Ipv4 {
3136 const LOOKUP_ADDR1: SpecifiedAddr<Ipv4Addr> =
3138 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.1")) };
3139 const LOOKUP_ADDR2: SpecifiedAddr<Ipv4Addr> =
3140 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.2")) };
3141 const LOOKUP_ADDR3: SpecifiedAddr<Ipv4Addr> =
3142 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.3")) };
3143 }
3144
3145 impl TestIpExt for Ipv6 {
3146 const LOOKUP_ADDR1: SpecifiedAddr<Ipv6Addr> =
3148 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::1")) };
3149 const LOOKUP_ADDR2: SpecifiedAddr<Ipv6Addr> =
3150 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::2")) };
3151 const LOOKUP_ADDR3: SpecifiedAddr<Ipv6Addr> =
3152 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::3")) };
3153 }
3154
3155 const LINK_ADDR1: FakeLinkAddress = FakeLinkAddress([1]);
3156 const LINK_ADDR2: FakeLinkAddress = FakeLinkAddress([2]);
3157 const LINK_ADDR3: FakeLinkAddress = FakeLinkAddress([3]);
3158
3159 impl<I: Ip, L: LinkDevice> NudTimerId<I, L, FakeWeakDeviceId<FakeLinkDeviceId>> {
3160 fn neighbor() -> Self {
3161 Self {
3162 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3163 timer_type: NudTimerType::Neighbor,
3164 _marker: PhantomData,
3165 }
3166 }
3167
3168 fn garbage_collection() -> Self {
3169 Self {
3170 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3171 timer_type: NudTimerType::GarbageCollection,
3172 _marker: PhantomData,
3173 }
3174 }
3175 }
3176
3177 fn queue_ip_packet_to_unresolved_neighbor<I: TestIpExt>(
3178 core_ctx: &mut FakeCoreCtxImpl<I>,
3179 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3180 neighbor: SpecifiedAddr<I::Addr>,
3181 pending_frames: &mut VecDeque<Buf<Vec<u8>>>,
3182 body: u8,
3183 expect_event: bool,
3184 ) {
3185 let body = [body];
3186 assert_eq!(
3187 NudHandler::send_ip_packet_to_neighbor(
3188 core_ctx,
3189 bindings_ctx,
3190 &FakeLinkDeviceId,
3191 neighbor,
3192 Buf::new(body, ..),
3193 FakeTxMetadata::default(),
3194 ),
3195 Ok(())
3196 );
3197
3198 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
3199
3200 pending_frames.push_back(Buf::new(body.to_vec(), ..));
3201
3202 assert_neighbor_state_with_ip(
3203 core_ctx,
3204 bindings_ctx,
3205 neighbor,
3206 DynamicNeighborState::Incomplete(Incomplete {
3207 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
3208 pending_frames: pending_frames
3209 .iter()
3210 .cloned()
3211 .map(|buf| (buf, FakeTxMetadata::default()))
3212 .collect(),
3213 notifiers: Vec::new(),
3214 _marker: PhantomData,
3215 }),
3216 expect_event.then_some(ExpectedEvent::Added),
3217 );
3218
3219 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3220 bindings_ctx,
3221 [(neighbor, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
3222 );
3223 }
3224
3225 fn init_incomplete_neighbor_with_ip<I: TestIpExt>(
3226 core_ctx: &mut FakeCoreCtxImpl<I>,
3227 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3228 ip_address: SpecifiedAddr<I::Addr>,
3229 take_probe: bool,
3230 ) -> VecDeque<Buf<Vec<u8>>> {
3231 let mut pending_frames = VecDeque::new();
3232 queue_ip_packet_to_unresolved_neighbor(
3233 core_ctx,
3234 bindings_ctx,
3235 ip_address,
3236 &mut pending_frames,
3237 1,
3238 true, );
3240 if take_probe {
3241 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, None);
3242 }
3243 pending_frames
3244 }
3245
3246 fn init_incomplete_neighbor<I: TestIpExt>(
3247 core_ctx: &mut FakeCoreCtxImpl<I>,
3248 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3249 take_probe: bool,
3250 ) -> VecDeque<Buf<Vec<u8>>> {
3251 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, take_probe)
3252 }
3253
3254 fn init_stale_neighbor_with_ip<I: TestIpExt>(
3255 core_ctx: &mut FakeCoreCtxImpl<I>,
3256 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3257 ip_address: SpecifiedAddr<I::Addr>,
3258 link_address: FakeLinkAddress,
3259 ) {
3260 NudHandler::handle_neighbor_update(
3261 core_ctx,
3262 bindings_ctx,
3263 &FakeLinkDeviceId,
3264 ip_address,
3265 DynamicNeighborUpdateSource::Probe { link_address },
3266 );
3267 assert_neighbor_state_with_ip(
3268 core_ctx,
3269 bindings_ctx,
3270 ip_address,
3271 DynamicNeighborState::Stale(Stale { link_address }),
3272 Some(ExpectedEvent::Added),
3273 );
3274 }
3275
3276 fn init_stale_neighbor<I: TestIpExt>(
3277 core_ctx: &mut FakeCoreCtxImpl<I>,
3278 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3279 link_address: FakeLinkAddress,
3280 ) {
3281 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3282 }
3283
3284 fn init_reachable_neighbor_with_ip<I: TestIpExt>(
3285 core_ctx: &mut FakeCoreCtxImpl<I>,
3286 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3287 ip_address: SpecifiedAddr<I::Addr>,
3288 link_address: FakeLinkAddress,
3289 ) {
3290 let queued_frame =
3291 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, true);
3292 NudHandler::handle_neighbor_update(
3293 core_ctx,
3294 bindings_ctx,
3295 &FakeLinkDeviceId,
3296 ip_address,
3297 DynamicNeighborUpdateSource::Confirmation {
3298 link_address: Some(link_address),
3299 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
3300 },
3301 );
3302 assert_neighbor_state_with_ip(
3303 core_ctx,
3304 bindings_ctx,
3305 ip_address,
3306 DynamicNeighborState::Reachable(Reachable {
3307 link_address,
3308 last_confirmed_at: bindings_ctx.now(),
3309 }),
3310 Some(ExpectedEvent::Changed),
3311 );
3312 assert_pending_frame_sent(core_ctx, queued_frame, link_address);
3313 }
3314
3315 fn init_reachable_neighbor<I: TestIpExt>(
3316 core_ctx: &mut FakeCoreCtxImpl<I>,
3317 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3318 link_address: FakeLinkAddress,
3319 ) {
3320 init_reachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3321 }
3322
3323 fn init_delay_neighbor_with_ip<I: TestIpExt>(
3324 core_ctx: &mut FakeCoreCtxImpl<I>,
3325 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3326 ip_address: SpecifiedAddr<I::Addr>,
3327 link_address: FakeLinkAddress,
3328 ) {
3329 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3330 assert_eq!(
3331 NudHandler::send_ip_packet_to_neighbor(
3332 core_ctx,
3333 bindings_ctx,
3334 &FakeLinkDeviceId,
3335 ip_address,
3336 Buf::new([1], ..),
3337 FakeTxMetadata::default(),
3338 ),
3339 Ok(())
3340 );
3341 assert_neighbor_state_with_ip(
3342 core_ctx,
3343 bindings_ctx,
3344 ip_address,
3345 DynamicNeighborState::Delay(Delay { link_address }),
3346 Some(ExpectedEvent::Changed),
3347 );
3348 assert_eq!(
3349 core_ctx.inner.take_frames(),
3350 vec![(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![1])],
3351 );
3352 }
3353
3354 fn init_delay_neighbor<I: TestIpExt>(
3355 core_ctx: &mut FakeCoreCtxImpl<I>,
3356 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3357 link_address: FakeLinkAddress,
3358 ) {
3359 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3360 }
3361
3362 fn init_probe_neighbor_with_ip<I: TestIpExt>(
3363 core_ctx: &mut FakeCoreCtxImpl<I>,
3364 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3365 ip_address: SpecifiedAddr<I::Addr>,
3366 link_address: FakeLinkAddress,
3367 take_probe: bool,
3368 ) {
3369 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3370 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3371 core_ctx.nud.state.timer_heap.neighbor.assert_top(&ip_address, &NudEvent::DelayFirstProbe);
3372 assert_eq!(
3373 bindings_ctx.trigger_timers_for(DELAY_FIRST_PROBE_TIME.into(), core_ctx),
3374 [NudTimerId::neighbor()]
3375 );
3376 assert_neighbor_state_with_ip(
3377 core_ctx,
3378 bindings_ctx,
3379 ip_address,
3380 DynamicNeighborState::Probe(Probe {
3381 link_address,
3382 transmit_counter: NonZeroU16::new(max_unicast_solicit - 1),
3383 }),
3384 Some(ExpectedEvent::Changed),
3385 );
3386 if take_probe {
3387 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3388 }
3389 }
3390
3391 fn init_probe_neighbor<I: TestIpExt>(
3392 core_ctx: &mut FakeCoreCtxImpl<I>,
3393 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3394 link_address: FakeLinkAddress,
3395 take_probe: bool,
3396 ) {
3397 init_probe_neighbor_with_ip(
3398 core_ctx,
3399 bindings_ctx,
3400 I::LOOKUP_ADDR1,
3401 link_address,
3402 take_probe,
3403 );
3404 }
3405
3406 fn init_unreachable_neighbor_with_ip<I: TestIpExt>(
3407 core_ctx: &mut FakeCoreCtxImpl<I>,
3408 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3409 ip_address: SpecifiedAddr<I::Addr>,
3410 link_address: FakeLinkAddress,
3411 ) {
3412 init_probe_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address, false);
3413 let retransmit_timeout = core_ctx.inner.retransmit_timeout();
3414 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3415 for _ in 0..max_unicast_solicit {
3416 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3417 assert_eq!(
3418 bindings_ctx.trigger_timers_for(retransmit_timeout.into(), core_ctx),
3419 [NudTimerId::neighbor()]
3420 );
3421 }
3422 assert_neighbor_state_with_ip(
3423 core_ctx,
3424 bindings_ctx,
3425 ip_address,
3426 DynamicNeighborState::Unreachable(Unreachable {
3427 link_address,
3428 mode: UnreachableMode::WaitingForPacketSend,
3429 }),
3430 Some(ExpectedEvent::Changed),
3431 );
3432 }
3433
3434 fn init_unreachable_neighbor<I: TestIpExt>(
3435 core_ctx: &mut FakeCoreCtxImpl<I>,
3436 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3437 link_address: FakeLinkAddress,
3438 ) {
3439 init_unreachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3440 }
3441
3442 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
3443 enum InitialState {
3444 Incomplete,
3445 Stale,
3446 Reachable,
3447 Delay,
3448 Probe,
3449 Unreachable,
3450 }
3451
3452 fn init_neighbor_in_state<I: TestIpExt>(
3453 core_ctx: &mut FakeCoreCtxImpl<I>,
3454 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3455 state: InitialState,
3456 ) -> DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>> {
3457 match state {
3458 InitialState::Incomplete => {
3459 let _: VecDeque<Buf<Vec<u8>>> =
3460 init_incomplete_neighbor(core_ctx, bindings_ctx, true);
3461 }
3462 InitialState::Reachable => {
3463 init_reachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3464 }
3465 InitialState::Stale => {
3466 init_stale_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3467 }
3468 InitialState::Delay => {
3469 init_delay_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3470 }
3471 InitialState::Probe => {
3472 init_probe_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, true);
3473 }
3474 InitialState::Unreachable => {
3475 init_unreachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3476 }
3477 }
3478 assert_matches!(core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
3479 Some(NeighborState::Dynamic(state)) => state.clone()
3480 )
3481 }
3482
3483 #[track_caller]
3484 fn init_static_neighbor_with_ip<I: TestIpExt>(
3485 core_ctx: &mut FakeCoreCtxImpl<I>,
3486 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3487 ip_address: SpecifiedAddr<I::Addr>,
3488 link_address: FakeLinkAddress,
3489 expected_event: ExpectedEvent,
3490 ) {
3491 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3492 NeighborApi::new(&mut ctx)
3493 .insert_static_entry(&FakeLinkDeviceId, *ip_address, link_address)
3494 .unwrap();
3495 assert_eq!(
3496 ctx.bindings_ctx.take_events(),
3497 [Event {
3498 device: FakeLinkDeviceId,
3499 addr: ip_address,
3500 kind: match expected_event {
3501 ExpectedEvent::Added => EventKind::Added(EventState::Static(link_address)),
3502 ExpectedEvent::Changed => EventKind::Changed(EventState::Static(link_address)),
3503 },
3504 at: ctx.bindings_ctx.now(),
3505 }],
3506 );
3507 }
3508
3509 #[track_caller]
3510 fn init_static_neighbor<I: TestIpExt>(
3511 core_ctx: &mut FakeCoreCtxImpl<I>,
3512 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3513 link_address: FakeLinkAddress,
3514 expected_event: ExpectedEvent,
3515 ) {
3516 init_static_neighbor_with_ip(
3517 core_ctx,
3518 bindings_ctx,
3519 I::LOOKUP_ADDR1,
3520 link_address,
3521 expected_event,
3522 );
3523 }
3524
3525 #[track_caller]
3526 fn delete_neighbor<I: TestIpExt>(
3527 core_ctx: &mut FakeCoreCtxImpl<I>,
3528 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3529 ) {
3530 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3531 NeighborApi::new(&mut ctx)
3532 .remove_entry(&FakeLinkDeviceId, *I::LOOKUP_ADDR1)
3533 .expect("neighbor entry should exist");
3534 assert_eq!(
3535 ctx.bindings_ctx.take_events(),
3536 [Event::removed(&FakeLinkDeviceId, I::LOOKUP_ADDR1, ctx.bindings_ctx.now())],
3537 );
3538 }
3539
3540 #[track_caller]
3541 fn assert_neighbor_state<I: TestIpExt>(
3542 core_ctx: &FakeCoreCtxImpl<I>,
3543 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3544 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3545 event_kind: Option<ExpectedEvent>,
3546 ) {
3547 assert_neighbor_state_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, state, event_kind);
3548 }
3549
3550 #[derive(Clone, Copy, Debug)]
3551 enum ExpectedEvent {
3552 Added,
3553 Changed,
3554 }
3555
3556 #[track_caller]
3557 fn assert_neighbor_state_with_ip<I: TestIpExt>(
3558 core_ctx: &FakeCoreCtxImpl<I>,
3559 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3560 neighbor: SpecifiedAddr<I::Addr>,
3561 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3562 expected_event: Option<ExpectedEvent>,
3563 ) {
3564 if let Some(expected_event) = expected_event {
3565 let event_state = EventState::Dynamic(state.to_event_dynamic_state());
3566 assert_eq!(
3567 bindings_ctx.take_events(),
3568 [Event {
3569 device: FakeLinkDeviceId,
3570 addr: neighbor,
3571 kind: match expected_event {
3572 ExpectedEvent::Added => EventKind::Added(event_state),
3573 ExpectedEvent::Changed => EventKind::Changed(event_state),
3574 },
3575 at: bindings_ctx.now(),
3576 }],
3577 );
3578 }
3579
3580 assert_eq!(
3581 core_ctx.nud.state.neighbors.get(&neighbor),
3582 Some(&NeighborState::Dynamic(state))
3583 );
3584 }
3585
3586 #[track_caller]
3587 fn assert_pending_frame_sent<I: TestIpExt>(
3588 core_ctx: &mut FakeCoreCtxImpl<I>,
3589 pending_frames: VecDeque<Buf<Vec<u8>>>,
3590 link_address: FakeLinkAddress,
3591 ) {
3592 assert_eq!(
3593 core_ctx.inner.take_frames(),
3594 pending_frames
3595 .into_iter()
3596 .map(|f| (
3597 FakeNudMessageMeta::IpFrame { dst_link_address: link_address },
3598 f.as_ref().to_vec(),
3599 ))
3600 .collect::<Vec<_>>()
3601 );
3602 }
3603
3604 #[track_caller]
3605 fn assert_neighbor_probe_sent_for_ip<I: TestIpExt>(
3606 core_ctx: &mut FakeCoreCtxImpl<I>,
3607 ip_address: SpecifiedAddr<I::Addr>,
3608 link_address: Option<FakeLinkAddress>,
3609 ) {
3610 assert_eq!(
3611 core_ctx.inner.take_frames(),
3612 [(
3613 FakeNudMessageMeta::NeighborSolicitation {
3614 lookup_addr: ip_address,
3615 remote_link_addr: link_address,
3616 },
3617 Vec::new()
3618 )]
3619 );
3620 }
3621
3622 #[track_caller]
3623 fn assert_neighbor_probe_sent<I: TestIpExt>(
3624 core_ctx: &mut FakeCoreCtxImpl<I>,
3625 link_address: Option<FakeLinkAddress>,
3626 ) {
3627 assert_neighbor_probe_sent_for_ip(core_ctx, I::LOOKUP_ADDR1, link_address);
3628 }
3629
3630 #[track_caller]
3631 fn assert_neighbor_removed_with_ip<I: TestIpExt>(
3632 core_ctx: &mut FakeCoreCtxImpl<I>,
3633 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3634 neighbor: SpecifiedAddr<I::Addr>,
3635 ) {
3636 super::testutil::assert_neighbor_unknown(core_ctx, FakeLinkDeviceId, neighbor);
3637 assert_eq!(
3638 bindings_ctx.take_events(),
3639 [Event::removed(&FakeLinkDeviceId, neighbor, bindings_ctx.now())],
3640 );
3641 }
3642
3643 #[ip_test(I)]
3644 fn serialization_failure_doesnt_schedule_timer<I: TestIpExt>() {
3645 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3646
3647 let packet = Buf::new([0; 2], ..).with_size_limit(1);
3650
3651 let err = assert_matches!(
3652 NudHandler::send_ip_packet_to_neighbor(
3653 &mut core_ctx,
3654 &mut bindings_ctx,
3655 &FakeLinkDeviceId,
3656 I::LOOKUP_ADDR1,
3657 packet,
3658 FakeTxMetadata::default(),
3659 ),
3660 Err(ErrorAndSerializer { error, serializer: _ }) => error
3661 );
3662 assert_eq!(err, SendFrameErrorReason::SizeConstraintsViolation);
3663
3664 super::testutil::assert_neighbor_unknown(&mut core_ctx, FakeLinkDeviceId, I::LOOKUP_ADDR1);
3667 assert_eq!(core_ctx.inner.take_frames(), []);
3668 bindings_ctx.timers.assert_no_timers_installed();
3669 }
3670
3671 #[ip_test(I)]
3672 fn incomplete_to_stale_on_probe<I: TestIpExt>() {
3673 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3674
3675 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3677
3678 NudHandler::handle_neighbor_update(
3680 &mut core_ctx,
3681 &mut bindings_ctx,
3682 &FakeLinkDeviceId,
3683 I::LOOKUP_ADDR1,
3684 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
3685 );
3686
3687 assert_neighbor_state(
3689 &core_ctx,
3690 &mut bindings_ctx,
3691 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3692 Some(ExpectedEvent::Changed),
3693 );
3694 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3695 }
3696
3697 #[ip_test(I)]
3698 #[test_case(true, true; "solicited override")]
3699 #[test_case(true, false; "solicited non-override")]
3700 #[test_case(false, true; "unsolicited override")]
3701 #[test_case(false, false; "unsolicited non-override")]
3702 fn incomplete_on_confirmation<I: TestIpExt>(solicited_flag: bool, override_flag: bool) {
3703 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3704
3705 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3707
3708 NudHandler::handle_neighbor_update(
3710 &mut core_ctx,
3711 &mut bindings_ctx,
3712 &FakeLinkDeviceId,
3713 I::LOOKUP_ADDR1,
3714 DynamicNeighborUpdateSource::Confirmation {
3715 link_address: Some(LINK_ADDR1),
3716 flags: ConfirmationFlags { solicited_flag, override_flag },
3717 },
3718 );
3719
3720 let expected_state = if solicited_flag {
3721 DynamicNeighborState::Reachable(Reachable {
3722 link_address: LINK_ADDR1,
3723 last_confirmed_at: bindings_ctx.now(),
3724 })
3725 } else {
3726 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 })
3727 };
3728 assert_neighbor_state(
3729 &core_ctx,
3730 &mut bindings_ctx,
3731 expected_state,
3732 Some(ExpectedEvent::Changed),
3733 );
3734 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3735 }
3736
3737 #[ip_test(I)]
3738 fn reachable_to_stale_on_timeout<I: TestIpExt>() {
3739 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3740
3741 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
3743
3744 assert_eq!(
3746 bindings_ctx
3747 .trigger_timers_for(core_ctx.inner.base_reachable_time().into(), &mut core_ctx,),
3748 [NudTimerId::neighbor()]
3749 );
3750 assert_neighbor_state(
3751 &core_ctx,
3752 &mut bindings_ctx,
3753 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3754 Some(ExpectedEvent::Changed),
3755 );
3756 }
3757
3758 #[ip_test(I)]
3759 #[test_case(InitialState::Reachable, true; "reachable with different address")]
3760 #[test_case(InitialState::Reachable, false; "reachable with same address")]
3761 #[test_case(InitialState::Stale, true; "stale with different address")]
3762 #[test_case(InitialState::Stale, false; "stale with same address")]
3763 #[test_case(InitialState::Delay, true; "delay with different address")]
3764 #[test_case(InitialState::Delay, false; "delay with same address")]
3765 #[test_case(InitialState::Probe, true; "probe with different address")]
3766 #[test_case(InitialState::Probe, false; "probe with same address")]
3767 #[test_case(InitialState::Unreachable, true; "unreachable with different address")]
3768 #[test_case(InitialState::Unreachable, false; "unreachable with same address")]
3769 fn transition_to_stale_on_probe_with_different_address<I: TestIpExt>(
3770 initial_state: InitialState,
3771 update_link_address: bool,
3772 ) {
3773 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3774
3775 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3777
3778 NudHandler::handle_neighbor_update(
3780 &mut core_ctx,
3781 &mut bindings_ctx,
3782 &FakeLinkDeviceId,
3783 I::LOOKUP_ADDR1,
3784 DynamicNeighborUpdateSource::Probe {
3785 link_address: if update_link_address { LINK_ADDR2 } else { LINK_ADDR1 },
3786 },
3787 );
3788
3789 let expected_state = if update_link_address {
3795 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 })
3796 } else {
3797 initial_state
3798 };
3799 assert_neighbor_state(
3800 &core_ctx,
3801 &mut bindings_ctx,
3802 expected_state,
3803 update_link_address.then_some(ExpectedEvent::Changed),
3804 );
3805 }
3806
3807 #[ip_test(I)]
3808 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
3809 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
3810 #[test_case(InitialState::Stale, true; "stale with override flag set")]
3811 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
3812 #[test_case(InitialState::Delay, true; "delay with override flag set")]
3813 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
3814 #[test_case(InitialState::Probe, true; "probe with override flag set")]
3815 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
3816 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
3817 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
3818 fn transition_to_reachable_on_solicited_confirmation_same_address<I: TestIpExt>(
3819 initial_state: InitialState,
3820 override_flag: bool,
3821 ) {
3822 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3823
3824 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3826
3827 NudHandler::handle_neighbor_update(
3829 &mut core_ctx,
3830 &mut bindings_ctx,
3831 &FakeLinkDeviceId,
3832 I::LOOKUP_ADDR1,
3833 DynamicNeighborUpdateSource::Confirmation {
3834 link_address: Some(LINK_ADDR1),
3835 flags: ConfirmationFlags { solicited_flag: true, override_flag },
3836 },
3837 );
3838
3839 let now = bindings_ctx.now();
3841 assert_neighbor_state(
3842 &core_ctx,
3843 &mut bindings_ctx,
3844 DynamicNeighborState::Reachable(Reachable {
3845 link_address: LINK_ADDR1,
3846 last_confirmed_at: now,
3847 }),
3848 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
3849 );
3850 }
3851
3852 #[ip_test(I)]
3853 #[test_case(InitialState::Reachable; "reachable")]
3854 #[test_case(InitialState::Stale; "stale")]
3855 #[test_case(InitialState::Delay; "delay")]
3856 #[test_case(InitialState::Probe; "probe")]
3857 #[test_case(InitialState::Unreachable; "unreachable")]
3858 fn transition_to_stale_on_unsolicited_override_confirmation_with_different_address<
3859 I: TestIpExt,
3860 >(
3861 initial_state: InitialState,
3862 ) {
3863 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3864
3865 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3867
3868 NudHandler::handle_neighbor_update(
3870 &mut core_ctx,
3871 &mut bindings_ctx,
3872 &FakeLinkDeviceId,
3873 I::LOOKUP_ADDR1,
3874 DynamicNeighborUpdateSource::Confirmation {
3875 link_address: Some(LINK_ADDR2),
3876 flags: ConfirmationFlags { solicited_flag: false, override_flag: true },
3877 },
3878 );
3879
3880 assert_neighbor_state(
3882 &core_ctx,
3883 &mut bindings_ctx,
3884 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
3885 Some(ExpectedEvent::Changed),
3886 );
3887 }
3888
3889 #[ip_test(I)]
3890 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
3891 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
3892 #[test_case(InitialState::Stale, true; "stale with override flag set")]
3893 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
3894 #[test_case(InitialState::Delay, true; "delay with override flag set")]
3895 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
3896 #[test_case(InitialState::Probe, true; "probe with override flag set")]
3897 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
3898 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
3899 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
3900 fn noop_on_unsolicited_confirmation_with_same_address<I: TestIpExt>(
3901 initial_state: InitialState,
3902 override_flag: bool,
3903 ) {
3904 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3905
3906 let expected_state =
3908 init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3909
3910 NudHandler::handle_neighbor_update(
3912 &mut core_ctx,
3913 &mut bindings_ctx,
3914 &FakeLinkDeviceId,
3915 I::LOOKUP_ADDR1,
3916 DynamicNeighborUpdateSource::Confirmation {
3917 link_address: Some(LINK_ADDR1),
3918 flags: ConfirmationFlags { solicited_flag: false, override_flag },
3919 },
3920 );
3921
3922 assert_neighbor_state(&core_ctx, &mut bindings_ctx, expected_state, None);
3924 }
3925
3926 #[ip_test(I)]
3927 #[test_case(InitialState::Reachable; "reachable")]
3928 #[test_case(InitialState::Stale; "stale")]
3929 #[test_case(InitialState::Delay; "delay")]
3930 #[test_case(InitialState::Probe; "probe")]
3931 #[test_case(InitialState::Unreachable; "unreachable")]
3932 fn transition_to_reachable_on_solicited_override_confirmation_with_different_address<
3933 I: TestIpExt,
3934 >(
3935 initial_state: InitialState,
3936 ) {
3937 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3938
3939 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3941
3942 NudHandler::handle_neighbor_update(
3944 &mut core_ctx,
3945 &mut bindings_ctx,
3946 &FakeLinkDeviceId,
3947 I::LOOKUP_ADDR1,
3948 DynamicNeighborUpdateSource::Confirmation {
3949 link_address: Some(LINK_ADDR2),
3950 flags: ConfirmationFlags { solicited_flag: true, override_flag: true },
3951 },
3952 );
3953
3954 let now = bindings_ctx.now();
3956 assert_neighbor_state(
3957 &core_ctx,
3958 &mut bindings_ctx,
3959 DynamicNeighborState::Reachable(Reachable {
3960 link_address: LINK_ADDR2,
3961 last_confirmed_at: now,
3962 }),
3963 Some(ExpectedEvent::Changed),
3964 );
3965 }
3966
3967 #[ip_test(I)]
3968 fn reachable_to_reachable_on_probe_with_same_address<I: TestIpExt>() {
3969 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3970
3971 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
3973
3974 NudHandler::handle_neighbor_update(
3976 &mut core_ctx,
3977 &mut bindings_ctx,
3978 &FakeLinkDeviceId,
3979 I::LOOKUP_ADDR1,
3980 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
3981 );
3982
3983 let now = bindings_ctx.now();
3985 assert_neighbor_state(
3986 &core_ctx,
3987 &mut bindings_ctx,
3988 DynamicNeighborState::Reachable(Reachable {
3989 link_address: LINK_ADDR1,
3990 last_confirmed_at: now,
3991 }),
3992 None,
3993 );
3994 }
3995
3996 #[ip_test(I)]
3997 #[test_case(true; "solicited")]
3998 #[test_case(false; "unsolicited")]
3999 fn reachable_to_stale_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4000 solicited_flag: bool,
4001 ) {
4002 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4003
4004 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4006
4007 NudHandler::handle_neighbor_update(
4009 &mut core_ctx,
4010 &mut bindings_ctx,
4011 &FakeLinkDeviceId,
4012 I::LOOKUP_ADDR1,
4013 DynamicNeighborUpdateSource::Confirmation {
4014 link_address: Some(LINK_ADDR2),
4015 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4016 },
4017 );
4018
4019 assert_neighbor_state(
4022 &core_ctx,
4023 &mut bindings_ctx,
4024 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4025 Some(ExpectedEvent::Changed),
4026 );
4027 }
4028
4029 #[ip_test(I)]
4030 #[test_case(InitialState::Stale, true; "stale solicited")]
4031 #[test_case(InitialState::Stale, false; "stale unsolicited")]
4032 #[test_case(InitialState::Delay, true; "delay solicited")]
4033 #[test_case(InitialState::Delay, false; "delay unsolicited")]
4034 #[test_case(InitialState::Probe, true; "probe solicited")]
4035 #[test_case(InitialState::Probe, false; "probe unsolicited")]
4036 #[test_case(InitialState::Unreachable, true; "unreachable solicited")]
4037 #[test_case(InitialState::Unreachable, false; "unreachable unsolicited")]
4038 fn noop_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4039 initial_state: InitialState,
4040 solicited_flag: bool,
4041 ) {
4042 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4043
4044 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4046
4047 NudHandler::handle_neighbor_update(
4049 &mut core_ctx,
4050 &mut bindings_ctx,
4051 &FakeLinkDeviceId,
4052 I::LOOKUP_ADDR1,
4053 DynamicNeighborUpdateSource::Confirmation {
4054 link_address: Some(LINK_ADDR2),
4055 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4056 },
4057 );
4058
4059 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial_state, None);
4062 }
4063
4064 #[ip_test(I)]
4065 fn stale_to_delay_on_packet_sent<I: TestIpExt>() {
4066 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4067
4068 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4070
4071 let body = 1;
4073 assert_eq!(
4074 NudHandler::send_ip_packet_to_neighbor(
4075 &mut core_ctx,
4076 &mut bindings_ctx,
4077 &FakeLinkDeviceId,
4078 I::LOOKUP_ADDR1,
4079 Buf::new([body], ..),
4080 FakeTxMetadata::default(),
4081 ),
4082 Ok(())
4083 );
4084
4085 assert_neighbor_state(
4087 &core_ctx,
4088 &mut bindings_ctx,
4089 DynamicNeighborState::Delay(Delay { link_address: LINK_ADDR1 }),
4090 Some(ExpectedEvent::Changed),
4091 );
4092 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4093 &mut bindings_ctx,
4094 [(I::LOOKUP_ADDR1, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
4095 );
4096 assert_pending_frame_sent(
4097 &mut core_ctx,
4098 VecDeque::from([Buf::new(vec![body], ..)]),
4099 LINK_ADDR1,
4100 );
4101 }
4102
4103 #[ip_test(I)]
4104 #[test_case(InitialState::Delay,
4105 NudEvent::DelayFirstProbe;
4106 "delay to probe")]
4107 #[test_case(InitialState::Probe,
4108 NudEvent::RetransmitUnicastProbe;
4109 "probe retransmit unicast probe")]
4110 fn delay_or_probe_to_probe_on_timeout<I: TestIpExt>(
4111 initial_state: InitialState,
4112 expected_initial_event: NudEvent,
4113 ) {
4114 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4115
4116 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4118
4119 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4120
4121 let (time, transmit_counter) = match initial_state {
4127 InitialState::Delay => {
4128 (DELAY_FIRST_PROBE_TIME, NonZeroU16::new(max_unicast_solicit - 1))
4129 }
4130 InitialState::Probe => {
4131 (core_ctx.inner.state.retrans_timer, NonZeroU16::new(max_unicast_solicit - 2))
4132 }
4133 other => unreachable!("test only covers DELAY and PROBE, got {:?}", other),
4134 };
4135 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4136 &mut bindings_ctx,
4137 [(I::LOOKUP_ADDR1, expected_initial_event, time.get())],
4138 );
4139 assert_eq!(
4140 bindings_ctx.trigger_timers_for(time.into(), &mut core_ctx,),
4141 [NudTimerId::neighbor()]
4142 );
4143 assert_neighbor_state(
4144 &core_ctx,
4145 &mut bindings_ctx,
4146 DynamicNeighborState::Probe(Probe { link_address: LINK_ADDR1, transmit_counter }),
4147 (initial_state != InitialState::Probe).then_some(ExpectedEvent::Changed),
4148 );
4149 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4150 &mut bindings_ctx,
4151 [(
4152 I::LOOKUP_ADDR1,
4153 NudEvent::RetransmitUnicastProbe,
4154 core_ctx.inner.state.retrans_timer.get(),
4155 )],
4156 );
4157 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4158 }
4159
4160 #[ip_test(I)]
4161 fn unreachable_probes_with_exponential_backoff_while_packets_sent<I: TestIpExt>() {
4162 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4163
4164 init_unreachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4165
4166 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4167 let timer_id = NudTimerId::neighbor();
4168
4169 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), []);
4171 assert_eq!(core_ctx.inner.take_frames(), []);
4172
4173 const BODY: u8 = 0x33;
4175 assert_eq!(
4176 NudHandler::send_ip_packet_to_neighbor(
4177 &mut core_ctx,
4178 &mut bindings_ctx,
4179 &FakeLinkDeviceId,
4180 I::LOOKUP_ADDR1,
4181 Buf::new([BODY], ..),
4182 FakeTxMetadata::default(),
4183 ),
4184 Ok(())
4185 );
4186 assert_eq!(
4187 core_ctx.inner.take_frames(),
4188 [
4189 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4190 (
4191 FakeNudMessageMeta::NeighborSolicitation {
4192 lookup_addr: I::LOOKUP_ADDR1,
4193 remote_link_addr: None,
4194 },
4195 Vec::new()
4196 )
4197 ]
4198 );
4199
4200 let next_backoff_timer = |core_ctx: &mut FakeCoreCtxImpl<I>, probes_sent| {
4201 UnreachableMode::Backoff {
4202 probes_sent: NonZeroU32::new(probes_sent).unwrap(),
4203 packet_sent: false,
4204 }
4205 .next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state)
4206 .get()
4207 };
4208
4209 const ITERATIONS: u8 = 2;
4210 for i in 1..ITERATIONS {
4211 let probes_sent = u32::from(i);
4212
4213 assert_eq!(
4216 NudHandler::send_ip_packet_to_neighbor(
4217 &mut core_ctx,
4218 &mut bindings_ctx,
4219 &FakeLinkDeviceId,
4220 I::LOOKUP_ADDR1,
4221 Buf::new([BODY + i], ..),
4222 FakeTxMetadata::default(),
4223 ),
4224 Ok(())
4225 );
4226 assert_eq!(
4227 core_ctx.inner.take_frames(),
4228 [(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY + i])]
4229 );
4230
4231 assert_eq!(
4236 bindings_ctx.trigger_timers_for(
4237 next_backoff_timer(&mut core_ctx, probes_sent),
4238 &mut core_ctx,
4239 ),
4240 [timer_id]
4241 );
4242 assert_neighbor_probe_sent(&mut core_ctx, None);
4243 bindings_ctx.timers.assert_timers_installed([(
4244 timer_id,
4245 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, probes_sent + 1),
4246 )]);
4247 }
4248
4249 let current_timer = next_backoff_timer(&mut core_ctx, u32::from(ITERATIONS));
4252 assert_eq!(bindings_ctx.trigger_timers_for(current_timer, &mut core_ctx,), [timer_id]);
4253 assert_eq!(core_ctx.inner.take_frames(), []);
4254 bindings_ctx.timers.assert_no_timers_installed();
4255
4256 assert_eq!(
4259 NudHandler::send_ip_packet_to_neighbor(
4260 &mut core_ctx,
4261 &mut bindings_ctx,
4262 &FakeLinkDeviceId,
4263 I::LOOKUP_ADDR1,
4264 Buf::new([BODY], ..),
4265 FakeTxMetadata::default(),
4266 ),
4267 Ok(())
4268 );
4269 assert_eq!(
4270 core_ctx.inner.take_frames(),
4271 [
4272 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4273 (
4274 FakeNudMessageMeta::NeighborSolicitation {
4275 lookup_addr: I::LOOKUP_ADDR1,
4276 remote_link_addr: None,
4277 },
4278 Vec::new()
4279 )
4280 ]
4281 );
4282 bindings_ctx.timers.assert_timers_installed([(
4283 timer_id,
4284 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, 1),
4285 )]);
4286 }
4287
4288 #[ip_test(I)]
4289 #[test_case(true; "solicited confirmation")]
4290 #[test_case(false; "unsolicited confirmation")]
4291 fn confirmation_should_not_create_entry<I: TestIpExt>(solicited_flag: bool) {
4292 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4293
4294 let link_address = Some(FakeLinkAddress([1]));
4295 NudHandler::handle_neighbor_update(
4296 &mut core_ctx,
4297 &mut bindings_ctx,
4298 &FakeLinkDeviceId,
4299 I::LOOKUP_ADDR1,
4300 DynamicNeighborUpdateSource::Confirmation {
4301 link_address,
4302 flags: ConfirmationFlags { solicited_flag, override_flag: false },
4303 },
4304 );
4305 assert_eq!(core_ctx.nud.state.neighbors, HashMap::new());
4306 }
4307
4308 #[ip_test(I)]
4309 #[test_case(true; "set_with_dynamic")]
4310 #[test_case(false; "set_with_static")]
4311 fn pending_frames<I: TestIpExt>(dynamic: bool) {
4312 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4313 assert_eq!(core_ctx.inner.take_frames(), []);
4314
4315 const MAX_PENDING_FRAMES_U8: u8 = MAX_PENDING_FRAMES as u8;
4319 let expected_pending_frames = (0..MAX_PENDING_FRAMES_U8)
4320 .map(|i| (Buf::new(vec![i], ..), FakeTxMetadata::default()))
4321 .collect::<VecDeque<_>>();
4322
4323 for (body, meta) in expected_pending_frames.iter() {
4324 assert_eq!(
4325 NudHandler::send_ip_packet_to_neighbor(
4326 &mut core_ctx,
4327 &mut bindings_ctx,
4328 &FakeLinkDeviceId,
4329 I::LOOKUP_ADDR1,
4330 body.clone(),
4331 meta.clone(),
4332 ),
4333 Ok(())
4334 );
4335 }
4336 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4337 assert_neighbor_probe_sent(&mut core_ctx, None);
4339 assert_neighbor_state(
4340 &core_ctx,
4341 &mut bindings_ctx,
4342 DynamicNeighborState::Incomplete(Incomplete {
4343 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4344 pending_frames: expected_pending_frames.clone(),
4345 notifiers: Vec::new(),
4346 _marker: PhantomData,
4347 }),
4348 Some(ExpectedEvent::Added),
4349 );
4350
4351 assert_eq!(
4353 NudHandler::send_ip_packet_to_neighbor(
4354 &mut core_ctx,
4355 &mut bindings_ctx,
4356 &FakeLinkDeviceId,
4357 I::LOOKUP_ADDR1,
4358 Buf::new([123], ..),
4359 FakeTxMetadata::default(),
4360 ),
4361 Ok(())
4362 );
4363 assert_eq!(core_ctx.inner.take_frames(), []);
4364 assert_neighbor_state(
4365 &core_ctx,
4366 &mut bindings_ctx,
4367 DynamicNeighborState::Incomplete(Incomplete {
4368 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4369 pending_frames: expected_pending_frames.clone(),
4370 notifiers: Vec::new(),
4371 _marker: PhantomData,
4372 }),
4373 None,
4374 );
4375
4376 if dynamic {
4378 NudHandler::handle_neighbor_update(
4379 &mut core_ctx,
4380 &mut bindings_ctx,
4381 &FakeLinkDeviceId,
4382 I::LOOKUP_ADDR1,
4383 DynamicNeighborUpdateSource::Confirmation {
4384 link_address: Some(LINK_ADDR1),
4385 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4386 },
4387 );
4388 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4389 &mut bindings_ctx,
4390 [(
4391 I::LOOKUP_ADDR1,
4392 NudEvent::ReachableTime,
4393 core_ctx.inner.base_reachable_time().get(),
4394 )],
4395 );
4396 let last_confirmed_at = bindings_ctx.now();
4397 assert_neighbor_state(
4398 &core_ctx,
4399 &mut bindings_ctx,
4400 DynamicNeighborState::Reachable(Reachable {
4401 link_address: LINK_ADDR1,
4402 last_confirmed_at,
4403 }),
4404 Some(ExpectedEvent::Changed),
4405 );
4406 } else {
4407 init_static_neighbor(
4408 &mut core_ctx,
4409 &mut bindings_ctx,
4410 LINK_ADDR1,
4411 ExpectedEvent::Changed,
4412 );
4413 bindings_ctx.timers.assert_no_timers_installed();
4414 }
4415 assert_eq!(
4416 core_ctx.inner.take_frames(),
4417 expected_pending_frames
4418 .into_iter()
4419 .map(|(p, FakeTxMetadata)| (
4420 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4421 p.as_ref().to_vec()
4422 ))
4423 .collect::<Vec<_>>()
4424 );
4425 }
4426
4427 #[ip_test(I)]
4428 fn static_neighbor<I: TestIpExt>() {
4429 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4430
4431 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4432 bindings_ctx.timers.assert_no_timers_installed();
4433 assert_eq!(core_ctx.inner.take_frames(), []);
4434 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4435
4436 NudHandler::handle_neighbor_update(
4438 &mut core_ctx,
4439 &mut bindings_ctx,
4440 &FakeLinkDeviceId,
4441 I::LOOKUP_ADDR1,
4442 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4443 );
4444 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4445
4446 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4447
4448 let neighbors = &core_ctx.nud.state.neighbors;
4449 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4450 }
4451
4452 #[ip_test(I)]
4453 fn dynamic_neighbor<I: TestIpExt>() {
4454 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4455
4456 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4457 bindings_ctx.timers.assert_no_timers_installed();
4458 assert_eq!(core_ctx.inner.take_frames(), []);
4459 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4460
4461 NudHandler::handle_neighbor_update(
4463 &mut core_ctx,
4464 &mut bindings_ctx,
4465 &FakeLinkDeviceId,
4466 I::LOOKUP_ADDR1,
4467 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4468 );
4469 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR2);
4470 assert_eq!(core_ctx.inner.take_frames(), []);
4471 assert_neighbor_state(
4472 &core_ctx,
4473 &mut bindings_ctx,
4474 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4475 Some(ExpectedEvent::Changed),
4476 );
4477
4478 init_static_neighbor_with_ip(
4480 &mut core_ctx,
4481 &mut bindings_ctx,
4482 I::LOOKUP_ADDR1,
4483 LINK_ADDR3,
4484 ExpectedEvent::Changed,
4485 );
4486 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR3);
4487 assert_eq!(core_ctx.inner.take_frames(), []);
4488 }
4489
4490 #[ip_test(I)]
4491 fn send_solicitation_on_lookup<I: TestIpExt>() {
4492 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4493 bindings_ctx.timers.assert_no_timers_installed();
4494 assert_eq!(core_ctx.inner.take_frames(), []);
4495
4496 let mut pending_frames = VecDeque::new();
4497
4498 queue_ip_packet_to_unresolved_neighbor(
4499 &mut core_ctx,
4500 &mut bindings_ctx,
4501 I::LOOKUP_ADDR1,
4502 &mut pending_frames,
4503 1,
4504 true, );
4506 assert_neighbor_probe_sent(&mut core_ctx, None);
4507
4508 queue_ip_packet_to_unresolved_neighbor(
4509 &mut core_ctx,
4510 &mut bindings_ctx,
4511 I::LOOKUP_ADDR1,
4512 &mut pending_frames,
4513 2,
4514 false, );
4516 assert_eq!(core_ctx.inner.take_frames(), []);
4517
4518 NudHandler::handle_neighbor_update(
4520 &mut core_ctx,
4521 &mut bindings_ctx,
4522 &FakeLinkDeviceId,
4523 I::LOOKUP_ADDR1,
4524 DynamicNeighborUpdateSource::Confirmation {
4525 link_address: Some(LINK_ADDR1),
4526 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4527 },
4528 );
4529 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4530
4531 let now = bindings_ctx.now();
4532 assert_neighbor_state(
4533 &core_ctx,
4534 &mut bindings_ctx,
4535 DynamicNeighborState::Reachable(Reachable {
4536 link_address: LINK_ADDR1,
4537 last_confirmed_at: now,
4538 }),
4539 Some(ExpectedEvent::Changed),
4540 );
4541 assert_eq!(
4542 core_ctx.inner.take_frames(),
4543 pending_frames
4544 .into_iter()
4545 .map(|f| (
4546 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4547 f.as_ref().to_vec(),
4548 ))
4549 .collect::<Vec<_>>()
4550 );
4551 }
4552
4553 #[ip_test(I)]
4554 fn solicitation_failure_in_incomplete<I: TestIpExt>() {
4555 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4556 bindings_ctx.timers.assert_no_timers_installed();
4557 assert_eq!(core_ctx.inner.take_frames(), []);
4558
4559 let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
4560
4561 let timer_id = NudTimerId::neighbor();
4562
4563 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4564 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4565
4566 for i in 1..=max_multicast_solicit {
4567 assert_neighbor_state(
4568 &core_ctx,
4569 &mut bindings_ctx,
4570 DynamicNeighborState::Incomplete(Incomplete {
4571 transmit_counter: NonZeroU16::new(max_multicast_solicit - i),
4572 pending_frames: pending_frames
4573 .iter()
4574 .cloned()
4575 .map(|b| (b, FakeTxMetadata::default()))
4576 .collect(),
4577 notifiers: Vec::new(),
4578 _marker: PhantomData,
4579 }),
4580 None,
4581 );
4582
4583 bindings_ctx
4584 .timers
4585 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4586 assert_neighbor_probe_sent(&mut core_ctx, None);
4587
4588 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4589 }
4590
4591 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1);
4593 bindings_ctx.timers.assert_no_timers_installed();
4594
4595 assert_eq!(core_ctx.inner.take_frames(), []);
4599 assert_eq!(core_ctx.counters().as_ref().icmp_dest_unreachable_dropped.get(), 1);
4600 }
4601
4602 #[ip_test(I)]
4603 fn solicitation_failure_in_probe<I: TestIpExt>() {
4604 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4605 bindings_ctx.timers.assert_no_timers_installed();
4606 assert_eq!(core_ctx.inner.take_frames(), []);
4607
4608 init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
4609
4610 let timer_id = NudTimerId::neighbor();
4611 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4612 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4613 for i in 1..=max_unicast_solicit {
4614 assert_neighbor_state(
4615 &core_ctx,
4616 &mut bindings_ctx,
4617 DynamicNeighborState::Probe(Probe {
4618 transmit_counter: NonZeroU16::new(max_unicast_solicit - i),
4619 link_address: LINK_ADDR1,
4620 }),
4621 None,
4622 );
4623
4624 bindings_ctx
4625 .timers
4626 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4627 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4628
4629 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4630 }
4631
4632 assert_neighbor_state(
4633 &core_ctx,
4634 &mut bindings_ctx,
4635 DynamicNeighborState::Unreachable(Unreachable {
4636 link_address: LINK_ADDR1,
4637 mode: UnreachableMode::WaitingForPacketSend,
4638 }),
4639 Some(ExpectedEvent::Changed),
4640 );
4641 bindings_ctx.timers.assert_no_timers_installed();
4642 assert_eq!(core_ctx.inner.take_frames(), []);
4643 }
4644
4645 #[ip_test(I)]
4646 fn flush_entries<I: TestIpExt>() {
4647 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4648 bindings_ctx.timers.assert_no_timers_installed();
4649 assert_eq!(core_ctx.inner.take_frames(), []);
4650
4651 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4652 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR2, LINK_ADDR2);
4653 let pending_frames = init_incomplete_neighbor_with_ip(
4654 &mut core_ctx,
4655 &mut bindings_ctx,
4656 I::LOOKUP_ADDR3,
4657 true,
4658 );
4659 let pending_frames =
4660 pending_frames.into_iter().map(|b| (b, FakeTxMetadata::default())).collect();
4661
4662 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4663 assert_eq!(
4664 core_ctx.nud.state.neighbors,
4665 HashMap::from([
4666 (I::LOOKUP_ADDR1, NeighborState::Static(LINK_ADDR1)),
4667 (
4668 I::LOOKUP_ADDR2,
4669 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
4670 link_address: LINK_ADDR2,
4671 })),
4672 ),
4673 (
4674 I::LOOKUP_ADDR3,
4675 NeighborState::Dynamic(DynamicNeighborState::Incomplete(Incomplete {
4676 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4677 pending_frames,
4678 notifiers: Vec::new(),
4679 _marker: PhantomData,
4680 })),
4681 ),
4682 ]),
4683 );
4684 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4685 &mut bindings_ctx,
4686 [(I::LOOKUP_ADDR3, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
4687 );
4688
4689 NudHandler::flush(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId);
4691 let neighbors = &core_ctx.nud.state.neighbors;
4692 assert!(neighbors.is_empty(), "neighbor table should be empty: {:?}", neighbors);
4693 assert_eq!(
4694 bindings_ctx.take_events().into_iter().collect::<HashSet<_>>(),
4695 [I::LOOKUP_ADDR1, I::LOOKUP_ADDR2, I::LOOKUP_ADDR3]
4696 .into_iter()
4697 .map(|addr| { Event::removed(&FakeLinkDeviceId, addr, bindings_ctx.now()) })
4698 .collect(),
4699 );
4700 bindings_ctx.timers.assert_no_timers_installed();
4701 }
4702
4703 #[ip_test(I)]
4704 fn delete_dynamic_entry<I: TestIpExt>() {
4705 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4706 bindings_ctx.timers.assert_no_timers_installed();
4707 assert_eq!(core_ctx.inner.take_frames(), []);
4708
4709 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4710 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4711
4712 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4713
4714 let neighbors = &core_ctx.nud.state.neighbors;
4716 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4717 bindings_ctx.timers.assert_no_timers_installed();
4718 }
4719
4720 #[ip_test(I)]
4721 #[test_case(InitialState::Reachable; "reachable neighbor")]
4722 #[test_case(InitialState::Stale; "stale neighbor")]
4723 #[test_case(InitialState::Delay; "delay neighbor")]
4724 #[test_case(InitialState::Probe; "probe neighbor")]
4725 #[test_case(InitialState::Unreachable; "unreachable neighbor")]
4726 fn resolve_cached_linked_addr<I: TestIpExt>(initial_state: InitialState) {
4727 let mut ctx = new_context::<I>();
4728 ctx.bindings_ctx.timers.assert_no_timers_installed();
4729 assert_eq!(ctx.core_ctx.inner.take_frames(), []);
4730
4731 let _ = init_neighbor_in_state(&mut ctx.core_ctx, &mut ctx.bindings_ctx, initial_state);
4732
4733 let link_addr = assert_matches!(
4734 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4735 &FakeLinkDeviceId,
4736 &I::LOOKUP_ADDR1,
4737 ),
4738 LinkResolutionResult::Resolved(addr) => addr
4739 );
4740 assert_eq!(link_addr, LINK_ADDR1);
4741 if initial_state == InitialState::Stale {
4742 assert_eq!(
4743 ctx.bindings_ctx.take_events(),
4744 [Event::changed(
4745 &FakeLinkDeviceId,
4746 EventState::Dynamic(EventDynamicState::Delay(LINK_ADDR1)),
4747 I::LOOKUP_ADDR1,
4748 ctx.bindings_ctx.now(),
4749 )],
4750 );
4751 }
4752 }
4753
4754 enum ResolutionSuccess {
4755 Confirmation,
4756 StaticEntryAdded,
4757 }
4758
4759 #[ip_test(I)]
4760 #[test_case(ResolutionSuccess::Confirmation; "incomplete entry timed out")]
4761 #[test_case(ResolutionSuccess::StaticEntryAdded; "incomplete entry removed from table")]
4762 fn dynamic_neighbor_resolution_success<I: TestIpExt>(reason: ResolutionSuccess) {
4763 let mut ctx = new_context::<I>();
4764
4765 let observers = (0..10)
4766 .map(|_| {
4767 let observer = assert_matches!(
4768 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4769 &FakeLinkDeviceId,
4770 &I::LOOKUP_ADDR1,
4771 ),
4772 LinkResolutionResult::Pending(observer) => observer
4773 );
4774 assert_eq!(*observer.lock(), None);
4775 observer
4776 })
4777 .collect::<Vec<_>>();
4778 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
4779 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4780
4781 assert_neighbor_state(
4784 core_ctx,
4785 bindings_ctx,
4786 DynamicNeighborState::Incomplete(Incomplete {
4787 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4788 pending_frames: VecDeque::new(),
4789 notifiers: Vec::new(),
4791 _marker: PhantomData,
4792 }),
4793 Some(ExpectedEvent::Added),
4794 );
4795 assert_neighbor_probe_sent(core_ctx, None);
4796
4797 match reason {
4798 ResolutionSuccess::Confirmation => {
4799 NudHandler::handle_neighbor_update(
4801 core_ctx,
4802 bindings_ctx,
4803 &FakeLinkDeviceId,
4804 I::LOOKUP_ADDR1,
4805 DynamicNeighborUpdateSource::Confirmation {
4806 link_address: Some(LINK_ADDR1),
4807 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4808 },
4809 );
4810 let now = bindings_ctx.now();
4811 assert_neighbor_state(
4812 core_ctx,
4813 bindings_ctx,
4814 DynamicNeighborState::Reachable(Reachable {
4815 link_address: LINK_ADDR1,
4816 last_confirmed_at: now,
4817 }),
4818 Some(ExpectedEvent::Changed),
4819 );
4820 }
4821 ResolutionSuccess::StaticEntryAdded => {
4822 init_static_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, ExpectedEvent::Changed);
4823 assert_eq!(
4824 core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
4825 Some(&NeighborState::Static(LINK_ADDR1))
4826 );
4827 }
4828 }
4829
4830 for observer in observers {
4832 assert_eq!(*observer.lock(), Some(Ok(LINK_ADDR1)));
4833 }
4834 }
4835
4836 enum ResolutionFailure {
4837 Timeout,
4838 Removed,
4839 }
4840
4841 #[ip_test(I)]
4842 #[test_case(ResolutionFailure::Timeout; "incomplete entry timed out")]
4843 #[test_case(ResolutionFailure::Removed; "incomplete entry removed from table")]
4844 fn dynamic_neighbor_resolution_failure<I: TestIpExt>(reason: ResolutionFailure) {
4845 let mut ctx = new_context::<I>();
4846
4847 let observers = (0..10)
4848 .map(|_| {
4849 let observer = assert_matches!(
4850 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4851 &FakeLinkDeviceId,
4852 &I::LOOKUP_ADDR1,
4853 ),
4854 LinkResolutionResult::Pending(observer) => observer
4855 );
4856 assert_eq!(*observer.lock(), None);
4857 observer
4858 })
4859 .collect::<Vec<_>>();
4860
4861 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
4862 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4863
4864 assert_neighbor_state(
4867 core_ctx,
4868 bindings_ctx,
4869 DynamicNeighborState::Incomplete(Incomplete {
4870 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4871 pending_frames: VecDeque::new(),
4872 notifiers: Vec::new(),
4874 _marker: PhantomData,
4875 }),
4876 Some(ExpectedEvent::Added),
4877 );
4878 assert_neighbor_probe_sent(core_ctx, None);
4879
4880 match reason {
4881 ResolutionFailure::Timeout => {
4882 for _ in 1..=max_multicast_solicit {
4885 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4886 assert_eq!(
4887 bindings_ctx.trigger_timers_for(retrans_timer, core_ctx),
4888 [NudTimerId::neighbor()]
4889 );
4890 }
4891 }
4892 ResolutionFailure::Removed => {
4893 NudHandler::flush(core_ctx, bindings_ctx, &FakeLinkDeviceId);
4895 }
4896 }
4897
4898 assert_neighbor_removed_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1);
4899 for observer in observers {
4901 assert_eq!(*observer.lock(), Some(Err(AddressResolutionFailed)));
4902 }
4903 }
4904
4905 #[ip_test(I)]
4906 #[test_case(InitialState::Incomplete, false; "incomplete neighbor")]
4907 #[test_case(InitialState::Reachable, true; "reachable neighbor")]
4908 #[test_case(InitialState::Stale, true; "stale neighbor")]
4909 #[test_case(InitialState::Delay, true; "delay neighbor")]
4910 #[test_case(InitialState::Probe, true; "probe neighbor")]
4911 #[test_case(InitialState::Unreachable, true; "unreachable neighbor")]
4912 fn upper_layer_confirmation<I: TestIpExt>(
4913 initial_state: InitialState,
4914 should_transition_to_reachable: bool,
4915 ) {
4916 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4917 let base_reachable_time = core_ctx.inner.base_reachable_time().get();
4918
4919 let initial = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4920
4921 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
4922
4923 if !should_transition_to_reachable {
4924 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial, None);
4925 return;
4926 }
4927
4928 let now = bindings_ctx.now();
4930 assert_neighbor_state(
4931 &core_ctx,
4932 &mut bindings_ctx,
4933 DynamicNeighborState::Reachable(Reachable {
4934 link_address: LINK_ADDR1,
4935 last_confirmed_at: now,
4936 }),
4937 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
4938 );
4939 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4940 &mut bindings_ctx,
4941 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time)],
4942 );
4943
4944 bindings_ctx.timers.instant.sleep(base_reachable_time / 2);
4948 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
4949 let now = bindings_ctx.now();
4950 assert_neighbor_state(
4951 &core_ctx,
4952 &mut bindings_ctx,
4953 DynamicNeighborState::Reachable(Reachable {
4954 link_address: LINK_ADDR1,
4955 last_confirmed_at: now,
4956 }),
4957 None,
4958 );
4959 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4960 &mut bindings_ctx,
4961 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
4962 );
4963
4964 assert_eq!(
4967 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
4968 [NudTimerId::neighbor()]
4969 );
4970 let now = bindings_ctx.now();
4971 assert_neighbor_state(
4972 &core_ctx,
4973 &mut bindings_ctx,
4974 DynamicNeighborState::Reachable(Reachable {
4975 link_address: LINK_ADDR1,
4976 last_confirmed_at: now - base_reachable_time / 2,
4977 }),
4978 None,
4979 );
4980
4981 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4982 &mut bindings_ctx,
4983 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
4984 );
4985
4986 assert_eq!(
4989 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
4990 [NudTimerId::neighbor()]
4991 );
4992 assert_neighbor_state(
4993 &core_ctx,
4994 &mut bindings_ctx,
4995 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4996 Some(ExpectedEvent::Changed),
4997 );
4998 bindings_ctx.timers.assert_no_timers_installed();
4999 }
5000
5001 fn generate_ip_addr<I: Ip>(i: usize) -> SpecifiedAddr<I::Addr> {
5002 I::map_ip_out(
5003 i,
5004 |i| {
5005 let start = u32::from_be_bytes(net_ip_v4!("192.168.0.1").ipv4_bytes());
5006 let bytes = (start + u32::try_from(i).unwrap()).to_be_bytes();
5007 SpecifiedAddr::new(Ipv4Addr::new(bytes)).unwrap()
5008 },
5009 |i| {
5010 let start = u128::from_be_bytes(net_ip_v6!("fe80::1").ipv6_bytes());
5011 let bytes = (start + u128::try_from(i).unwrap()).to_be_bytes();
5012 SpecifiedAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap()
5013 },
5014 )
5015 }
5016
5017 #[ip_test(I)]
5018 fn garbage_collection_retains_static_entries<I: TestIpExt>() {
5019 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5020
5021 for i in 0..MAX_ENTRIES * 2 {
5025 if i % 2 == 0 {
5026 init_stale_neighbor_with_ip(
5027 &mut core_ctx,
5028 &mut bindings_ctx,
5029 generate_ip_addr::<I>(i),
5030 LINK_ADDR1,
5031 );
5032 } else {
5033 init_static_neighbor_with_ip(
5034 &mut core_ctx,
5035 &mut bindings_ctx,
5036 generate_ip_addr::<I>(i),
5037 LINK_ADDR1,
5038 ExpectedEvent::Added,
5039 );
5040 }
5041 }
5042 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES * 2);
5043
5044 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5046 for event in bindings_ctx.take_events() {
5047 assert_matches!(event, Event {
5048 device,
5049 addr: _,
5050 kind,
5051 at,
5052 } => {
5053 assert_eq!(kind, EventKind::Removed);
5054 assert_eq!(device, FakeLinkDeviceId);
5055 assert_eq!(at, bindings_ctx.now());
5056 });
5057 }
5058 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5059 for (_, neighbor) in core_ctx.nud.state.neighbors {
5060 assert_matches!(neighbor, NeighborState::Static(_));
5061 }
5062 }
5063
5064 #[ip_test(I)]
5065 fn garbage_collection_retains_in_use_entries<I: TestIpExt>() {
5066 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5067
5068 for i in 0..MAX_ENTRIES - 1 {
5070 init_static_neighbor_with_ip(
5071 &mut core_ctx,
5072 &mut bindings_ctx,
5073 generate_ip_addr::<I>(i),
5074 LINK_ADDR1,
5075 ExpectedEvent::Added,
5076 );
5077 }
5078
5079 let stale_entry = generate_ip_addr::<I>(MAX_ENTRIES - 1);
5081 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry, LINK_ADDR1);
5082 let reachable_entry = generate_ip_addr::<I>(MAX_ENTRIES);
5084 init_reachable_neighbor_with_ip(
5085 &mut core_ctx,
5086 &mut bindings_ctx,
5087 reachable_entry,
5088 LINK_ADDR1,
5089 );
5090
5091 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5093 super::testutil::assert_dynamic_neighbor_state(
5094 &mut core_ctx,
5095 FakeLinkDeviceId,
5096 reachable_entry,
5097 DynamicNeighborState::Reachable(Reachable {
5098 link_address: LINK_ADDR1,
5099 last_confirmed_at: bindings_ctx.now(),
5100 }),
5101 );
5102 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry);
5103 }
5104
5105 #[ip_test(I)]
5106 fn garbage_collection_triggered_on_new_stale_entry<I: TestIpExt>() {
5107 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5108 core_ctx.nud.state.last_gc = Some(bindings_ctx.now());
5110
5111 for i in 0..MAX_ENTRIES {
5113 init_static_neighbor_with_ip(
5114 &mut core_ctx,
5115 &mut bindings_ctx,
5116 generate_ip_addr::<I>(i),
5117 LINK_ADDR1,
5118 ExpectedEvent::Added,
5119 );
5120 }
5121
5122 init_stale_neighbor_with_ip(
5125 &mut core_ctx,
5126 &mut bindings_ctx,
5127 generate_ip_addr::<I>(MAX_ENTRIES + 1),
5128 LINK_ADDR1,
5129 );
5130 let expected_gc_time = bindings_ctx.now() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5131 bindings_ctx
5132 .timers
5133 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5134
5135 bindings_ctx.timers.instant.sleep(ONE_SECOND.get());
5139 init_stale_neighbor_with_ip(
5140 &mut core_ctx,
5141 &mut bindings_ctx,
5142 generate_ip_addr::<I>(MAX_ENTRIES + 2),
5143 LINK_ADDR1,
5144 );
5145 bindings_ctx
5146 .timers
5147 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5148 }
5149
5150 #[ip_test(I)]
5151 fn garbage_collection_triggered_on_transition_to_unreachable<I: TestIpExt>() {
5152 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5153 core_ctx.nud.state.last_gc = Some(bindings_ctx.now());
5155
5156 for i in 0..MAX_ENTRIES {
5158 init_static_neighbor_with_ip(
5159 &mut core_ctx,
5160 &mut bindings_ctx,
5161 generate_ip_addr::<I>(i),
5162 LINK_ADDR1,
5163 ExpectedEvent::Added,
5164 );
5165 }
5166 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5167
5168 init_unreachable_neighbor_with_ip(
5171 &mut core_ctx,
5172 &mut bindings_ctx,
5173 generate_ip_addr::<I>(MAX_ENTRIES),
5174 LINK_ADDR1,
5175 );
5176 let expected_gc_time =
5177 core_ctx.nud.state.last_gc.unwrap() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5178 bindings_ctx
5179 .timers
5180 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5181
5182 init_unreachable_neighbor_with_ip(
5185 &mut core_ctx,
5186 &mut bindings_ctx,
5187 generate_ip_addr::<I>(MAX_ENTRIES + 1),
5188 LINK_ADDR1,
5189 );
5190 bindings_ctx
5191 .timers
5192 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5193 }
5194
5195 #[ip_test(I)]
5196 fn garbage_collection_not_triggered_on_new_incomplete_entry<I: TestIpExt>() {
5197 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5198
5199 for i in 0..MAX_ENTRIES {
5201 init_static_neighbor_with_ip(
5202 &mut core_ctx,
5203 &mut bindings_ctx,
5204 generate_ip_addr::<I>(i),
5205 LINK_ADDR1,
5206 ExpectedEvent::Added,
5207 );
5208 }
5209 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5210
5211 let _: VecDeque<Buf<Vec<u8>>> = init_incomplete_neighbor_with_ip(
5212 &mut core_ctx,
5213 &mut bindings_ctx,
5214 generate_ip_addr::<I>(MAX_ENTRIES),
5215 true,
5216 );
5217 assert_eq!(
5218 bindings_ctx.timers.scheduled_instant(&mut core_ctx.nud.state.timer_heap.gc),
5219 None
5220 );
5221 }
5222
5223 #[ip_test(I)]
5224 fn confirmation_processed_even_if_no_target_link_layer_addr<I: TestIpExt>() {
5225 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5226
5227 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
5229
5230 NudHandler::handle_neighbor_update(
5234 &mut core_ctx,
5235 &mut bindings_ctx,
5236 &FakeLinkDeviceId,
5237 I::LOOKUP_ADDR1,
5238 DynamicNeighborUpdateSource::Confirmation {
5239 link_address: None,
5240 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
5241 },
5242 );
5243 let now = bindings_ctx.now();
5244 assert_neighbor_state(
5245 &core_ctx,
5246 &mut bindings_ctx,
5247 DynamicNeighborState::Reachable(Reachable {
5248 link_address: LINK_ADDR1,
5249 last_confirmed_at: now,
5250 }),
5251 Some(ExpectedEvent::Changed),
5252 );
5253 }
5254}