1use alloc::collections::{BinaryHeap, VecDeque};
8use alloc::vec::Vec;
9use core::fmt::Debug;
10use core::hash::Hash;
11use core::marker::PhantomData;
12use core::num::{NonZeroU16, NonZeroU32};
13use core::time::Duration;
14
15use assert_matches::assert_matches;
16use derivative::Derivative;
17use log::{debug, error, warn};
18use net_types::ip::{GenericOverIp, Ip, IpMarked, Ipv4, Ipv6};
19use net_types::{SpecifiedAddr, UnicastAddr};
20use netstack3_base::socket::{SocketIpAddr, SocketIpAddrExt as _};
21use netstack3_base::{
22 AddressResolutionFailed, AnyDevice, CoreTimerContext, Counter, CounterContext, DeviceIdContext,
23 ErrorAndSerializer, EventContext, HandleableTimer, Instant, InstantBindingsTypes, LinkDevice,
24 LinkDeviceAddress, LocalTimerHeap, NetworkSerializationContext, NetworkSerializer,
25 SendFrameError, SendFrameErrorReason, StrongDeviceIdentifier, TimerBindingsTypes, TimerContext,
26 TxMetadataBindingsTypes, WeakDeviceIdentifier,
27};
28use netstack3_hashmap::hash_map::{Entry, HashMap, OccupiedEntry};
29use packet::{
30 Buf, BufferMut, GrowBuffer as _, ParsablePacket as _, ParseBufferMut as _, SerializeError,
31};
32use packet_formats::ip::IpPacket as _;
33use packet_formats::ipv4::{Ipv4FragmentType, Ipv4Header as _, Ipv4Packet};
34use packet_formats::ipv6::Ipv6Packet;
35use packet_formats::utils::NonZeroDuration;
36use static_assertions::const_assert;
37use thiserror::Error;
38use zerocopy::SplitByteSlice;
39
40pub(crate) mod api;
41
42pub(crate) const DEFAULT_MAX_MULTICAST_SOLICIT: NonZeroU16 = NonZeroU16::new(3).unwrap();
47
48const DEFAULT_MAX_UNICAST_SOLICIT: NonZeroU16 = NonZeroU16::new(3).unwrap();
53
54const MAX_RETRANS_TIMER: NonZeroDuration = NonZeroDuration::from_secs(60).unwrap();
59
60pub const DEFAULT_RETRANS_TIMER: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
67
68const BACKOFF_MULTIPLE: NonZeroU32 = NonZeroU32::new(3).unwrap();
73
74const MAX_PENDING_FRAMES: usize = 10;
75
76const DEFAULT_BASE_REACHABLE_TIME: NonZeroDuration = NonZeroDuration::from_secs(30).unwrap();
81
82const DELAY_FIRST_PROBE_TIME: NonZeroDuration = NonZeroDuration::from_secs(5).unwrap();
87
88pub const GC_THRESHOLD: usize = 512;
93
94pub const MAX_ENTRIES: usize = 1024;
98const_assert!(MAX_ENTRIES > GC_THRESHOLD);
99
100const MIN_GARBAGE_COLLECTION_INTERVAL: NonZeroDuration = NonZeroDuration::from_secs(30).unwrap();
103
104#[derive(Default)]
106pub struct NudCountersInner {
107 pub icmp_dest_unreachable_dropped: Counter,
109}
110
111pub type NudCounters<I> = IpMarked<I, NudCountersInner>;
113
114#[derive(Debug, Copy, Clone)]
116pub struct ConfirmationFlags {
117 pub solicited_flag: bool,
119 pub override_flag: bool,
121}
122
123#[derive(Debug, Copy, Clone)]
125pub enum DynamicNeighborUpdateSource<A> {
126 Probe {
130 link_address: UnicastAddr<A>,
132 },
133
134 Confirmation {
138 link_address: Option<UnicastAddr<A>>,
140 flags: ConfirmationFlags,
142 },
143}
144
145#[derive(Derivative)]
147#[derivative(Debug(bound = ""))]
148#[cfg_attr(
149 any(test, feature = "testutils"),
150 derivative(
151 Clone(bound = "BT::TxMetadata: Clone"),
152 PartialEq(bound = "BT::TxMetadata: PartialEq"),
153 Eq(bound = "BT::TxMetadata: Eq")
154 )
155)]
156#[allow(missing_docs)]
157pub enum NeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
158 Dynamic(DynamicNeighborState<D, BT>),
159 Static(UnicastAddr<D::Address>),
160}
161
162#[derive(Derivative)]
169#[derivative(Debug(bound = ""))]
170#[cfg_attr(
171 any(test, feature = "testutils"),
172 derivative(
173 Clone(bound = "BT::TxMetadata: Clone"),
174 PartialEq(bound = "BT::TxMetadata: PartialEq"),
175 Eq(bound = "BT::TxMetadata: Eq")
176 )
177)]
178pub enum DynamicNeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
179 Incomplete(Incomplete<D, BT::Notifier, BT::TxMetadata>),
185
186 Reachable(Reachable<D, BT::Instant>),
191
192 Stale(Stale<D>),
203
204 Delay(Delay<D>),
216
217 Probe(Probe<D>),
221
222 Unreachable(Unreachable<D>),
229}
230
231#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
246pub enum EventDynamicState<L: LinkDeviceAddress> {
247 Incomplete,
250 Reachable(UnicastAddr<L>),
253 Stale(UnicastAddr<L>),
261 Delay(UnicastAddr<L>),
269 Probe(UnicastAddr<L>),
274 Unreachable(UnicastAddr<L>),
278}
279
280#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
288pub enum EventState<L: LinkDeviceAddress> {
289 Dynamic(EventDynamicState<L>),
291 Static(UnicastAddr<L>),
293}
294
295#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
297pub enum EventKind<L: LinkDeviceAddress> {
298 Added(EventState<L>),
300 Changed(EventState<L>),
302 Removed,
304}
305
306#[derive(Debug, Eq, Hash, PartialEq, GenericOverIp)]
308#[generic_over_ip(I, Ip)]
309pub struct Event<L: LinkDeviceAddress, DeviceId, I: Ip, Instant> {
310 pub device: DeviceId,
312 pub addr: SpecifiedAddr<I::Addr>,
314 pub kind: EventKind<L>,
316 pub at: Instant,
318}
319
320impl<L: LinkDeviceAddress, DeviceId, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
321 pub fn map_device<N, F: FnOnce(DeviceId) -> N>(self, map: F) -> Event<L, N, I, Instant> {
323 let Self { device, kind, addr, at } = self;
324 Event { device: map(device), kind, addr, at }
325 }
326}
327
328impl<L: LinkDeviceAddress, DeviceId: Clone, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
329 fn changed(
330 device: &DeviceId,
331 event_state: EventState<L>,
332 addr: SpecifiedAddr<I::Addr>,
333 at: Instant,
334 ) -> Self {
335 Self { device: device.clone(), kind: EventKind::Changed(event_state), addr, at }
336 }
337
338 fn added(
339 device: &DeviceId,
340 event_state: EventState<L>,
341 addr: SpecifiedAddr<I::Addr>,
342 at: Instant,
343 ) -> Self {
344 Self { device: device.clone(), kind: EventKind::Added(event_state), addr, at }
345 }
346
347 fn removed(device: &DeviceId, addr: SpecifiedAddr<I::Addr>, at: Instant) -> Self {
348 Self { device: device.clone(), kind: EventKind::Removed, addr, at }
349 }
350}
351
352fn schedule_timer_if_should_retransmit<I, D, DeviceId, CC, BC>(
353 core_ctx: &mut CC,
354 bindings_ctx: &mut BC,
355 timers: &mut TimerHeap<I, BC>,
356 neighbor: SpecifiedAddr<I::Addr>,
357 event: NudEvent,
358 counter: &mut Option<NonZeroU16>,
359) -> bool
360where
361 I: Ip,
362 D: LinkDevice,
363 DeviceId: StrongDeviceIdentifier,
364 BC: NudBindingsContext<I, D, DeviceId>,
365 CC: NudConfigContext<I>,
366{
367 match counter {
368 Some(c) => {
369 *counter = NonZeroU16::new(c.get() - 1);
370 let retransmit_timeout = core_ctx.retransmit_timeout();
371 timers.schedule_neighbor(bindings_ctx, retransmit_timeout, neighbor, event);
372 true
373 }
374 None => false,
375 }
376}
377
378#[derive(Debug, Derivative)]
380#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq(bound = "M: PartialEq"), Eq))]
381pub struct Incomplete<D: LinkDevice, N: LinkResolutionNotifier<D>, M> {
382 transmit_counter: Option<NonZeroU16>,
383 pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
384 #[derivative(PartialEq = "ignore")]
385 notifiers: Vec<N>,
386 _marker: PhantomData<D>,
387}
388
389#[cfg(any(test, feature = "testutils"))]
390impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M: Clone> Clone for Incomplete<D, N, M> {
391 fn clone(&self) -> Self {
392 let Self { transmit_counter, pending_frames, notifiers: _, _marker } = self;
396 Self {
397 transmit_counter: transmit_counter.clone(),
398 pending_frames: pending_frames.clone(),
399 notifiers: Vec::new(),
400 _marker: PhantomData,
401 }
402 }
403}
404
405impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Drop for Incomplete<D, N, M> {
406 fn drop(&mut self) {
407 let Self { transmit_counter: _, pending_frames: _, notifiers, _marker } = self;
408 for notifier in notifiers.drain(..) {
409 notifier.notify(Err(AddressResolutionFailed));
410 }
411 }
412}
413
414impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Incomplete<D, N, M> {
415 #[cfg(any(test, feature = "testutils"))]
418 pub fn new_with_pending_frames_and_transmit_counter(
419 pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
420 transmit_counter: Option<NonZeroU16>,
421 ) -> Self {
422 Self {
423 transmit_counter,
424 pending_frames,
425 notifiers: Default::default(),
426 _marker: PhantomData,
427 }
428 }
429
430 fn new<I, CC, BC, DeviceId>(
431 core_ctx: &mut CC,
432 bindings_ctx: &mut BC,
433 timers: &mut TimerHeap<I, BC>,
434 neighbor: SpecifiedAddr<I::Addr>,
435 ) -> Self
436 where
437 I: Ip,
438 D: LinkDevice,
439 BC: NudBindingsContext<I, D, DeviceId>,
440 CC: NudConfigContext<I>,
441 DeviceId: StrongDeviceIdentifier,
442 {
443 let mut this = Incomplete {
444 transmit_counter: Some(core_ctx.max_multicast_solicit()),
445 pending_frames: VecDeque::new(),
446 notifiers: Vec::new(),
447 _marker: PhantomData,
448 };
449 assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
453
454 this
455 }
456
457 fn new_with_notifier<I, CC, BC, DeviceId>(
458 core_ctx: &mut CC,
459 bindings_ctx: &mut BC,
460 timers: &mut TimerHeap<I, BC>,
461 neighbor: SpecifiedAddr<I::Addr>,
462 notifier: BC::Notifier,
463 ) -> Self
464 where
465 I: Ip,
466 D: LinkDevice,
467 BC: NudBindingsContext<I, D, DeviceId, Notifier = N>,
468 CC: NudConfigContext<I>,
469 DeviceId: StrongDeviceIdentifier,
470 {
471 let mut this = Incomplete {
472 transmit_counter: Some(core_ctx.max_multicast_solicit()),
473 pending_frames: VecDeque::new(),
474 notifiers: [notifier].into(),
475 _marker: PhantomData,
476 };
477 assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
481
482 this
483 }
484
485 fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
486 &mut self,
487 core_ctx: &mut CC,
488 bindings_ctx: &mut BC,
489 timers: &mut TimerHeap<I, BC>,
490 neighbor: SpecifiedAddr<I::Addr>,
491 ) -> bool
492 where
493 I: Ip,
494 D: LinkDevice,
495 DeviceId: StrongDeviceIdentifier,
496 BC: NudBindingsContext<I, D, DeviceId>,
497 CC: NudConfigContext<I>,
498 {
499 let Self { transmit_counter, pending_frames: _, notifiers: _, _marker } = self;
500 schedule_timer_if_should_retransmit(
501 core_ctx,
502 bindings_ctx,
503 timers,
504 neighbor,
505 NudEvent::RetransmitMulticastProbe,
506 transmit_counter,
507 )
508 }
509
510 fn queue_packet<B, S>(
511 &mut self,
512 body: S,
513 meta: M,
514 ) -> Result<(), ErrorAndSerializer<SerializeError<!>, S>>
515 where
516 B: BufferMut,
517 S: NetworkSerializer<Buffer = B>,
518 {
519 let Self { pending_frames, transmit_counter: _, notifiers: _, _marker } = self;
520
521 if pending_frames.len() < MAX_PENDING_FRAMES {
528 pending_frames.push_back((
529 body.serialize_vec_outer(&mut NetworkSerializationContext::default())
530 .map_err(|(error, serializer)| ErrorAndSerializer { error, serializer })?
531 .map_a(|b| Buf::new(b.as_ref().to_vec(), ..))
532 .into_inner(),
533 meta,
534 ));
535 }
536 Ok(())
537 }
538
539 fn complete_resolution<I, CC, BC>(
542 &mut self,
543 core_ctx: &mut CC,
544 bindings_ctx: &mut BC,
545 link_address: UnicastAddr<D::Address>,
546 ) where
547 I: Ip,
548 D: LinkDevice,
549 BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata = M>,
550 CC: NudSenderContext<I, D, BC>,
551 {
552 let Self { pending_frames, notifiers, transmit_counter: _, _marker } = self;
553
554 for (body, meta) in pending_frames.drain(..) {
562 core_ctx
566 .send_ip_packet_to_neighbor_link_addr(bindings_ctx, link_address, body, meta)
567 .unwrap_or_else(|err| {
568 error!("failed to send pending IP packet to neighbor {link_address:?} {err:?}")
569 })
570 }
571 for notifier in notifiers.drain(..) {
572 notifier.notify(Ok(link_address));
573 }
574 }
575}
576
577#[derive(Debug, Derivative)]
579#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
580pub struct Reachable<D: LinkDevice, I: Instant> {
581 pub link_address: UnicastAddr<D::Address>,
583 pub last_confirmed_at: I,
585}
586
587#[derive(Debug, Derivative)]
589#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
590pub struct Stale<D: LinkDevice> {
591 pub link_address: UnicastAddr<D::Address>,
593}
594
595impl<D: LinkDevice> Stale<D> {
596 fn enter_delay<I, BC, DeviceId: Clone>(
597 &mut self,
598 bindings_ctx: &mut BC,
599 timers: &mut TimerHeap<I, BC>,
600 neighbor: SpecifiedAddr<I::Addr>,
601 ) -> Delay<D>
602 where
603 I: Ip,
604 BC: NudBindingsContext<I, D, DeviceId>,
605 {
606 let Self { link_address } = *self;
607
608 timers.schedule_neighbor(
611 bindings_ctx,
612 DELAY_FIRST_PROBE_TIME,
613 neighbor,
614 NudEvent::DelayFirstProbe,
615 );
616
617 Delay { link_address }
618 }
619}
620
621#[derive(Debug, Derivative)]
623#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
624pub struct Delay<D: LinkDevice> {
625 pub link_address: UnicastAddr<D::Address>,
627}
628
629impl<D: LinkDevice> Delay<D> {
630 fn enter_probe<I, DeviceId, CC, BC>(
631 &mut self,
632 core_ctx: &mut CC,
633 bindings_ctx: &mut BC,
634 timers: &mut TimerHeap<I, BC>,
635 neighbor: SpecifiedAddr<I::Addr>,
636 ) -> Probe<D>
637 where
638 I: Ip,
639 DeviceId: StrongDeviceIdentifier,
640 BC: NudBindingsContext<I, D, DeviceId>,
641 CC: NudConfigContext<I>,
642 {
643 let Self { link_address } = *self;
644
645 let retransmit_timeout = core_ctx.retransmit_timeout();
649 timers.schedule_neighbor(
650 bindings_ctx,
651 retransmit_timeout,
652 neighbor,
653 NudEvent::RetransmitUnicastProbe,
654 );
655
656 Probe {
657 link_address,
658 transmit_counter: NonZeroU16::new(core_ctx.max_unicast_solicit().get() - 1),
659 }
660 }
661}
662
663#[derive(Debug, Derivative)]
664#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
665pub struct Probe<D: LinkDevice> {
666 link_address: UnicastAddr<D::Address>,
667 transmit_counter: Option<NonZeroU16>,
668}
669
670impl<D: LinkDevice> Probe<D> {
671 fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
672 &mut self,
673 core_ctx: &mut CC,
674 bindings_ctx: &mut BC,
675 timers: &mut TimerHeap<I, BC>,
676 neighbor: SpecifiedAddr<I::Addr>,
677 ) -> bool
678 where
679 I: Ip,
680 DeviceId: StrongDeviceIdentifier,
681 BC: NudBindingsContext<I, D, DeviceId>,
682 CC: NudConfigContext<I>,
683 {
684 let Self { link_address: _, transmit_counter } = self;
685 schedule_timer_if_should_retransmit(
686 core_ctx,
687 bindings_ctx,
688 timers,
689 neighbor,
690 NudEvent::RetransmitUnicastProbe,
691 transmit_counter,
692 )
693 }
694
695 fn enter_unreachable<I, BC, DeviceId>(
696 &mut self,
697 bindings_ctx: &mut BC,
698 timers: &mut TimerHeap<I, BC>,
699 num_entries: usize,
700 gc_state: &mut GarbageCollectionState<BC::Instant>,
701 ) -> Unreachable<D>
702 where
703 I: Ip,
704 BC: NudBindingsContext<I, D, DeviceId>,
705 DeviceId: Clone,
706 {
707 timers.maybe_schedule_gc(bindings_ctx, num_entries, gc_state);
711
712 let Self { link_address, transmit_counter: _ } = self;
713 Unreachable { link_address: *link_address, mode: UnreachableMode::WaitingForPacketSend }
714 }
715}
716
717#[derive(Debug, Derivative)]
718#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
719pub struct Unreachable<D: LinkDevice> {
720 link_address: UnicastAddr<D::Address>,
721 mode: UnreachableMode,
722}
723
724#[derive(Debug, Clone, Copy, Derivative)]
736#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq, Eq))]
737pub(crate) enum UnreachableMode {
738 WaitingForPacketSend,
739 Backoff { probes_sent: NonZeroU32, packet_sent: bool },
740}
741
742impl UnreachableMode {
743 fn next_backoff_retransmit_timeout<I, CC>(&self, core_ctx: &mut CC) -> NonZeroDuration
749 where
750 I: Ip,
751 CC: NudConfigContext<I>,
752 {
753 let probes_sent = match self {
754 UnreachableMode::Backoff { probes_sent, packet_sent: _ } => probes_sent,
755 UnreachableMode::WaitingForPacketSend => {
756 panic!("cannot calculate exponential backoff in state {self:?}")
757 }
758 };
759 (core_ctx.retransmit_timeout() * BACKOFF_MULTIPLE.saturating_pow(probes_sent.get()))
763 .min(MAX_RETRANS_TIMER)
764 }
765}
766
767impl<D: LinkDevice> Unreachable<D> {
768 fn handle_timer<I, DeviceId, CC, BC>(
769 &mut self,
770 core_ctx: &mut CC,
771 bindings_ctx: &mut BC,
772 timers: &mut TimerHeap<I, BC>,
773 device_id: &DeviceId,
774 neighbor: SpecifiedAddr<I::Addr>,
775 ) -> Option<TransmitProbe<UnicastAddr<D::Address>>>
776 where
777 I: Ip,
778 DeviceId: StrongDeviceIdentifier,
779 BC: NudBindingsContext<I, D, DeviceId>,
780 CC: NudConfigContext<I>,
781 {
782 let Self { link_address: _, mode } = self;
783 match mode {
784 UnreachableMode::WaitingForPacketSend => {
785 panic!(
786 "timer should not have fired in UNREACHABLE while waiting for packet send; got \
787 a retransmit multicast probe event for {neighbor} on {device_id:?}",
788 );
789 }
790 UnreachableMode::Backoff { probes_sent, packet_sent } => {
791 if *packet_sent {
792 *probes_sent = probes_sent.saturating_add(1);
799 *packet_sent = false;
800
801 let duration = mode.next_backoff_retransmit_timeout(core_ctx);
802 timers.schedule_neighbor(
803 bindings_ctx,
804 duration,
805 neighbor,
806 NudEvent::RetransmitMulticastProbe,
807 );
808
809 Some(TransmitProbe::Multicast)
810 } else {
811 *mode = UnreachableMode::WaitingForPacketSend;
812
813 None
814 }
815 }
816 }
817 }
818
819 fn handle_packet_queued_to_send<I, DeviceId, CC, BC>(
824 &mut self,
825 core_ctx: &mut CC,
826 bindings_ctx: &mut BC,
827 timers: &mut TimerHeap<I, BC>,
828 neighbor: SpecifiedAddr<I::Addr>,
829 ) -> bool
830 where
831 I: Ip,
832 DeviceId: StrongDeviceIdentifier,
833 BC: NudBindingsContext<I, D, DeviceId>,
834 CC: NudConfigContext<I>,
835 {
836 let Self { link_address: _, mode } = self;
837 match mode {
838 UnreachableMode::WaitingForPacketSend => {
839 let probes_sent = NonZeroU32::new(1).unwrap();
854 *mode = UnreachableMode::Backoff { probes_sent, packet_sent: false };
855
856 let duration = mode.next_backoff_retransmit_timeout(core_ctx);
857 timers.schedule_neighbor(
858 bindings_ctx,
859 duration,
860 neighbor,
861 NudEvent::RetransmitMulticastProbe,
862 );
863
864 true
866 }
867 UnreachableMode::Backoff { probes_sent: _, packet_sent } => {
868 *packet_sent = true;
872
873 false
874 }
875 }
876 }
877}
878
879#[derive(Debug, PartialEq, Eq, Error)]
880pub(crate) enum EnterProbeError {
881 #[error("link address is unknown")]
882 LinkAddressUnknown,
883}
884
885impl<D: LinkDevice, BT: NudBindingsTypes<D>> NeighborState<D, BT> {
886 fn to_event_state(&self) -> EventState<D::Address> {
887 match self {
888 NeighborState::Dynamic(dynamic_state) => {
889 EventState::Dynamic(dynamic_state.to_event_dynamic_state())
890 }
891 NeighborState::Static(addr) => EventState::Static(*addr),
892 }
893 }
894
895 pub(crate) fn enter_probe<I, DeviceId, CC>(
905 &mut self,
906 core_ctx: &mut CC,
907 bindings_ctx: &mut BT,
908 timers: &mut TimerHeap<I, BT>,
909 neighbor: SpecifiedAddr<I::Addr>,
910 device_id: &DeviceId,
911 ) -> Result<Option<UnicastAddr<D::Address>>, EnterProbeError>
912 where
913 I: Ip,
914 DeviceId: StrongDeviceIdentifier,
915 BT: NudBindingsContext<I, D, DeviceId>,
916 CC: NudConfigContext<I>,
917 {
918 let link_addr = match self {
919 NeighborState::Dynamic(dynamic_state) => {
920 let link_addr = match dynamic_state {
921 DynamicNeighborState::Reachable(Reachable { link_address, .. }) => {
922 *link_address
923 }
924 DynamicNeighborState::Stale(Stale { link_address }) => *link_address,
925 DynamicNeighborState::Delay(Delay { link_address }) => *link_address,
926 DynamicNeighborState::Unreachable(Unreachable { link_address, .. }) => {
927 *link_address
928 }
929 DynamicNeighborState::Incomplete(_) => {
932 return Err(EnterProbeError::LinkAddressUnknown);
933 }
934 DynamicNeighborState::Probe(_) => return Ok(None),
937 };
938
939 dynamic_state.cancel_timer(bindings_ctx, timers, neighbor);
942 link_addr
943 }
944 NeighborState::Static(link_address) => *link_address,
945 };
946
947 let retransmit_timeout = core_ctx.retransmit_timeout();
949 timers.schedule_neighbor(
950 bindings_ctx,
951 retransmit_timeout,
952 neighbor,
953 NudEvent::RetransmitUnicastProbe,
954 );
955
956 *self = NeighborState::Dynamic(DynamicNeighborState::Probe(Probe {
958 link_address: link_addr,
959 transmit_counter: NonZeroU16::new(core_ctx.max_unicast_solicit().get() - 1),
960 }));
961 let event_state = self.to_event_state();
962 bindings_ctx.on_event(Event::changed(
963 &device_id,
964 event_state,
965 neighbor,
966 bindings_ctx.now(),
967 ));
968 Ok(Some(link_addr))
969 }
970}
971
972impl<D: LinkDevice, BC: NudBindingsTypes<D>> DynamicNeighborState<D, BC> {
973 fn cancel_timer<I, DeviceId>(
974 &mut self,
975 bindings_ctx: &mut BC,
976 timers: &mut TimerHeap<I, BC>,
977 neighbor: SpecifiedAddr<I::Addr>,
978 ) where
979 I: Ip,
980 DeviceId: StrongDeviceIdentifier,
981 BC: NudBindingsContext<I, D, DeviceId>,
982 {
983 let expected_event = match self {
984 DynamicNeighborState::Incomplete(Incomplete {
985 transmit_counter: _,
986 pending_frames: _,
987 notifiers: _,
988 _marker,
989 }) => Some(NudEvent::RetransmitMulticastProbe),
990 DynamicNeighborState::Reachable(Reachable {
991 link_address: _,
992 last_confirmed_at: _,
993 }) => Some(NudEvent::ReachableTime),
994 DynamicNeighborState::Stale(Stale { link_address: _ }) => None,
995 DynamicNeighborState::Delay(Delay { link_address: _ }) => {
996 Some(NudEvent::DelayFirstProbe)
997 }
998 DynamicNeighborState::Probe(Probe { link_address: _, transmit_counter: _ }) => {
999 Some(NudEvent::RetransmitUnicastProbe)
1000 }
1001 DynamicNeighborState::Unreachable(Unreachable { link_address: _, mode }) => {
1002 match mode {
1005 UnreachableMode::WaitingForPacketSend => None,
1006 UnreachableMode::Backoff { probes_sent: _, packet_sent: _ } => {
1007 Some(NudEvent::RetransmitMulticastProbe)
1008 }
1009 }
1010 }
1011 };
1012 assert_eq!(
1013 timers.cancel_neighbor(bindings_ctx, neighbor),
1014 expected_event,
1015 "neighbor {neighbor} ({self:?}) had unexpected timer installed"
1016 );
1017 }
1018
1019 fn cancel_timer_and_complete_resolution<I, CC>(
1020 mut self,
1021 core_ctx: &mut CC,
1022 bindings_ctx: &mut BC,
1023 timers: &mut TimerHeap<I, BC>,
1024 neighbor: SpecifiedAddr<I::Addr>,
1025 link_address: UnicastAddr<D::Address>,
1026 ) where
1027 I: Ip,
1028 BC: NudBindingsContext<I, D, CC::DeviceId>,
1029 CC: NudSenderContext<I, D, BC>,
1030 {
1031 self.cancel_timer(bindings_ctx, timers, neighbor);
1032
1033 match self {
1034 DynamicNeighborState::Incomplete(mut incomplete) => {
1035 incomplete.complete_resolution(core_ctx, bindings_ctx, link_address);
1036 }
1037 DynamicNeighborState::Reachable(_)
1038 | DynamicNeighborState::Stale(_)
1039 | DynamicNeighborState::Delay(_)
1040 | DynamicNeighborState::Probe(_)
1041 | DynamicNeighborState::Unreachable(_) => {}
1042 }
1043 }
1044
1045 fn to_event_dynamic_state(&self) -> EventDynamicState<D::Address> {
1046 match self {
1047 Self::Incomplete(_) => EventDynamicState::Incomplete,
1048 Self::Reachable(Reachable { link_address, last_confirmed_at: _ }) => {
1049 EventDynamicState::Reachable(*link_address)
1050 }
1051 Self::Stale(Stale { link_address }) => EventDynamicState::Stale(*link_address),
1052 Self::Delay(Delay { link_address }) => EventDynamicState::Delay(*link_address),
1053 Self::Probe(Probe { link_address, transmit_counter: _ }) => {
1054 EventDynamicState::Probe(*link_address)
1055 }
1056 Self::Unreachable(Unreachable { link_address, mode: _ }) => {
1057 EventDynamicState::Unreachable(*link_address)
1058 }
1059 }
1060 }
1061
1062 fn enter_reachable<I, CC>(
1064 &mut self,
1065 core_ctx: &mut CC,
1066 bindings_ctx: &mut BC,
1067 timers: &mut TimerHeap<I, BC>,
1068 device_id: &CC::DeviceId,
1069 neighbor: SpecifiedAddr<I::Addr>,
1070 link_address: UnicastAddr<D::Address>,
1071 ) where
1072 I: Ip,
1073 BC: NudBindingsContext<I, D, CC::DeviceId>,
1074 CC: NudSenderContext<I, D, BC>,
1075 {
1076 let now = bindings_ctx.now();
1079 match self {
1080 DynamicNeighborState::Reachable(Reachable {
1085 link_address: current,
1086 last_confirmed_at,
1087 }) if *current == link_address => {
1088 *last_confirmed_at = now;
1089 return;
1090 }
1091 DynamicNeighborState::Incomplete(_)
1092 | DynamicNeighborState::Reachable(_)
1093 | DynamicNeighborState::Stale(_)
1094 | DynamicNeighborState::Delay(_)
1095 | DynamicNeighborState::Probe(_)
1096 | DynamicNeighborState::Unreachable(_) => {}
1097 }
1098 let previous = core::mem::replace(
1099 self,
1100 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: now }),
1101 );
1102 let event_dynamic_state = self.to_event_dynamic_state();
1103 debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1104 let event_state = EventState::Dynamic(event_dynamic_state);
1105 bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1106 previous.cancel_timer_and_complete_resolution(
1107 core_ctx,
1108 bindings_ctx,
1109 timers,
1110 neighbor,
1111 link_address,
1112 );
1113 timers.schedule_neighbor(
1114 bindings_ctx,
1115 core_ctx.base_reachable_time(),
1116 neighbor,
1117 NudEvent::ReachableTime,
1118 );
1119 }
1120
1121 fn enter_stale<I, CC>(
1129 &mut self,
1130 core_ctx: &mut CC,
1131 bindings_ctx: &mut BC,
1132 timers: &mut TimerHeap<I, BC>,
1133 device_id: &CC::DeviceId,
1134 neighbor: SpecifiedAddr<I::Addr>,
1135 link_address: UnicastAddr<D::Address>,
1136 num_entries: usize,
1137 gc_state: &mut GarbageCollectionState<BC::Instant>,
1138 ) where
1139 I: Ip,
1140 BC: NudBindingsContext<I, D, CC::DeviceId>,
1141 CC: NudSenderContext<I, D, BC>,
1142 {
1143 let previous =
1146 core::mem::replace(self, DynamicNeighborState::Stale(Stale { link_address }));
1147 let event_dynamic_state = self.to_event_dynamic_state();
1148 debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1149 let event_state = EventState::Dynamic(event_dynamic_state);
1150 bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1151 previous.cancel_timer_and_complete_resolution(
1152 core_ctx,
1153 bindings_ctx,
1154 timers,
1155 neighbor,
1156 link_address,
1157 );
1158
1159 timers.maybe_schedule_gc(bindings_ctx, num_entries, gc_state);
1163
1164 }
1167
1168 fn resolve_link_addr<I, DeviceId, CC>(
1174 &mut self,
1175 core_ctx: &mut CC,
1176 bindings_ctx: &mut BC,
1177 timers: &mut TimerHeap<I, BC>,
1178 device_id: &DeviceId,
1179 neighbor: SpecifiedAddr<I::Addr>,
1180 ) -> (
1181 LinkResolutionResult<
1182 UnicastAddr<D::Address>,
1183 <<BC as LinkResolutionContext<D>>::Notifier as LinkResolutionNotifier<D>>::Observer,
1184 >,
1185 bool,
1186 )
1187 where
1188 I: Ip,
1189 DeviceId: StrongDeviceIdentifier,
1190 BC: NudBindingsContext<I, D, DeviceId>,
1191 CC: NudConfigContext<I>,
1192 {
1193 match self {
1194 DynamicNeighborState::Incomplete(Incomplete {
1195 notifiers,
1196 transmit_counter: _,
1197 pending_frames: _,
1198 _marker,
1199 }) => {
1200 let (notifier, observer) = BC::Notifier::new();
1201 notifiers.push(notifier);
1202
1203 (LinkResolutionResult::Pending(observer), false)
1204 }
1205 DynamicNeighborState::Stale(entry) => {
1206 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 (LinkResolutionResult::Resolved(link_address), false)
1226 }
1227 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1228 | DynamicNeighborState::Delay(Delay { link_address })
1229 | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1230 (LinkResolutionResult::Resolved(*link_address), false)
1231 }
1232 DynamicNeighborState::Unreachable(unreachable) => {
1233 let Unreachable { link_address, mode: _ } = unreachable;
1234 let link_address = *link_address;
1235
1236 let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1238 core_ctx,
1239 bindings_ctx,
1240 timers,
1241 neighbor,
1242 );
1243 (LinkResolutionResult::Resolved(link_address), do_multicast_solicit)
1244 }
1245 }
1246 }
1247
1248 fn handle_packet_queued_to_send<I, CC, S>(
1254 &mut self,
1255 core_ctx: &mut CC,
1256 bindings_ctx: &mut BC,
1257 timers: &mut TimerHeap<I, BC>,
1258 device_id: &CC::DeviceId,
1259 neighbor: SpecifiedAddr<I::Addr>,
1260 body: S,
1261 meta: BC::TxMetadata,
1262 ) -> Result<bool, SendFrameError<S>>
1263 where
1264 I: Ip,
1265 BC: NudBindingsContext<I, D, CC::DeviceId>,
1266 CC: NudSenderContext<I, D, BC>,
1267 S: NetworkSerializer,
1268 S::Buffer: BufferMut,
1269 {
1270 match self {
1271 DynamicNeighborState::Incomplete(incomplete) => {
1272 incomplete.queue_packet(body, meta).map(|()| false).map_err(|e| e.err_into())
1273 }
1274 DynamicNeighborState::Stale(entry) => {
1281 let delay @ Delay { link_address } =
1289 entry.enter_delay(bindings_ctx, timers, neighbor);
1290 *self = DynamicNeighborState::Delay(delay);
1291 let event_state = EventState::Dynamic(self.to_event_dynamic_state());
1292 bindings_ctx.on_event(Event::changed(
1293 device_id,
1294 event_state,
1295 neighbor,
1296 bindings_ctx.now(),
1297 ));
1298
1299 core_ctx.send_ip_packet_to_neighbor_link_addr(
1300 bindings_ctx,
1301 link_address,
1302 body,
1303 meta,
1304 )?;
1305
1306 Ok(false)
1307 }
1308 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1309 | DynamicNeighborState::Delay(Delay { link_address })
1310 | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1311 core_ctx.send_ip_packet_to_neighbor_link_addr(
1312 bindings_ctx,
1313 *link_address,
1314 body,
1315 meta,
1316 )?;
1317
1318 Ok(false)
1319 }
1320 DynamicNeighborState::Unreachable(unreachable) => {
1321 let Unreachable { link_address, mode: _ } = unreachable;
1322 core_ctx.send_ip_packet_to_neighbor_link_addr(
1323 bindings_ctx,
1324 *link_address,
1325 body,
1326 meta,
1327 )?;
1328
1329 let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1330 core_ctx,
1331 bindings_ctx,
1332 timers,
1333 neighbor,
1334 );
1335 Ok(do_multicast_solicit)
1336 }
1337 }
1338 }
1339
1340 fn handle_probe<I, CC>(
1341 &mut self,
1342 core_ctx: &mut CC,
1343 bindings_ctx: &mut BC,
1344 timers: &mut TimerHeap<I, BC>,
1345 device_id: &CC::DeviceId,
1346 neighbor: SpecifiedAddr<I::Addr>,
1347 incoming_link_address: UnicastAddr<D::Address>,
1348 num_entries: usize,
1349 gc_state: &mut GarbageCollectionState<BC::Instant>,
1350 ) where
1351 I: Ip,
1352 BC: NudBindingsContext<I, D, CC::DeviceId>,
1353 CC: NudSenderContext<I, D, BC>,
1354 {
1355 let transition_to_stale = match self {
1364 DynamicNeighborState::Incomplete(_) => true,
1365 DynamicNeighborState::Reachable(Reachable {
1366 link_address: current,
1367 last_confirmed_at: _,
1368 })
1369 | DynamicNeighborState::Stale(Stale { link_address: current })
1370 | DynamicNeighborState::Delay(Delay { link_address: current })
1371 | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1372 | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1373 current != &incoming_link_address
1374 }
1375 };
1376 if transition_to_stale {
1377 self.enter_stale(
1378 core_ctx,
1379 bindings_ctx,
1380 timers,
1381 device_id,
1382 neighbor,
1383 incoming_link_address,
1384 num_entries,
1385 gc_state,
1386 );
1387 }
1388 }
1389
1390 fn handle_confirmation<I, CC>(
1391 &mut self,
1392 core_ctx: &mut CC,
1393 bindings_ctx: &mut BC,
1394 timers: &mut TimerHeap<I, BC>,
1395 device_id: &CC::DeviceId,
1396 neighbor: SpecifiedAddr<I::Addr>,
1397 incoming_link_address: Option<UnicastAddr<D::Address>>,
1398 flags: ConfirmationFlags,
1399 num_entries: usize,
1400 gc_state: &mut GarbageCollectionState<BC::Instant>,
1401 ) where
1402 I: Ip,
1403 BC: NudBindingsContext<I, D, CC::DeviceId>,
1404 CC: NudSenderContext<I, D, BC>,
1405 {
1406 let ConfirmationFlags { solicited_flag, override_flag } = flags;
1407 enum NewState<A> {
1408 Reachable { link_address: A },
1409 Stale { link_address: A },
1410 }
1411
1412 let new_state = match self {
1413 DynamicNeighborState::Incomplete(Incomplete {
1414 transmit_counter: _,
1415 pending_frames: _,
1416 notifiers: _,
1417 _marker,
1418 }) => {
1419 incoming_link_address.map(|link_address| {
1431 if solicited_flag {
1432 NewState::Reachable { link_address }
1433 } else {
1434 NewState::Stale { link_address }
1435 }
1436 })
1437 }
1438
1439 DynamicNeighborState::Reachable(Reachable {
1443 link_address: current,
1444 last_confirmed_at,
1445 }) if !core_ctx.override_lock_time().is_zero()
1446 && solicited_flag
1447 && bindings_ctx.now().saturating_duration_since(*last_confirmed_at)
1448 < core_ctx.override_lock_time() =>
1449 {
1450 if incoming_link_address.as_ref().is_some_and(|addr| addr != current) {
1456 warn!(
1457 "Ignoring duplicate neighbor confirmation for {:?}. Current address {:?}. \
1458 New address {:?}",
1459 neighbor, current, incoming_link_address
1460 );
1461 } else {
1462 debug!(
1463 "Ignoring duplicate neighbor confirmation for {:?}. Current address {:?}. \
1464 New address {:?}",
1465 neighbor, current, incoming_link_address
1466 );
1467 }
1468 None
1469 }
1470
1471 DynamicNeighborState::Reachable(Reachable {
1472 link_address: current,
1473 last_confirmed_at: _,
1474 })
1475 | DynamicNeighborState::Stale(Stale { link_address: current })
1476 | DynamicNeighborState::Delay(Delay { link_address: current })
1477 | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1478 | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1479 let updated_link_address = incoming_link_address
1498 .and_then(|link_address| (*current != link_address).then_some(link_address));
1499
1500 match (solicited_flag, updated_link_address, override_flag) {
1501 (true, _, true) | (true, None, _) => Some(NewState::Reachable {
1507 link_address: incoming_link_address.unwrap_or(*current),
1508 }),
1509 (_, Some(_), false) => match self {
1519 DynamicNeighborState::Reachable(Reachable {
1521 link_address: current,
1522 last_confirmed_at: _,
1523 }) => Some(NewState::Stale { link_address: *current }),
1524 DynamicNeighborState::Stale(_)
1526 | DynamicNeighborState::Delay(_)
1527 | DynamicNeighborState::Probe(_)
1528 | DynamicNeighborState::Unreachable(_) => None,
1529 DynamicNeighborState::Incomplete(_) => unreachable!(),
1531 },
1532 (false, Some(link_address), true) => Some(NewState::Stale { link_address }),
1538 (false, None, _) => None,
1543 }
1544 }
1545 };
1546 match new_state {
1547 Some(NewState::Reachable { link_address }) => self.enter_reachable(
1548 core_ctx,
1549 bindings_ctx,
1550 timers,
1551 device_id,
1552 neighbor,
1553 link_address,
1554 ),
1555 Some(NewState::Stale { link_address }) => self.enter_stale(
1556 core_ctx,
1557 bindings_ctx,
1558 timers,
1559 device_id,
1560 neighbor,
1561 link_address,
1562 num_entries,
1563 gc_state,
1564 ),
1565 None => {}
1566 }
1567 }
1568}
1569
1570#[cfg(any(test, feature = "testutils"))]
1571pub(crate) mod testutil {
1572 use super::*;
1573
1574 use alloc::sync::Arc;
1575
1576 use netstack3_base::sync::Mutex;
1577 use netstack3_base::testutil::{FakeBindingsCtx, FakeCoreCtx};
1578
1579 pub fn assert_dynamic_neighbor_with_addr<
1581 I: Ip,
1582 D: LinkDevice,
1583 BC: NudBindingsContext<I, D, CC::DeviceId>,
1584 CC: NudContext<I, D, BC>,
1585 >(
1586 core_ctx: &mut CC,
1587 device_id: CC::DeviceId,
1588 neighbor: SpecifiedAddr<I::Addr>,
1589 expected_link_addr: UnicastAddr<D::Address>,
1590 ) {
1591 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1592 assert_matches!(
1593 neighbors.get(&neighbor),
1594 Some(NeighborState::Dynamic(
1595 DynamicNeighborState::Reachable(Reachable{ link_address, last_confirmed_at: _ })
1596 | DynamicNeighborState::Stale(Stale{ link_address })
1597 )) => {
1598 assert_eq!(link_address, &expected_link_addr)
1599 }
1600 )
1601 })
1602 }
1603
1604 pub fn assert_dynamic_neighbor_state<I, D, BC, CC>(
1606 core_ctx: &mut CC,
1607 device_id: CC::DeviceId,
1608 neighbor: SpecifiedAddr<I::Addr>,
1609 expected_state: DynamicNeighborState<D, BC>,
1610 ) where
1611 I: Ip,
1612 D: LinkDevice + PartialEq,
1613 BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata: PartialEq>,
1614 CC: NudContext<I, D, BC>,
1615 {
1616 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1617 assert_matches!(
1618 neighbors.get(&neighbor),
1619 Some(NeighborState::Dynamic(state)) => {
1620 assert_eq!(state, &expected_state)
1621 }
1622 )
1623 })
1624 }
1625
1626 pub fn assert_neighbor_unknown<
1628 I: Ip,
1629 D: LinkDevice,
1630 BC: NudBindingsContext<I, D, CC::DeviceId>,
1631 CC: NudContext<I, D, BC>,
1632 >(
1633 core_ctx: &mut CC,
1634 device_id: CC::DeviceId,
1635 neighbor: SpecifiedAddr<I::Addr>,
1636 ) {
1637 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1638 assert_matches!(neighbors.get(&neighbor), None)
1639 })
1640 }
1641
1642 impl<D: LinkDevice, Id, Event: Debug, State, FrameMeta> LinkResolutionContext<D>
1643 for FakeBindingsCtx<Id, Event, State, FrameMeta>
1644 {
1645 type Notifier = FakeLinkResolutionNotifier<D>;
1646 }
1647
1648 #[derive(Debug)]
1650 pub struct FakeLinkResolutionNotifier<D: LinkDevice>(
1651 Arc<Mutex<Option<Result<UnicastAddr<D::Address>, AddressResolutionFailed>>>>,
1652 );
1653
1654 impl<D: LinkDevice> LinkResolutionNotifier<D> for FakeLinkResolutionNotifier<D> {
1655 type Observer =
1656 Arc<Mutex<Option<Result<UnicastAddr<D::Address>, AddressResolutionFailed>>>>;
1657
1658 fn new() -> (Self, Self::Observer) {
1659 let inner = Arc::new(Mutex::new(None));
1660 (Self(inner.clone()), inner)
1661 }
1662
1663 fn notify(self, result: Result<UnicastAddr<D::Address>, AddressResolutionFailed>) {
1664 let Self(inner) = self;
1665 let mut inner = inner.lock();
1666 assert_eq!(*inner, None, "resolved link address was set more than once");
1667 *inner = Some(result);
1668 }
1669 }
1670
1671 impl<S, Meta, DeviceId> UseDelegateNudContext for FakeCoreCtx<S, Meta, DeviceId> where
1672 S: UseDelegateNudContext
1673 {
1674 }
1675 impl<I: Ip, S, Meta, DeviceId> DelegateNudContext<I> for FakeCoreCtx<S, Meta, DeviceId>
1676 where
1677 S: DelegateNudContext<I>,
1678 {
1679 type Delegate<T> = S::Delegate<T>;
1680 }
1681}
1682
1683#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1684enum NudEvent {
1685 RetransmitMulticastProbe,
1686 ReachableTime,
1687 DelayFirstProbe,
1688 RetransmitUnicastProbe,
1689}
1690
1691#[derive(GenericOverIp, Copy, Clone, Debug, Eq, PartialEq, Hash)]
1693#[generic_over_ip(I, Ip)]
1694pub struct NudTimerId<I: Ip, L: LinkDevice, D: WeakDeviceIdentifier> {
1695 device_id: D,
1696 timer_type: NudTimerType,
1697 _marker: PhantomData<(I, L)>,
1698}
1699
1700#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1701enum NudTimerType {
1702 Neighbor,
1703 GarbageCollection,
1704}
1705
1706#[derive(Debug)]
1708pub(crate) struct TimerHeap<I: Ip, BT: TimerBindingsTypes + InstantBindingsTypes> {
1709 gc: BT::Timer,
1710 neighbor: LocalTimerHeap<SpecifiedAddr<I::Addr>, NudEvent, BT>,
1711}
1712
1713impl<I: Ip, BC: TimerContext> TimerHeap<I, BC> {
1714 fn new<
1715 DeviceId: WeakDeviceIdentifier,
1716 L: LinkDevice,
1717 CC: CoreTimerContext<NudTimerId<I, L, DeviceId>, BC>,
1718 >(
1719 bindings_ctx: &mut BC,
1720 device_id: DeviceId,
1721 ) -> Self {
1722 Self {
1723 neighbor: LocalTimerHeap::new_with_context::<_, CC>(
1724 bindings_ctx,
1725 NudTimerId {
1726 device_id: device_id.clone(),
1727 timer_type: NudTimerType::Neighbor,
1728 _marker: PhantomData,
1729 },
1730 ),
1731 gc: CC::new_timer(
1732 bindings_ctx,
1733 NudTimerId {
1734 device_id,
1735 timer_type: NudTimerType::GarbageCollection,
1736 _marker: PhantomData,
1737 },
1738 ),
1739 }
1740 }
1741
1742 fn schedule_neighbor(
1743 &mut self,
1744 bindings_ctx: &mut BC,
1745 after: NonZeroDuration,
1746 neighbor: SpecifiedAddr<I::Addr>,
1747 event: NudEvent,
1748 ) {
1749 let Self { neighbor: heap, gc: _ } = self;
1750 assert_eq!(heap.schedule_after(bindings_ctx, neighbor, event, after.get()), None);
1751 }
1752
1753 fn schedule_neighbor_at(
1754 &mut self,
1755 bindings_ctx: &mut BC,
1756 at: BC::Instant,
1757 neighbor: SpecifiedAddr<I::Addr>,
1758 event: NudEvent,
1759 ) {
1760 let Self { neighbor: heap, gc: _ } = self;
1761 assert_eq!(heap.schedule_instant(bindings_ctx, neighbor, event, at), None);
1762 }
1763
1764 fn cancel_neighbor(
1766 &mut self,
1767 bindings_ctx: &mut BC,
1768 neighbor: SpecifiedAddr<I::Addr>,
1769 ) -> Option<NudEvent> {
1770 let Self { neighbor: heap, gc: _ } = self;
1771 heap.cancel(bindings_ctx, &neighbor).map(|(_instant, v)| v)
1772 }
1773
1774 fn pop_neighbor(
1775 &mut self,
1776 bindings_ctx: &mut BC,
1777 ) -> Option<(SpecifiedAddr<I::Addr>, NudEvent)> {
1778 let Self { neighbor: heap, gc: _ } = self;
1779 heap.pop(bindings_ctx)
1780 }
1781
1782 fn maybe_schedule_gc(
1785 &mut self,
1786 bindings_ctx: &mut BC,
1787 num_entries: usize,
1788 gc_state: &mut GarbageCollectionState<BC::Instant>,
1789 ) {
1790 let GarbageCollectionState { is_dirty, last_gc } = gc_state;
1791 *is_dirty = true;
1792 let Self { gc, neighbor: _ } = self;
1793 if num_entries > GC_THRESHOLD && bindings_ctx.scheduled_instant(gc).is_none() {
1794 let instant = if let Some(last_gc) = last_gc {
1795 last_gc.panicking_add(MIN_GARBAGE_COLLECTION_INTERVAL.get())
1796 } else {
1797 bindings_ctx.now()
1798 };
1799 assert_eq!(bindings_ctx.schedule_timer_instant(instant, gc), None);
1803 }
1804 }
1805
1806 fn cancel_gc(&mut self, bindings_ctx: &mut BC) {
1807 let Self { gc, neighbor: _ } = self;
1808 let _: Option<BC::Instant> = bindings_ctx.cancel_timer(gc);
1809 }
1810}
1811
1812#[derive(Debug)]
1814pub struct GarbageCollectionState<Instant> {
1815 last_gc: Option<Instant>,
1817 is_dirty: bool,
1820}
1821
1822#[derive(Debug)]
1824pub struct NudState<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> {
1825 neighbors: HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>>,
1827 gc_state: GarbageCollectionState<BT::Instant>,
1828 timer_heap: TimerHeap<I, BT>,
1829}
1830
1831impl<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> NudState<I, D, BT> {
1832 #[cfg(any(test, feature = "testutils"))]
1834 pub fn neighbors(&self) -> &HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>> {
1835 &self.neighbors
1836 }
1837}
1838
1839impl<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D> + TimerContext> NudState<I, D, BC> {
1840 pub fn new<
1842 DeviceId: WeakDeviceIdentifier,
1843 CC: CoreTimerContext<NudTimerId<I, D, DeviceId>, BC>,
1844 >(
1845 bindings_ctx: &mut BC,
1846 device_id: DeviceId,
1847 ) -> Self {
1848 Self {
1849 neighbors: Default::default(),
1850 gc_state: GarbageCollectionState { last_gc: None, is_dirty: false },
1851 timer_heap: TimerHeap::new::<_, _, CC>(bindings_ctx, device_id),
1852 }
1853 }
1854}
1855
1856pub trait NudBindingsContext<I: Ip, D: LinkDevice, DeviceId>:
1858 TimerContext
1859 + LinkResolutionContext<D>
1860 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1861 + NudBindingsTypes<D>
1862{
1863}
1864
1865impl<
1866 I: Ip,
1867 D: LinkDevice,
1868 DeviceId,
1869 BC: TimerContext
1870 + LinkResolutionContext<D>
1871 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1872 + NudBindingsTypes<D>,
1873> NudBindingsContext<I, D, DeviceId> for BC
1874{
1875}
1876
1877pub trait NudBindingsTypes<D: LinkDevice>:
1879 LinkResolutionContext<D> + InstantBindingsTypes + TimerBindingsTypes + TxMetadataBindingsTypes
1880{
1881}
1882
1883impl<BT, D> NudBindingsTypes<D> for BT
1884where
1885 D: LinkDevice,
1886 BT: LinkResolutionContext<D>
1887 + InstantBindingsTypes
1888 + TimerBindingsTypes
1889 + TxMetadataBindingsTypes,
1890{
1891}
1892
1893pub trait LinkResolutionContext<D: LinkDevice> {
1895 type Notifier: LinkResolutionNotifier<D>;
1898}
1899
1900pub trait LinkResolutionNotifier<D: LinkDevice>: Debug + Sized + Send {
1903 type Observer;
1906
1907 fn new() -> (Self, Self::Observer);
1909
1910 fn notify(self, result: Result<UnicastAddr<D::Address>, AddressResolutionFailed>);
1913}
1914
1915pub trait NudContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
1917 type ConfigCtx<'a>: NudConfigContext<I>;
1919 type SenderCtx<'a>: NudSenderContext<I, D, BC, DeviceId = Self::DeviceId>;
1921
1922 fn with_nud_state_mut_and_sender_ctx<
1925 O,
1926 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1927 >(
1928 &mut self,
1929 device_id: &Self::DeviceId,
1930 cb: F,
1931 ) -> O;
1932
1933 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1936 &mut self,
1937 device_id: &Self::DeviceId,
1938 cb: F,
1939 ) -> O;
1940
1941 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1943 &mut self,
1944 device_id: &Self::DeviceId,
1945 cb: F,
1946 ) -> O;
1947
1948 fn send_neighbor_solicitation(
1953 &mut self,
1954 bindings_ctx: &mut BC,
1955 device_id: &Self::DeviceId,
1956 lookup_addr: SpecifiedAddr<I::Addr>,
1957 remote_link_addr: Option<UnicastAddr<D::Address>>,
1958 );
1959}
1960
1961pub trait UseDelegateNudContext {}
1964
1965pub trait DelegateNudContext<I: Ip>: UseDelegateNudContext + Sized {
1971 type Delegate<T>: ref_cast::RefCast<From = T>;
1973 fn wrap(&mut self) -> &mut Self::Delegate<Self> {
1975 <Self::Delegate<Self> as ref_cast::RefCast>::ref_cast_mut(self)
1976 }
1977}
1978
1979impl<I, D, BC, CC> NudContext<I, D, BC> for CC
1980where
1981 I: Ip,
1982 D: LinkDevice,
1983 BC: NudBindingsTypes<D>,
1984 CC: DelegateNudContext<I, Delegate<CC>: NudContext<I, D, BC, DeviceId = CC::DeviceId>>
1985 + UseDelegateNudContext
1988 + DeviceIdContext<D>,
1989{
1990 type ConfigCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::ConfigCtx<'a>;
1991 type SenderCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::SenderCtx<'a>;
1992 fn with_nud_state_mut_and_sender_ctx<
1993 O,
1994 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1995 >(
1996 &mut self,
1997 device_id: &Self::DeviceId,
1998 cb: F,
1999 ) -> O {
2000 self.wrap().with_nud_state_mut_and_sender_ctx(device_id, cb)
2001 }
2002
2003 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
2004 &mut self,
2005 device_id: &Self::DeviceId,
2006 cb: F,
2007 ) -> O {
2008 self.wrap().with_nud_state_mut(device_id, cb)
2009 }
2010 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
2011 &mut self,
2012 device_id: &Self::DeviceId,
2013 cb: F,
2014 ) -> O {
2015 self.wrap().with_nud_state(device_id, cb)
2016 }
2017 fn send_neighbor_solicitation(
2018 &mut self,
2019 bindings_ctx: &mut BC,
2020 device_id: &Self::DeviceId,
2021 lookup_addr: SpecifiedAddr<I::Addr>,
2022 remote_link_addr: Option<UnicastAddr<D::Address>>,
2023 ) {
2024 self.wrap().send_neighbor_solicitation(
2025 bindings_ctx,
2026 device_id,
2027 lookup_addr,
2028 remote_link_addr,
2029 )
2030 }
2031}
2032
2033pub trait NudIcmpIpExt: packet_formats::ip::IpExt {
2035 type Metadata;
2038
2039 fn extract_metadata<B: SplitByteSlice>(packet: &Self::Packet<B>) -> Self::Metadata;
2041}
2042
2043impl NudIcmpIpExt for Ipv4 {
2044 type Metadata = Ipv4FragmentType;
2045
2046 fn extract_metadata<B: SplitByteSlice>(packet: &Ipv4Packet<B>) -> Self::Metadata {
2047 packet.fragment_type()
2048 }
2049}
2050
2051impl NudIcmpIpExt for Ipv6 {
2052 type Metadata = ();
2053
2054 fn extract_metadata<B: SplitByteSlice>(_packet: &Ipv6Packet<B>) -> Self::Metadata {
2055 ()
2056 }
2057}
2058
2059pub trait NudIcmpContext<I: NudIcmpIpExt, D: LinkDevice, BC>: DeviceIdContext<D> {
2062 fn send_icmp_dest_unreachable(
2069 &mut self,
2070 bindings_ctx: &mut BC,
2071 frame: Buf<Vec<u8>>,
2072 device_id: Option<&Self::DeviceId>,
2073 original_src_ip: SocketIpAddr<I::Addr>,
2074 original_dst_ip: SocketIpAddr<I::Addr>,
2075 header_len: usize,
2076 proto: I::Proto,
2077 metadata: I::Metadata,
2078 );
2079}
2080
2081#[derive(Clone, Debug)]
2083pub struct NudUserConfig {
2084 pub max_unicast_solicitations: NonZeroU16,
2089 pub max_multicast_solicitations: NonZeroU16,
2094 pub base_reachable_time: NonZeroDuration,
2100 pub retrans_timer: NonZeroDuration,
2106}
2107
2108impl Default for NudUserConfig {
2109 fn default() -> Self {
2110 NudUserConfig {
2111 max_unicast_solicitations: DEFAULT_MAX_UNICAST_SOLICIT,
2112 max_multicast_solicitations: DEFAULT_MAX_MULTICAST_SOLICIT,
2113 base_reachable_time: DEFAULT_BASE_REACHABLE_TIME,
2114 retrans_timer: DEFAULT_RETRANS_TIMER,
2115 }
2116 }
2117}
2118
2119#[allow(missing_docs)]
2123#[derive(Clone, Debug, Eq, PartialEq, Default)]
2124pub struct NudUserConfigUpdate {
2125 pub max_unicast_solicitations: Option<NonZeroU16>,
2126 pub max_multicast_solicitations: Option<NonZeroU16>,
2127 pub base_reachable_time: Option<NonZeroDuration>,
2128 pub retrans_timer: Option<NonZeroDuration>,
2129}
2130
2131impl NudUserConfigUpdate {
2132 pub fn apply_and_take_previous(mut self, config: &mut NudUserConfig) -> Self {
2135 fn swap_if_set<T>(opt: &mut Option<T>, target: &mut T) {
2136 if let Some(opt) = opt.as_mut() {
2137 core::mem::swap(opt, target)
2138 }
2139 }
2140 let Self {
2141 max_unicast_solicitations,
2142 max_multicast_solicitations,
2143 base_reachable_time,
2144 retrans_timer,
2145 } = &mut self;
2146 swap_if_set(max_unicast_solicitations, &mut config.max_unicast_solicitations);
2147 swap_if_set(max_multicast_solicitations, &mut config.max_multicast_solicitations);
2148 swap_if_set(base_reachable_time, &mut config.base_reachable_time);
2149 swap_if_set(retrans_timer, &mut config.retrans_timer);
2150
2151 self
2152 }
2153}
2154
2155pub trait NudConfigContext<I: Ip> {
2158 fn retransmit_timeout(&mut self) -> NonZeroDuration;
2165
2166 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O;
2168
2169 fn max_unicast_solicit(&mut self) -> NonZeroU16 {
2171 self.with_nud_user_config(|NudUserConfig { max_unicast_solicitations, .. }| {
2172 *max_unicast_solicitations
2173 })
2174 }
2175
2176 fn max_multicast_solicit(&mut self) -> NonZeroU16 {
2178 self.with_nud_user_config(|NudUserConfig { max_multicast_solicitations, .. }| {
2179 *max_multicast_solicitations
2180 })
2181 }
2182
2183 fn base_reachable_time(&mut self) -> NonZeroDuration {
2186 self.with_nud_user_config(|NudUserConfig { base_reachable_time, .. }| *base_reachable_time)
2187 }
2188
2189 fn override_lock_time(&mut self) -> Duration;
2192}
2193
2194pub trait NudSenderContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>:
2197 NudConfigContext<I> + DeviceIdContext<D>
2198{
2199 fn send_ip_packet_to_neighbor_link_addr<S>(
2201 &mut self,
2202 bindings_ctx: &mut BC,
2203 neighbor_link_addr: UnicastAddr<D::Address>,
2204 body: S,
2205 meta: BC::TxMetadata,
2206 ) -> Result<(), SendFrameError<S>>
2207 where
2208 S: NetworkSerializer,
2209 S::Buffer: BufferMut;
2210}
2211
2212pub trait NudIpHandler<I: Ip, BC>: DeviceIdContext<AnyDevice> {
2214 fn handle_neighbor_probe(
2219 &mut self,
2220 bindings_ctx: &mut BC,
2221 device_id: &Self::DeviceId,
2222 neighbor: SpecifiedAddr<I::Addr>,
2223 link_addr: &[u8],
2224 );
2225
2226 fn handle_neighbor_confirmation(
2230 &mut self,
2231 bindings_ctx: &mut BC,
2232 device_id: &Self::DeviceId,
2233 neighbor: SpecifiedAddr<I::Addr>,
2234 link_addr: Option<&[u8]>,
2235 flags: ConfirmationFlags,
2236 );
2237
2238 fn flush_neighbor_table(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2240}
2241
2242#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2244pub enum LinkResolutionResult<A, Observer> {
2245 Resolved(A),
2247 Pending(Observer),
2249}
2250
2251pub trait NudHandler<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
2253 fn handle_neighbor_update(
2256 &mut self,
2257 bindings_ctx: &mut BC,
2258 device_id: &Self::DeviceId,
2259 neighbor: SpecifiedAddr<I::Addr>,
2264 source: DynamicNeighborUpdateSource<D::Address>,
2265 );
2266
2267 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2269
2270 fn send_ip_packet_to_neighbor<S>(
2275 &mut self,
2276 bindings_ctx: &mut BC,
2277 device_id: &Self::DeviceId,
2278 neighbor: SpecifiedAddr<I::Addr>,
2279 body: S,
2280 meta: BC::TxMetadata,
2281 ) -> Result<(), SendFrameError<S>>
2282 where
2283 S: NetworkSerializer,
2284 S::Buffer: BufferMut;
2285}
2286
2287enum TransmitProbe<A> {
2288 Multicast,
2289 Unicast(A),
2290}
2291
2292impl<
2293 I: NudIcmpIpExt,
2294 D: LinkDevice,
2295 BC: NudBindingsContext<I, D, CC::DeviceId>,
2296 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2297> HandleableTimer<CC, BC> for NudTimerId<I, D, CC::WeakDeviceId>
2298{
2299 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
2300 let Self { device_id, timer_type, _marker: PhantomData } = self;
2301 let Some(device_id) = device_id.upgrade() else {
2302 return;
2303 };
2304 match timer_type {
2305 NudTimerType::Neighbor => handle_neighbor_timer(core_ctx, bindings_ctx, device_id),
2306 NudTimerType::GarbageCollection => collect_garbage(core_ctx, bindings_ctx, device_id),
2307 }
2308 }
2309}
2310
2311fn handle_neighbor_timer<I, D, CC, BC>(
2312 core_ctx: &mut CC,
2313 bindings_ctx: &mut BC,
2314 device_id: CC::DeviceId,
2315) where
2316 I: NudIcmpIpExt,
2317 D: LinkDevice,
2318 BC: NudBindingsContext<I, D, CC::DeviceId>,
2319 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2320{
2321 enum Action<L, A, M> {
2322 TransmitProbe { probe: TransmitProbe<L>, to: A },
2323 SendIcmpDestUnreachable(VecDeque<(Buf<Vec<u8>>, M)>),
2324 }
2325 let action = core_ctx.with_nud_state_mut(
2326 &device_id,
2327 |NudState { neighbors, gc_state, timer_heap }, core_ctx| {
2328 let (lookup_addr, event) = timer_heap.pop_neighbor(bindings_ctx)?;
2329 let num_entries = neighbors.len();
2330 let mut entry = match neighbors.entry(lookup_addr) {
2331 Entry::Occupied(entry) => entry,
2332 Entry::Vacant(_) => panic!("timer fired for invalid entry"),
2333 };
2334
2335 match entry.get_mut() {
2336 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)) => {
2337 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2338
2339 if incomplete.schedule_timer_if_should_retransmit(
2340 core_ctx,
2341 bindings_ctx,
2342 timer_heap,
2343 lookup_addr,
2344 ) {
2345 Some(Action::TransmitProbe {
2346 probe: TransmitProbe::Multicast,
2347 to: lookup_addr,
2348 })
2349 } else {
2350 debug!("neighbor resolution failed for {lookup_addr}; removing entry");
2358 let Incomplete {
2359 transmit_counter: _,
2360 ref mut pending_frames,
2361 notifiers: _,
2362 _marker,
2363 } = assert_matches!(
2364 entry.remove(),
2365 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete))
2366 => incomplete
2367 );
2368 let pending_frames = core::mem::take(pending_frames);
2369 bindings_ctx.on_event(Event::removed(
2370 &device_id,
2371 lookup_addr,
2372 bindings_ctx.now(),
2373 ));
2374 Some(Action::SendIcmpDestUnreachable(pending_frames))
2375 }
2376 }
2377 NeighborState::Dynamic(DynamicNeighborState::Probe(probe)) => {
2378 assert_eq!(event, NudEvent::RetransmitUnicastProbe);
2379
2380 let Probe { link_address, transmit_counter: _ } = probe;
2381 let link_address = *link_address;
2382 if probe.schedule_timer_if_should_retransmit(
2383 core_ctx,
2384 bindings_ctx,
2385 timer_heap,
2386 lookup_addr,
2387 ) {
2388 Some(Action::TransmitProbe {
2389 probe: TransmitProbe::Unicast(link_address),
2390 to: lookup_addr,
2391 })
2392 } else {
2393 let unreachable = probe.enter_unreachable(
2394 bindings_ctx,
2395 timer_heap,
2396 num_entries,
2397 gc_state,
2398 );
2399 *entry.get_mut() =
2400 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable));
2401 let event_state = entry.get_mut().to_event_state();
2402 let event = Event::changed(
2403 &device_id,
2404 event_state,
2405 lookup_addr,
2406 bindings_ctx.now(),
2407 );
2408 bindings_ctx.on_event(event);
2409 None
2410 }
2411 }
2412 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable)) => {
2413 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2414 unreachable
2415 .handle_timer(core_ctx, bindings_ctx, timer_heap, &device_id, lookup_addr)
2416 .map(|probe| Action::TransmitProbe { probe, to: lookup_addr })
2417 }
2418 NeighborState::Dynamic(DynamicNeighborState::Reachable(Reachable {
2419 link_address,
2420 last_confirmed_at,
2421 })) => {
2422 assert_eq!(event, NudEvent::ReachableTime);
2423 let link_address = *link_address;
2424
2425 let expiration =
2426 last_confirmed_at.saturating_add(core_ctx.base_reachable_time().get());
2427 if expiration > bindings_ctx.now() {
2428 timer_heap.schedule_neighbor_at(
2429 bindings_ctx,
2430 expiration,
2431 lookup_addr,
2432 NudEvent::ReachableTime,
2433 );
2434 } else {
2435 *entry.get_mut() =
2443 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2444 link_address,
2445 }));
2446 let event_state = entry.get_mut().to_event_state();
2447 let event = Event::changed(
2448 &device_id,
2449 event_state,
2450 lookup_addr,
2451 bindings_ctx.now(),
2452 );
2453 bindings_ctx.on_event(event);
2454
2455 timer_heap.maybe_schedule_gc(bindings_ctx, num_entries, gc_state);
2459 }
2460
2461 None
2462 }
2463 NeighborState::Dynamic(DynamicNeighborState::Delay(delay)) => {
2464 assert_eq!(event, NudEvent::DelayFirstProbe);
2465
2466 let probe @ Probe { link_address, transmit_counter: _ } =
2473 delay.enter_probe(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2474 *entry.get_mut() = NeighborState::Dynamic(DynamicNeighborState::Probe(probe));
2475 let event_state = entry.get_mut().to_event_state();
2476 bindings_ctx.on_event(Event::changed(
2477 &device_id,
2478 event_state,
2479 lookup_addr,
2480 bindings_ctx.now(),
2481 ));
2482
2483 Some(Action::TransmitProbe {
2484 probe: TransmitProbe::Unicast(link_address),
2485 to: lookup_addr,
2486 })
2487 }
2488 state @ (NeighborState::Static(_)
2489 | NeighborState::Dynamic(DynamicNeighborState::Stale(_))) => {
2490 panic!("timer unexpectedly fired in state {state:?}")
2491 }
2492 }
2493 },
2494 );
2495
2496 match action {
2497 Some(Action::SendIcmpDestUnreachable(mut pending_frames)) => {
2498 for (mut frame, meta) in pending_frames.drain(..) {
2499 core::mem::drop(meta);
2502
2503 let Some((packet, original_src_ip, original_dst_ip)) = frame
2507 .parse_mut::<I::Packet<_>>()
2508 .map_err(|e| {
2509 warn!("not sending ICMP dest unreachable due to parsing error: {:?}", e);
2510 })
2511 .ok()
2512 .and_then(|packet| {
2513 let original_src_ip = SocketIpAddr::new(packet.src_ip())?;
2514 let original_dst_ip = SocketIpAddr::new(packet.dst_ip())?;
2515 Some((packet, original_src_ip, original_dst_ip))
2516 })
2517 .or_else(|| {
2518 core_ctx.counters().icmp_dest_unreachable_dropped.increment();
2519 None
2520 })
2521 else {
2522 continue;
2523 };
2524 let header_metadata = I::extract_metadata(&packet);
2525 let header_len = packet.parse_metadata().header_len();
2526 let proto = packet.proto();
2527 let metadata = packet.parse_metadata();
2528 core::mem::drop(packet);
2529 frame.undo_parse(metadata);
2530 core_ctx.send_icmp_dest_unreachable(
2531 bindings_ctx,
2532 frame,
2533 original_src_ip.as_ref().must_have_zone().then_some(&device_id),
2545 original_src_ip,
2546 original_dst_ip,
2547 header_len,
2548 proto,
2549 header_metadata,
2550 );
2551 }
2552 }
2553 Some(Action::TransmitProbe { probe, to }) => {
2554 let remote_link_addr = match probe {
2555 TransmitProbe::Multicast => None,
2556 TransmitProbe::Unicast(link_addr) => Some(link_addr),
2557 };
2558 core_ctx.send_neighbor_solicitation(bindings_ctx, &device_id, to, remote_link_addr);
2559 }
2560 None => {}
2561 }
2562}
2563
2564impl<I: Ip, D: LinkDevice, BC: NudBindingsContext<I, D, CC::DeviceId>, CC: NudContext<I, D, BC>>
2565 NudHandler<I, D, BC> for CC
2566{
2567 fn handle_neighbor_update(
2568 &mut self,
2569 bindings_ctx: &mut BC,
2570 device_id: &CC::DeviceId,
2571 neighbor: SpecifiedAddr<I::Addr>,
2572 source: DynamicNeighborUpdateSource<D::Address>,
2573 ) {
2574 debug!("received neighbor {:?} from {}", source, neighbor);
2575 self.with_nud_state_mut_and_sender_ctx(
2576 device_id,
2577 |NudState { neighbors, gc_state, timer_heap }, core_ctx| {
2578 let num_entries = neighbors.len();
2579 match neighbors.get_mut(&neighbor) {
2580 None => match source {
2581 DynamicNeighborUpdateSource::Probe { link_address } => {
2582 let result = insert_new_entry(
2590 neighbors,
2591 gc_state,
2592 timer_heap,
2593 bindings_ctx,
2594 device_id,
2595 neighbor,
2596 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2597 link_address,
2598 })),
2599 );
2600 match result {
2601 Ok(_entry) => {}
2602 Err(TableFullError { entry }) => {
2603 debug!("Neighbor table full; failed to insert {entry:?}");
2604 return;
2605 }
2606 }
2607
2608 timer_heap.maybe_schedule_gc(bindings_ctx, neighbors.len(), gc_state);
2611 }
2612 DynamicNeighborUpdateSource::Confirmation { .. } => {}
2621 },
2622 Some(entry) => match entry {
2623 NeighborState::Dynamic(e) => match source {
2624 DynamicNeighborUpdateSource::Probe { link_address } => e.handle_probe(
2625 core_ctx,
2626 bindings_ctx,
2627 timer_heap,
2628 device_id,
2629 neighbor,
2630 link_address,
2631 num_entries,
2632 gc_state,
2633 ),
2634 DynamicNeighborUpdateSource::Confirmation { link_address, flags } => e
2635 .handle_confirmation(
2636 core_ctx,
2637 bindings_ctx,
2638 timer_heap,
2639 device_id,
2640 neighbor,
2641 link_address,
2642 flags,
2643 num_entries,
2644 gc_state,
2645 ),
2646 },
2647 NeighborState::Static(_) => {}
2648 },
2649 }
2650 },
2651 );
2652 }
2653
2654 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
2655 self.with_nud_state_mut(
2656 device_id,
2657 |NudState { neighbors, gc_state: _, timer_heap }, _config| {
2658 neighbors.drain().for_each(|(neighbor, state)| {
2659 match state {
2660 NeighborState::Dynamic(mut entry) => {
2661 entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
2662 }
2663 NeighborState::Static(_) => {}
2664 }
2665 bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
2666 });
2667 },
2668 );
2669 }
2670
2671 fn send_ip_packet_to_neighbor<S>(
2672 &mut self,
2673 bindings_ctx: &mut BC,
2674 device_id: &Self::DeviceId,
2675 lookup_addr: SpecifiedAddr<I::Addr>,
2676 body: S,
2677 meta: BC::TxMetadata,
2678 ) -> Result<(), SendFrameError<S>>
2679 where
2680 S: NetworkSerializer,
2681 S::Buffer: BufferMut,
2682 {
2683 let do_multicast_solicit = self.with_nud_state_mut_and_sender_ctx(
2684 device_id,
2685 |NudState { neighbors, gc_state, timer_heap },
2686 core_ctx|
2687 -> Result<_, SendFrameError<S>> {
2688 match neighbors.get_mut(&lookup_addr) {
2689 None => {
2690 let incomplete =
2691 Incomplete::new(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2692 let result = insert_new_entry(
2693 neighbors,
2694 gc_state,
2695 timer_heap,
2696 bindings_ctx,
2697 device_id,
2698 lookup_addr,
2699 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)),
2700 );
2701 match result {
2702 Err(TableFullError { entry }) => {
2703 debug!("Neighbor table full; failed to insert {entry:?}");
2704 return Err(ErrorAndSerializer {
2705 serializer: body,
2706 error: SendFrameErrorReason::AddressResolutionFailed,
2707 });
2708 }
2709 Ok(mut entry) => {
2710 let dynamic = assert_matches!(
2711 entry.get_mut(),
2712 NeighborState::Dynamic(d) => d,
2713 "newly inserted entry must still be dynamic"
2714 );
2715 let incomplete = assert_matches!(
2716 dynamic,
2717 DynamicNeighborState::Incomplete(i) => i,
2718 "newly inserted entry must still be incomplete"
2719 );
2720 match incomplete.queue_packet(body, meta) {
2722 Ok(()) => Ok(true),
2723 Err(e) => {
2724 dynamic.cancel_timer(bindings_ctx, timer_heap, lookup_addr);
2725 let _entry = entry.remove();
2726 Err(e.err_into())
2727 }
2728 }
2729 }
2730 }
2731 }
2732 Some(entry) => {
2733 match entry {
2734 NeighborState::Static(link_address) => {
2735 core_ctx.send_ip_packet_to_neighbor_link_addr(
2743 bindings_ctx,
2744 *link_address,
2745 body,
2746 meta,
2747 )?;
2748
2749 Ok(false)
2750 }
2751 NeighborState::Dynamic(e) => {
2752 let do_multicast_solicit = e.handle_packet_queued_to_send(
2753 core_ctx,
2754 bindings_ctx,
2755 timer_heap,
2756 device_id,
2757 lookup_addr,
2758 body,
2759 meta,
2760 )?;
2761
2762 Ok(do_multicast_solicit)
2763 }
2764 }
2765 }
2766 }
2767 },
2768 )?;
2769
2770 if do_multicast_solicit {
2771 self.send_neighbor_solicitation(
2772 bindings_ctx,
2773 &device_id,
2774 lookup_addr,
2775 None,
2776 );
2777 }
2778
2779 Ok(())
2780 }
2781}
2782
2783pub(crate) struct TableFullError<E> {
2784 entry: E,
2785}
2786
2787pub(crate) fn insert_new_entry<
2801 'a,
2802 I: Ip,
2803 D: LinkDevice,
2804 DeviceId: StrongDeviceIdentifier,
2805 BC: NudBindingsContext<I, D, DeviceId>,
2806>(
2807 neighbors: &'a mut HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2808 gc_state: &mut GarbageCollectionState<BC::Instant>,
2809 timer_heap: &mut TimerHeap<I, BC>,
2810 bindings_ctx: &mut BC,
2811 device_id: &DeviceId,
2812 ip: SpecifiedAddr<I::Addr>,
2813 entry: NeighborState<D, BC>,
2814) -> Result<
2815 OccupiedEntry<'a, SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2816 TableFullError<NeighborState<D, BC>>,
2817> {
2818 if neighbors.len() >= MAX_ENTRIES {
2819 timer_heap.cancel_gc(bindings_ctx);
2824 collect_garbage_inner(neighbors, gc_state, timer_heap, bindings_ctx, device_id);
2825 }
2826
2827 if neighbors.len() >= MAX_ENTRIES {
2828 return Err(TableFullError { entry });
2829 }
2830
2831 match neighbors.entry(ip) {
2832 Entry::Occupied(_) => panic!("neighbor entry unexpectedly existed"),
2833 Entry::Vacant(e) => {
2834 let event_state = entry.to_event_state();
2835 let entry = e.insert_entry(entry);
2836 let event = Event::added(device_id, event_state, ip, bindings_ctx.now());
2837 bindings_ctx.on_event(event);
2838 Ok(entry)
2839 }
2840 }
2841}
2842
2843pub fn confirm_reachable<I, D, CC, BC>(
2846 core_ctx: &mut CC,
2847 bindings_ctx: &mut BC,
2848 device_id: &CC::DeviceId,
2849 neighbor: SpecifiedAddr<I::Addr>,
2850) where
2851 I: Ip,
2852 D: LinkDevice,
2853 BC: NudBindingsContext<I, D, CC::DeviceId>,
2854 CC: NudContext<I, D, BC>,
2855{
2856 core_ctx.with_nud_state_mut_and_sender_ctx(
2857 device_id,
2858 |NudState { neighbors, timer_heap, .. }, core_ctx| {
2859 match neighbors.entry(neighbor) {
2860 Entry::Vacant(_) => {
2861 debug!(
2862 "got an upper-layer confirmation for non-existent neighbor entry {}",
2863 neighbor
2864 );
2865 }
2866 Entry::Occupied(e) => match e.into_mut() {
2867 NeighborState::Static(_) => {}
2868 NeighborState::Dynamic(e) => {
2869 let link_address = match e {
2879 DynamicNeighborState::Incomplete(_) => return,
2880 DynamicNeighborState::Reachable(Reachable {
2881 link_address,
2882 last_confirmed_at: _,
2883 })
2884 | DynamicNeighborState::Stale(Stale { link_address })
2885 | DynamicNeighborState::Delay(Delay { link_address })
2886 | DynamicNeighborState::Probe(Probe {
2887 link_address,
2888 transmit_counter: _,
2889 })
2890 | DynamicNeighborState::Unreachable(Unreachable {
2891 link_address,
2892 mode: _,
2893 }) => *link_address,
2894 };
2895 e.enter_reachable(
2896 core_ctx,
2897 bindings_ctx,
2898 timer_heap,
2899 device_id,
2900 neighbor,
2901 link_address,
2902 );
2903 }
2904 },
2905 }
2906 },
2907 );
2908}
2909
2910fn collect_garbage_inner<I, D, DeviceId, BC>(
2921 neighbors: &mut HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2922 gc_state: &mut GarbageCollectionState<BC::Instant>,
2923 timer_heap: &mut TimerHeap<I, BC>,
2924 bindings_ctx: &mut BC,
2925 device_id: &DeviceId,
2926) where
2927 I: Ip,
2928 D: LinkDevice,
2929 DeviceId: StrongDeviceIdentifier,
2930 BC: NudBindingsContext<I, D, DeviceId>,
2931{
2932 let GarbageCollectionState { last_gc, is_dirty } = gc_state;
2933 if !*is_dirty {
2935 return;
2936 }
2937
2938 let max_to_remove = neighbors.len().saturating_sub(GC_THRESHOLD);
2939 if max_to_remove == 0 {
2940 return;
2941 }
2942
2943 let mut is_still_dirty = false;
2944
2945 fn gc_priority<D: LinkDevice, BT: NudBindingsTypes<D>>(
2954 state: &DynamicNeighborState<D, BT>,
2955 ) -> usize {
2956 match state {
2957 DynamicNeighborState::Incomplete(_)
2958 | DynamicNeighborState::Reachable(_)
2959 | DynamicNeighborState::Delay(_)
2960 | DynamicNeighborState::Probe(_) => unreachable!(
2961 "the netstack should only ever discard STALE or UNREACHABLE entries; \
2962 found {:?}",
2963 state,
2964 ),
2965 DynamicNeighborState::Stale(_) => 0,
2966 DynamicNeighborState::Unreachable(Unreachable {
2967 link_address: _,
2968 mode: UnreachableMode::Backoff { probes_sent: _, packet_sent: _ },
2969 }) => 1,
2970 DynamicNeighborState::Unreachable(Unreachable {
2971 link_address: _,
2972 mode: UnreachableMode::WaitingForPacketSend,
2973 }) => 2,
2974 }
2975 }
2976
2977 struct SortEntry<'a, K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> {
2978 key: K,
2979 state: &'a mut DynamicNeighborState<D, BT>,
2980 }
2981
2982 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialEq for SortEntry<'_, K, D, BT> {
2983 fn eq(&self, other: &Self) -> bool {
2984 self.key == other.key && gc_priority(self.state) == gc_priority(other.state)
2985 }
2986 }
2987 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Eq for SortEntry<'_, K, D, BT> {}
2988 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Ord for SortEntry<'_, K, D, BT> {
2989 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2990 gc_priority(self.state).cmp(&gc_priority(other.state)).reverse()
2994 }
2995 }
2996 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialOrd for SortEntry<'_, K, D, BT> {
2997 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
2998 Some(self.cmp(&other))
2999 }
3000 }
3001
3002 let mut entries_to_remove = BinaryHeap::with_capacity(max_to_remove);
3003 for (ip, neighbor) in neighbors.iter_mut() {
3004 match neighbor {
3005 NeighborState::Static(_) => {
3006 continue;
3008 }
3009 NeighborState::Dynamic(state) => {
3010 match state {
3011 DynamicNeighborState::Incomplete(_)
3012 | DynamicNeighborState::Reachable(_)
3013 | DynamicNeighborState::Delay(_)
3014 | DynamicNeighborState::Probe(_) => {
3015 continue;
3017 }
3018 DynamicNeighborState::Stale(_) | DynamicNeighborState::Unreachable(_) => {
3019 if entries_to_remove.len() < max_to_remove {
3021 entries_to_remove.push(SortEntry { key: ip, state });
3022 continue;
3023 }
3024 is_still_dirty = true;
3029 let minimum =
3030 entries_to_remove.peek().expect("heap should have at least 1 entry");
3031 let candidate = SortEntry { key: ip, state };
3032 if &candidate > minimum {
3033 let _: SortEntry<'_, _, _, _> = entries_to_remove.pop().unwrap();
3034 entries_to_remove.push(candidate);
3035 }
3036 }
3037 }
3038 }
3039 }
3040 }
3041
3042 let entries_to_remove = entries_to_remove
3043 .into_iter()
3044 .map(|SortEntry { key: neighbor, state }| {
3045 state.cancel_timer(bindings_ctx, timer_heap, *neighbor);
3046 *neighbor
3047 })
3048 .collect::<Vec<_>>();
3049
3050 for neighbor in entries_to_remove {
3051 assert_matches!(neighbors.remove(&neighbor), Some(_));
3052 bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
3053 }
3054
3055 *last_gc = Some(bindings_ctx.now());
3056 *is_dirty = is_still_dirty;
3057}
3058
3059fn collect_garbage<I, D, CC, BC>(core_ctx: &mut CC, bindings_ctx: &mut BC, device_id: CC::DeviceId)
3060where
3061 I: Ip,
3062 D: LinkDevice,
3063 BC: NudBindingsContext<I, D, CC::DeviceId>,
3064 CC: NudContext<I, D, BC>,
3065{
3066 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, gc_state, timer_heap }, _| {
3067 collect_garbage_inner(neighbors, gc_state, timer_heap, bindings_ctx, &device_id);
3068 })
3069}
3070
3071#[cfg(test)]
3072mod tests {
3073 use alloc::vec;
3074
3075 use ip_test_macro::ip_test;
3076 use net_declare::{net_ip_v4, net_ip_v6};
3077 use net_types::UnicastAddr;
3078 use net_types::ip::{Ipv4Addr, Ipv6Addr};
3079 use netstack3_base::testutil::{
3080 FakeBindingsCtx, FakeCoreCtx, FakeInstant, FakeLinkAddress, FakeLinkDevice,
3081 FakeLinkDeviceId, FakeTimerCtxExt as _, FakeTxMetadata, FakeWeakDeviceId,
3082 };
3083 use netstack3_base::{
3084 CtxPair, InstantContext, IntoCoreTimerCtx, SendFrameContext as _, SendFrameErrorReason,
3085 };
3086 use netstack3_hashmap::HashSet;
3087 use test_case::test_case;
3088
3089 use super::*;
3090 use crate::internal::device::nud::api::{NeighborApi, StaticNeighborInsertionError};
3091 use packet::NestableSerializer as _;
3092
3093 struct FakeNudContext<I: Ip, D: LinkDevice> {
3094 state: NudState<I, D, FakeBindingsCtxImpl<I>>,
3095 counters: NudCounters<I>,
3096 }
3097
3098 struct FakeConfigContext {
3099 retrans_timer: NonZeroDuration,
3100 nud_config: NudUserConfig,
3101 }
3102
3103 struct FakeCoreCtxImpl<I: Ip> {
3104 nud: FakeNudContext<I, FakeLinkDevice>,
3105 inner: FakeInnerCtxImpl<I>,
3106 }
3107
3108 type FakeInnerCtxImpl<I> =
3109 FakeCoreCtx<FakeConfigContext, FakeNudMessageMeta<I>, FakeLinkDeviceId>;
3110
3111 #[derive(Debug, PartialEq, Eq)]
3112 enum FakeNudMessageMeta<I: Ip> {
3113 NeighborSolicitation {
3114 lookup_addr: SpecifiedAddr<I::Addr>,
3115 remote_link_addr: Option<UnicastAddr<FakeLinkAddress>>,
3116 },
3117 IpFrame {
3118 dst_link_address: UnicastAddr<FakeLinkAddress>,
3119 },
3120 IcmpDestUnreachable,
3121 }
3122
3123 type FakeBindingsCtxImpl<I> = FakeBindingsCtx<
3124 NudTimerId<I, FakeLinkDevice, FakeWeakDeviceId<FakeLinkDeviceId>>,
3125 Event<FakeLinkAddress, FakeLinkDeviceId, I, FakeInstant>,
3126 (),
3127 (),
3128 >;
3129
3130 impl<I: Ip> FakeCoreCtxImpl<I> {
3131 fn new(bindings_ctx: &mut FakeBindingsCtxImpl<I>) -> Self {
3132 Self {
3133 nud: {
3134 FakeNudContext {
3135 state: NudState::new::<_, IntoCoreTimerCtx>(
3136 bindings_ctx,
3137 FakeWeakDeviceId(FakeLinkDeviceId),
3138 ),
3139 counters: Default::default(),
3140 }
3141 },
3142 inner: FakeInnerCtxImpl::with_state(FakeConfigContext {
3143 retrans_timer: ONE_SECOND,
3144 nud_config: NudUserConfig {
3148 max_unicast_solicitations: NonZeroU16::new(4).unwrap(),
3149 max_multicast_solicitations: NonZeroU16::new(5).unwrap(),
3150 base_reachable_time: NonZeroDuration::from_secs(23).unwrap(),
3151 retrans_timer: NonZeroDuration::from_secs(3).unwrap(),
3152 },
3153 }),
3154 }
3155 }
3156 }
3157
3158 fn new_context<I: Ip>() -> CtxPair<FakeCoreCtxImpl<I>, FakeBindingsCtxImpl<I>> {
3159 CtxPair::with_default_bindings_ctx(|bindings_ctx| FakeCoreCtxImpl::<I>::new(bindings_ctx))
3160 }
3161
3162 impl<I: Ip> DeviceIdContext<FakeLinkDevice> for FakeCoreCtxImpl<I> {
3163 type DeviceId = FakeLinkDeviceId;
3164 type WeakDeviceId = FakeWeakDeviceId<FakeLinkDeviceId>;
3165 }
3166
3167 impl<I: Ip> NudContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeCoreCtxImpl<I> {
3168 type ConfigCtx<'a> = FakeConfigContext;
3169
3170 type SenderCtx<'a> = FakeInnerCtxImpl<I>;
3171
3172 fn with_nud_state_mut_and_sender_ctx<
3173 O,
3174 F: FnOnce(
3175 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3176 &mut Self::SenderCtx<'_>,
3177 ) -> O,
3178 >(
3179 &mut self,
3180 _device_id: &Self::DeviceId,
3181 cb: F,
3182 ) -> O {
3183 cb(&mut self.nud.state, &mut self.inner)
3184 }
3185
3186 fn with_nud_state_mut<
3187 O,
3188 F: FnOnce(
3189 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3190 &mut Self::ConfigCtx<'_>,
3191 ) -> O,
3192 >(
3193 &mut self,
3194 &FakeLinkDeviceId: &FakeLinkDeviceId,
3195 cb: F,
3196 ) -> O {
3197 cb(&mut self.nud.state, &mut self.inner.state)
3198 }
3199
3200 fn with_nud_state<
3201 O,
3202 F: FnOnce(&NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>) -> O,
3203 >(
3204 &mut self,
3205 &FakeLinkDeviceId: &FakeLinkDeviceId,
3206 cb: F,
3207 ) -> O {
3208 cb(&self.nud.state)
3209 }
3210
3211 fn send_neighbor_solicitation(
3212 &mut self,
3213 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3214 &FakeLinkDeviceId: &FakeLinkDeviceId,
3215 lookup_addr: SpecifiedAddr<I::Addr>,
3216 remote_link_addr: Option<UnicastAddr<FakeLinkAddress>>,
3217 ) {
3218 self.inner
3219 .send_frame(
3220 bindings_ctx,
3221 FakeNudMessageMeta::NeighborSolicitation { lookup_addr, remote_link_addr },
3222 Buf::new(Vec::new(), ..),
3223 )
3224 .unwrap()
3225 }
3226 }
3227
3228 impl<I: NudIcmpIpExt> NudIcmpContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>
3229 for FakeCoreCtxImpl<I>
3230 {
3231 fn send_icmp_dest_unreachable(
3232 &mut self,
3233 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3234 frame: Buf<Vec<u8>>,
3235 _device_id: Option<&Self::DeviceId>,
3236 _original_src_ip: SocketIpAddr<I::Addr>,
3237 _original_dst_ip: SocketIpAddr<I::Addr>,
3238 _header_len: usize,
3239 _proto: I::Proto,
3240 _metadata: I::Metadata,
3241 ) {
3242 self.inner
3243 .send_frame(bindings_ctx, FakeNudMessageMeta::IcmpDestUnreachable, frame)
3244 .unwrap()
3245 }
3246 }
3247
3248 impl<I: Ip> CounterContext<NudCounters<I>> for FakeCoreCtxImpl<I> {
3249 fn counters(&self) -> &NudCounters<I> {
3250 &self.nud.counters
3251 }
3252 }
3253
3254 impl<I: Ip> NudConfigContext<I> for FakeConfigContext {
3255 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3256 self.retrans_timer
3257 }
3258
3259 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3260 cb(&self.nud_config)
3261 }
3262
3263 fn override_lock_time(&mut self) -> Duration {
3264 Duration::ZERO
3265 }
3266 }
3267
3268 impl<I: Ip> NudSenderContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeInnerCtxImpl<I> {
3269 fn send_ip_packet_to_neighbor_link_addr<S>(
3270 &mut self,
3271 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3272 dst_link_address: UnicastAddr<FakeLinkAddress>,
3273 body: S,
3274 _tx_meta: FakeTxMetadata,
3275 ) -> Result<(), SendFrameError<S>>
3276 where
3277 S: NetworkSerializer,
3278 S::Buffer: BufferMut,
3279 {
3280 self.send_frame(bindings_ctx, FakeNudMessageMeta::IpFrame { dst_link_address }, body)
3281 }
3282 }
3283
3284 impl<I: Ip> NudConfigContext<I> for FakeInnerCtxImpl<I> {
3285 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3286 <FakeConfigContext as NudConfigContext<I>>::retransmit_timeout(&mut self.state)
3287 }
3288
3289 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3290 <FakeConfigContext as NudConfigContext<I>>::with_nud_user_config(&mut self.state, cb)
3291 }
3292
3293 fn override_lock_time(&mut self) -> Duration {
3294 <FakeConfigContext as NudConfigContext<I>>::override_lock_time(&mut self.state)
3295 }
3296 }
3297
3298 const ONE_SECOND: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
3299
3300 #[track_caller]
3301 fn check_lookup_has<I: Ip>(
3302 core_ctx: &mut FakeCoreCtxImpl<I>,
3303 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3304 lookup_addr: SpecifiedAddr<I::Addr>,
3305 expected_link_addr: UnicastAddr<FakeLinkAddress>,
3306 ) {
3307 let entry = assert_matches!(
3308 core_ctx.nud.state.neighbors.get(&lookup_addr),
3309 Some(entry @ (
3310 NeighborState::Dynamic(
3311 DynamicNeighborState::Reachable (Reachable { link_address, last_confirmed_at: _ })
3312 | DynamicNeighborState::Stale (Stale { link_address })
3313 | DynamicNeighborState::Delay (Delay { link_address })
3314 | DynamicNeighborState::Probe (Probe { link_address, transmit_counter: _ })
3315 | DynamicNeighborState::Unreachable (Unreachable { link_address, mode: _ })
3316 )
3317 | NeighborState::Static(link_address)
3318 )) => {
3319 assert_eq!(link_address, &expected_link_addr);
3320 entry
3321 }
3322 );
3323 match entry {
3324 NeighborState::Dynamic(DynamicNeighborState::Incomplete { .. }) => {
3325 unreachable!("entry must be static, REACHABLE, or STALE")
3326 }
3327 NeighborState::Dynamic(DynamicNeighborState::Reachable { .. }) => {
3328 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3329 bindings_ctx,
3330 [(
3331 lookup_addr,
3332 NudEvent::ReachableTime,
3333 core_ctx.inner.base_reachable_time().get(),
3334 )],
3335 );
3336 }
3337 NeighborState::Dynamic(DynamicNeighborState::Delay { .. }) => {
3338 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3339 bindings_ctx,
3340 [(lookup_addr, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
3341 );
3342 }
3343 NeighborState::Dynamic(DynamicNeighborState::Probe { .. }) => {
3344 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3345 bindings_ctx,
3346 [(
3347 lookup_addr,
3348 NudEvent::RetransmitUnicastProbe,
3349 core_ctx.inner.state.retrans_timer.get(),
3350 )],
3351 );
3352 }
3353 NeighborState::Dynamic(DynamicNeighborState::Unreachable(Unreachable {
3354 link_address: _,
3355 mode,
3356 })) => {
3357 let instant = match mode {
3358 UnreachableMode::WaitingForPacketSend => None,
3359 mode @ UnreachableMode::Backoff { .. } => {
3360 let duration =
3361 mode.next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state);
3362 Some(bindings_ctx.now() + duration.get())
3363 }
3364 };
3365 if let Some(instant) = instant {
3366 core_ctx.nud.state.timer_heap.neighbor.assert_timers([(
3367 lookup_addr,
3368 NudEvent::RetransmitUnicastProbe,
3369 instant,
3370 )]);
3371 }
3372 }
3373 NeighborState::Dynamic(DynamicNeighborState::Stale { .. })
3374 | NeighborState::Static(_) => bindings_ctx.timers.assert_no_timers_installed(),
3375 }
3376 }
3377
3378 trait TestIpExt: NudIcmpIpExt {
3379 const LOOKUP_ADDR1: SpecifiedAddr<Self::Addr>;
3380 const LOOKUP_ADDR2: SpecifiedAddr<Self::Addr>;
3381 const LOOKUP_ADDR3: SpecifiedAddr<Self::Addr>;
3382 }
3383
3384 impl TestIpExt for Ipv4 {
3385 const LOOKUP_ADDR1: SpecifiedAddr<Ipv4Addr> =
3387 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.1")) };
3388 const LOOKUP_ADDR2: SpecifiedAddr<Ipv4Addr> =
3389 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.2")) };
3390 const LOOKUP_ADDR3: SpecifiedAddr<Ipv4Addr> =
3391 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.3")) };
3392 }
3393
3394 impl TestIpExt for Ipv6 {
3395 const LOOKUP_ADDR1: SpecifiedAddr<Ipv6Addr> =
3397 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::1")) };
3398 const LOOKUP_ADDR2: SpecifiedAddr<Ipv6Addr> =
3399 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::2")) };
3400 const LOOKUP_ADDR3: SpecifiedAddr<Ipv6Addr> =
3401 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::3")) };
3402 }
3403
3404 const LINK_ADDR1: UnicastAddr<FakeLinkAddress> =
3405 unsafe { UnicastAddr::new_unchecked(FakeLinkAddress([2])) };
3406 const LINK_ADDR2: UnicastAddr<FakeLinkAddress> =
3407 unsafe { UnicastAddr::new_unchecked(FakeLinkAddress([4])) };
3408 const LINK_ADDR3: UnicastAddr<FakeLinkAddress> =
3409 unsafe { UnicastAddr::new_unchecked(FakeLinkAddress([6])) };
3410
3411 impl<I: Ip, L: LinkDevice> NudTimerId<I, L, FakeWeakDeviceId<FakeLinkDeviceId>> {
3412 fn neighbor() -> Self {
3413 Self {
3414 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3415 timer_type: NudTimerType::Neighbor,
3416 _marker: PhantomData,
3417 }
3418 }
3419
3420 fn garbage_collection() -> Self {
3421 Self {
3422 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3423 timer_type: NudTimerType::GarbageCollection,
3424 _marker: PhantomData,
3425 }
3426 }
3427 }
3428
3429 fn queue_ip_packet_to_unresolved_neighbor<I: TestIpExt>(
3430 core_ctx: &mut FakeCoreCtxImpl<I>,
3431 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3432 neighbor: SpecifiedAddr<I::Addr>,
3433 pending_frames: &mut VecDeque<Buf<Vec<u8>>>,
3434 body: u8,
3435 expect_event: bool,
3436 ) {
3437 let body = [body];
3438 assert_eq!(
3439 NudHandler::send_ip_packet_to_neighbor(
3440 core_ctx,
3441 bindings_ctx,
3442 &FakeLinkDeviceId,
3443 neighbor,
3444 Buf::new(body, ..),
3445 FakeTxMetadata::default(),
3446 ),
3447 Ok(())
3448 );
3449
3450 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
3451
3452 pending_frames.push_back(Buf::new(body.to_vec(), ..));
3453
3454 assert_neighbor_state_with_ip(
3455 core_ctx,
3456 bindings_ctx,
3457 neighbor,
3458 DynamicNeighborState::Incomplete(Incomplete {
3459 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
3460 pending_frames: pending_frames
3461 .iter()
3462 .cloned()
3463 .map(|buf| (buf, FakeTxMetadata::default()))
3464 .collect(),
3465 notifiers: Vec::new(),
3466 _marker: PhantomData,
3467 }),
3468 expect_event.then_some(ExpectedEvent::Added),
3469 );
3470
3471 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3472 bindings_ctx,
3473 [(neighbor, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
3474 );
3475 }
3476
3477 fn init_incomplete_neighbor_with_ip<I: TestIpExt>(
3478 core_ctx: &mut FakeCoreCtxImpl<I>,
3479 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3480 ip_address: SpecifiedAddr<I::Addr>,
3481 take_probe: bool,
3482 ) -> VecDeque<Buf<Vec<u8>>> {
3483 let mut pending_frames = VecDeque::new();
3484 queue_ip_packet_to_unresolved_neighbor(
3485 core_ctx,
3486 bindings_ctx,
3487 ip_address,
3488 &mut pending_frames,
3489 1,
3490 true, );
3492 if take_probe {
3493 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, None);
3494 }
3495 pending_frames
3496 }
3497
3498 fn init_incomplete_neighbor<I: TestIpExt>(
3499 core_ctx: &mut FakeCoreCtxImpl<I>,
3500 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3501 take_probe: bool,
3502 ) -> VecDeque<Buf<Vec<u8>>> {
3503 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, take_probe)
3504 }
3505
3506 fn init_stale_neighbor_with_ip<I: TestIpExt>(
3507 core_ctx: &mut FakeCoreCtxImpl<I>,
3508 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3509 ip_address: SpecifiedAddr<I::Addr>,
3510 link_address: UnicastAddr<FakeLinkAddress>,
3511 ) {
3512 NudHandler::handle_neighbor_update(
3513 core_ctx,
3514 bindings_ctx,
3515 &FakeLinkDeviceId,
3516 ip_address,
3517 DynamicNeighborUpdateSource::Probe { link_address },
3518 );
3519 assert_neighbor_state_with_ip(
3520 core_ctx,
3521 bindings_ctx,
3522 ip_address,
3523 DynamicNeighborState::Stale(Stale { link_address }),
3524 Some(ExpectedEvent::Added),
3525 );
3526 }
3527
3528 fn init_stale_neighbor<I: TestIpExt>(
3529 core_ctx: &mut FakeCoreCtxImpl<I>,
3530 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3531 link_address: UnicastAddr<FakeLinkAddress>,
3532 ) {
3533 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3534 }
3535
3536 fn init_reachable_neighbor_with_ip<I: TestIpExt>(
3537 core_ctx: &mut FakeCoreCtxImpl<I>,
3538 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3539 ip_address: SpecifiedAddr<I::Addr>,
3540 link_address: UnicastAddr<FakeLinkAddress>,
3541 ) {
3542 let queued_frame =
3543 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, true);
3544 NudHandler::handle_neighbor_update(
3545 core_ctx,
3546 bindings_ctx,
3547 &FakeLinkDeviceId,
3548 ip_address,
3549 DynamicNeighborUpdateSource::Confirmation {
3550 link_address: Some(link_address),
3551 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
3552 },
3553 );
3554 assert_neighbor_state_with_ip(
3555 core_ctx,
3556 bindings_ctx,
3557 ip_address,
3558 DynamicNeighborState::Reachable(Reachable {
3559 link_address,
3560 last_confirmed_at: bindings_ctx.now(),
3561 }),
3562 Some(ExpectedEvent::Changed),
3563 );
3564 assert_pending_frame_sent(core_ctx, queued_frame, link_address);
3565 }
3566
3567 fn init_reachable_neighbor<I: TestIpExt>(
3568 core_ctx: &mut FakeCoreCtxImpl<I>,
3569 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3570 link_address: UnicastAddr<FakeLinkAddress>,
3571 ) {
3572 init_reachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3573 }
3574
3575 fn init_delay_neighbor_with_ip<I: TestIpExt>(
3576 core_ctx: &mut FakeCoreCtxImpl<I>,
3577 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3578 ip_address: SpecifiedAddr<I::Addr>,
3579 link_address: UnicastAddr<FakeLinkAddress>,
3580 ) {
3581 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3582 assert_eq!(
3583 NudHandler::send_ip_packet_to_neighbor(
3584 core_ctx,
3585 bindings_ctx,
3586 &FakeLinkDeviceId,
3587 ip_address,
3588 Buf::new([1], ..),
3589 FakeTxMetadata::default(),
3590 ),
3591 Ok(())
3592 );
3593 assert_neighbor_state_with_ip(
3594 core_ctx,
3595 bindings_ctx,
3596 ip_address,
3597 DynamicNeighborState::Delay(Delay { link_address }),
3598 Some(ExpectedEvent::Changed),
3599 );
3600 assert_eq!(
3601 core_ctx.inner.take_frames(),
3602 vec![(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![1])],
3603 );
3604 }
3605
3606 fn init_delay_neighbor<I: TestIpExt>(
3607 core_ctx: &mut FakeCoreCtxImpl<I>,
3608 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3609 link_address: UnicastAddr<FakeLinkAddress>,
3610 ) {
3611 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3612 }
3613
3614 fn init_probe_neighbor_with_ip<I: TestIpExt>(
3615 core_ctx: &mut FakeCoreCtxImpl<I>,
3616 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3617 ip_address: SpecifiedAddr<I::Addr>,
3618 link_address: UnicastAddr<FakeLinkAddress>,
3619 take_probe: bool,
3620 ) {
3621 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3622 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3623 core_ctx.nud.state.timer_heap.neighbor.assert_top(&ip_address, &NudEvent::DelayFirstProbe);
3624 assert_eq!(
3625 bindings_ctx.trigger_timers_for(DELAY_FIRST_PROBE_TIME.into(), core_ctx),
3626 [NudTimerId::neighbor()]
3627 );
3628 assert_neighbor_state_with_ip(
3629 core_ctx,
3630 bindings_ctx,
3631 ip_address,
3632 DynamicNeighborState::Probe(Probe {
3633 link_address,
3634 transmit_counter: NonZeroU16::new(max_unicast_solicit - 1),
3635 }),
3636 Some(ExpectedEvent::Changed),
3637 );
3638 if take_probe {
3639 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3640 }
3641 }
3642
3643 fn init_probe_neighbor<I: TestIpExt>(
3644 core_ctx: &mut FakeCoreCtxImpl<I>,
3645 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3646 link_address: UnicastAddr<FakeLinkAddress>,
3647 take_probe: bool,
3648 ) {
3649 init_probe_neighbor_with_ip(
3650 core_ctx,
3651 bindings_ctx,
3652 I::LOOKUP_ADDR1,
3653 link_address,
3654 take_probe,
3655 );
3656 }
3657
3658 fn init_unreachable_neighbor_with_ip<I: TestIpExt>(
3659 core_ctx: &mut FakeCoreCtxImpl<I>,
3660 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3661 ip_address: SpecifiedAddr<I::Addr>,
3662 link_address: UnicastAddr<FakeLinkAddress>,
3663 ) {
3664 init_probe_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address, false);
3665 let retransmit_timeout = core_ctx.inner.retransmit_timeout();
3666 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3667 for _ in 0..max_unicast_solicit {
3668 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3669 assert_eq!(
3670 bindings_ctx.trigger_timers_for(retransmit_timeout.into(), core_ctx),
3671 [NudTimerId::neighbor()]
3672 );
3673 }
3674 assert_neighbor_state_with_ip(
3675 core_ctx,
3676 bindings_ctx,
3677 ip_address,
3678 DynamicNeighborState::Unreachable(Unreachable {
3679 link_address,
3680 mode: UnreachableMode::WaitingForPacketSend,
3681 }),
3682 Some(ExpectedEvent::Changed),
3683 );
3684 }
3685
3686 fn init_unreachable_neighbor<I: TestIpExt>(
3687 core_ctx: &mut FakeCoreCtxImpl<I>,
3688 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3689 link_address: UnicastAddr<FakeLinkAddress>,
3690 ) {
3691 init_unreachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3692 }
3693
3694 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
3695 enum InitialState {
3696 Incomplete,
3697 Stale,
3698 Reachable,
3699 Delay,
3700 Probe,
3701 Unreachable,
3702 }
3703
3704 fn init_neighbor_in_state<I: TestIpExt>(
3705 core_ctx: &mut FakeCoreCtxImpl<I>,
3706 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3707 state: InitialState,
3708 ) -> DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>> {
3709 match state {
3710 InitialState::Incomplete => {
3711 let _: VecDeque<Buf<Vec<u8>>> =
3712 init_incomplete_neighbor(core_ctx, bindings_ctx, true);
3713 }
3714 InitialState::Reachable => {
3715 init_reachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3716 }
3717 InitialState::Stale => {
3718 init_stale_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3719 }
3720 InitialState::Delay => {
3721 init_delay_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3722 }
3723 InitialState::Probe => {
3724 init_probe_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, true);
3725 }
3726 InitialState::Unreachable => {
3727 init_unreachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3728 }
3729 }
3730 assert_matches!(core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
3731 Some(NeighborState::Dynamic(state)) => state.clone()
3732 )
3733 }
3734
3735 #[track_caller]
3736 fn init_static_neighbor_with_ip<I: TestIpExt>(
3737 core_ctx: &mut FakeCoreCtxImpl<I>,
3738 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3739 ip_address: SpecifiedAddr<I::Addr>,
3740 link_address: UnicastAddr<FakeLinkAddress>,
3741 expected_event: ExpectedEvent,
3742 ) {
3743 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3744 NeighborApi::new(&mut ctx)
3745 .insert_static_entry(&FakeLinkDeviceId, *ip_address, link_address)
3746 .unwrap();
3747 assert_eq!(
3748 ctx.bindings_ctx.take_events(),
3749 [Event {
3750 device: FakeLinkDeviceId,
3751 addr: ip_address,
3752 kind: match expected_event {
3753 ExpectedEvent::Added => EventKind::Added(EventState::Static(link_address)),
3754 ExpectedEvent::Changed => EventKind::Changed(EventState::Static(link_address)),
3755 },
3756 at: ctx.bindings_ctx.now(),
3757 }],
3758 );
3759 }
3760
3761 #[track_caller]
3762 fn init_static_neighbor<I: TestIpExt>(
3763 core_ctx: &mut FakeCoreCtxImpl<I>,
3764 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3765 link_address: UnicastAddr<FakeLinkAddress>,
3766 expected_event: ExpectedEvent,
3767 ) {
3768 init_static_neighbor_with_ip(
3769 core_ctx,
3770 bindings_ctx,
3771 I::LOOKUP_ADDR1,
3772 link_address,
3773 expected_event,
3774 );
3775 }
3776
3777 #[track_caller]
3778 fn delete_neighbor<I: TestIpExt>(
3779 core_ctx: &mut FakeCoreCtxImpl<I>,
3780 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3781 ) {
3782 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3783 NeighborApi::new(&mut ctx)
3784 .remove_entry(&FakeLinkDeviceId, *I::LOOKUP_ADDR1)
3785 .expect("neighbor entry should exist");
3786 assert_eq!(
3787 ctx.bindings_ctx.take_events(),
3788 [Event::removed(&FakeLinkDeviceId, I::LOOKUP_ADDR1, ctx.bindings_ctx.now())],
3789 );
3790 }
3791
3792 #[track_caller]
3793 fn assert_neighbor_state<I: TestIpExt>(
3794 core_ctx: &FakeCoreCtxImpl<I>,
3795 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3796 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3797 event_kind: Option<ExpectedEvent>,
3798 ) {
3799 assert_neighbor_state_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, state, event_kind);
3800 }
3801
3802 #[derive(Clone, Copy, Debug)]
3803 enum ExpectedEvent {
3804 Added,
3805 Changed,
3806 }
3807
3808 #[track_caller]
3809 fn assert_neighbor_state_with_ip<I: TestIpExt>(
3810 core_ctx: &FakeCoreCtxImpl<I>,
3811 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3812 neighbor: SpecifiedAddr<I::Addr>,
3813 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3814 expected_event: Option<ExpectedEvent>,
3815 ) {
3816 if let Some(expected_event) = expected_event {
3817 let event_state = EventState::Dynamic(state.to_event_dynamic_state());
3818 assert_eq!(
3819 bindings_ctx.take_events(),
3820 [Event {
3821 device: FakeLinkDeviceId,
3822 addr: neighbor,
3823 kind: match expected_event {
3824 ExpectedEvent::Added => EventKind::Added(event_state),
3825 ExpectedEvent::Changed => EventKind::Changed(event_state),
3826 },
3827 at: bindings_ctx.now(),
3828 }],
3829 );
3830 }
3831
3832 assert_eq!(
3833 core_ctx.nud.state.neighbors.get(&neighbor),
3834 Some(&NeighborState::Dynamic(state))
3835 );
3836 }
3837
3838 #[track_caller]
3839 fn assert_pending_frame_sent<I: TestIpExt>(
3840 core_ctx: &mut FakeCoreCtxImpl<I>,
3841 pending_frames: VecDeque<Buf<Vec<u8>>>,
3842 link_address: UnicastAddr<FakeLinkAddress>,
3843 ) {
3844 assert_eq!(
3845 core_ctx.inner.take_frames(),
3846 pending_frames
3847 .into_iter()
3848 .map(|f| (
3849 FakeNudMessageMeta::IpFrame { dst_link_address: link_address },
3850 f.as_ref().to_vec(),
3851 ))
3852 .collect::<Vec<_>>()
3853 );
3854 }
3855
3856 #[track_caller]
3857 fn assert_neighbor_probe_sent_for_ip<I: TestIpExt>(
3858 core_ctx: &mut FakeCoreCtxImpl<I>,
3859 ip_address: SpecifiedAddr<I::Addr>,
3860 link_address: Option<UnicastAddr<FakeLinkAddress>>,
3861 ) {
3862 assert_eq!(
3863 core_ctx.inner.take_frames(),
3864 [(
3865 FakeNudMessageMeta::NeighborSolicitation {
3866 lookup_addr: ip_address,
3867 remote_link_addr: link_address,
3868 },
3869 Vec::new()
3870 )]
3871 );
3872 }
3873
3874 #[track_caller]
3875 fn assert_neighbor_probe_sent<I: TestIpExt>(
3876 core_ctx: &mut FakeCoreCtxImpl<I>,
3877 link_address: Option<UnicastAddr<FakeLinkAddress>>,
3878 ) {
3879 assert_neighbor_probe_sent_for_ip(core_ctx, I::LOOKUP_ADDR1, link_address);
3880 }
3881
3882 #[track_caller]
3883 fn assert_neighbor_removed_with_ip<I: TestIpExt>(
3884 core_ctx: &mut FakeCoreCtxImpl<I>,
3885 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3886 neighbor: SpecifiedAddr<I::Addr>,
3887 ) {
3888 super::testutil::assert_neighbor_unknown(core_ctx, FakeLinkDeviceId, neighbor);
3889 assert_eq!(
3890 bindings_ctx.take_events(),
3891 [Event::removed(&FakeLinkDeviceId, neighbor, bindings_ctx.now())],
3892 );
3893 }
3894
3895 #[ip_test(I)]
3896 fn serialization_failure_doesnt_schedule_timer<I: TestIpExt>() {
3897 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3898
3899 let packet = Buf::new([0; 2], ..).with_size_limit(1);
3902
3903 let err = assert_matches!(
3904 NudHandler::send_ip_packet_to_neighbor(
3905 &mut core_ctx,
3906 &mut bindings_ctx,
3907 &FakeLinkDeviceId,
3908 I::LOOKUP_ADDR1,
3909 packet,
3910 FakeTxMetadata::default(),
3911 ),
3912 Err(ErrorAndSerializer { error, serializer: _ }) => error
3913 );
3914 assert_eq!(err, SendFrameErrorReason::SizeConstraintsViolation);
3915
3916 super::testutil::assert_neighbor_unknown(&mut core_ctx, FakeLinkDeviceId, I::LOOKUP_ADDR1);
3919 assert_eq!(core_ctx.inner.take_frames(), []);
3920 bindings_ctx.timers.assert_no_timers_installed();
3921 }
3922
3923 #[ip_test(I)]
3924 fn incomplete_to_stale_on_probe<I: TestIpExt>() {
3925 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3926
3927 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3929
3930 NudHandler::handle_neighbor_update(
3932 &mut core_ctx,
3933 &mut bindings_ctx,
3934 &FakeLinkDeviceId,
3935 I::LOOKUP_ADDR1,
3936 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
3937 );
3938
3939 assert_neighbor_state(
3941 &core_ctx,
3942 &mut bindings_ctx,
3943 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3944 Some(ExpectedEvent::Changed),
3945 );
3946 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3947 }
3948
3949 #[ip_test(I)]
3950 #[test_case(true, true; "solicited override")]
3951 #[test_case(true, false; "solicited non-override")]
3952 #[test_case(false, true; "unsolicited override")]
3953 #[test_case(false, false; "unsolicited non-override")]
3954 fn incomplete_on_confirmation<I: TestIpExt>(solicited_flag: bool, override_flag: bool) {
3955 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3956
3957 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3959
3960 NudHandler::handle_neighbor_update(
3962 &mut core_ctx,
3963 &mut bindings_ctx,
3964 &FakeLinkDeviceId,
3965 I::LOOKUP_ADDR1,
3966 DynamicNeighborUpdateSource::Confirmation {
3967 link_address: Some(LINK_ADDR1),
3968 flags: ConfirmationFlags { solicited_flag, override_flag },
3969 },
3970 );
3971
3972 let expected_state = if solicited_flag {
3973 DynamicNeighborState::Reachable(Reachable {
3974 link_address: LINK_ADDR1,
3975 last_confirmed_at: bindings_ctx.now(),
3976 })
3977 } else {
3978 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 })
3979 };
3980 assert_neighbor_state(
3981 &core_ctx,
3982 &mut bindings_ctx,
3983 expected_state,
3984 Some(ExpectedEvent::Changed),
3985 );
3986 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3987 }
3988
3989 #[ip_test(I)]
3990 fn reachable_to_stale_on_timeout<I: TestIpExt>() {
3991 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3992
3993 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
3995
3996 assert_eq!(
3998 bindings_ctx
3999 .trigger_timers_for(core_ctx.inner.base_reachable_time().into(), &mut core_ctx,),
4000 [NudTimerId::neighbor()]
4001 );
4002 assert_neighbor_state(
4003 &core_ctx,
4004 &mut bindings_ctx,
4005 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4006 Some(ExpectedEvent::Changed),
4007 );
4008 }
4009
4010 #[ip_test(I)]
4011 #[test_case(InitialState::Reachable, true; "reachable with different address")]
4012 #[test_case(InitialState::Reachable, false; "reachable with same address")]
4013 #[test_case(InitialState::Stale, true; "stale with different address")]
4014 #[test_case(InitialState::Stale, false; "stale with same address")]
4015 #[test_case(InitialState::Delay, true; "delay with different address")]
4016 #[test_case(InitialState::Delay, false; "delay with same address")]
4017 #[test_case(InitialState::Probe, true; "probe with different address")]
4018 #[test_case(InitialState::Probe, false; "probe with same address")]
4019 #[test_case(InitialState::Unreachable, true; "unreachable with different address")]
4020 #[test_case(InitialState::Unreachable, false; "unreachable with same address")]
4021 fn transition_to_stale_on_probe_with_different_address<I: TestIpExt>(
4022 initial_state: InitialState,
4023 update_link_address: bool,
4024 ) {
4025 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4026
4027 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4029
4030 NudHandler::handle_neighbor_update(
4032 &mut core_ctx,
4033 &mut bindings_ctx,
4034 &FakeLinkDeviceId,
4035 I::LOOKUP_ADDR1,
4036 DynamicNeighborUpdateSource::Probe {
4037 link_address: if update_link_address { LINK_ADDR2 } else { LINK_ADDR1 },
4038 },
4039 );
4040
4041 let expected_state = if update_link_address {
4047 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 })
4048 } else {
4049 initial_state
4050 };
4051 assert_neighbor_state(
4052 &core_ctx,
4053 &mut bindings_ctx,
4054 expected_state,
4055 update_link_address.then_some(ExpectedEvent::Changed),
4056 );
4057 }
4058
4059 #[ip_test(I)]
4060 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
4061 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
4062 #[test_case(InitialState::Stale, true; "stale with override flag set")]
4063 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
4064 #[test_case(InitialState::Delay, true; "delay with override flag set")]
4065 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
4066 #[test_case(InitialState::Probe, true; "probe with override flag set")]
4067 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
4068 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
4069 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
4070 fn transition_to_reachable_on_solicited_confirmation_same_address<I: TestIpExt>(
4071 initial_state: InitialState,
4072 override_flag: bool,
4073 ) {
4074 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4075
4076 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4078
4079 NudHandler::handle_neighbor_update(
4081 &mut core_ctx,
4082 &mut bindings_ctx,
4083 &FakeLinkDeviceId,
4084 I::LOOKUP_ADDR1,
4085 DynamicNeighborUpdateSource::Confirmation {
4086 link_address: Some(LINK_ADDR1),
4087 flags: ConfirmationFlags { solicited_flag: true, override_flag },
4088 },
4089 );
4090
4091 let now = bindings_ctx.now();
4093 assert_neighbor_state(
4094 &core_ctx,
4095 &mut bindings_ctx,
4096 DynamicNeighborState::Reachable(Reachable {
4097 link_address: LINK_ADDR1,
4098 last_confirmed_at: now,
4099 }),
4100 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
4101 );
4102 }
4103
4104 #[ip_test(I)]
4105 #[test_case(InitialState::Reachable; "reachable")]
4106 #[test_case(InitialState::Stale; "stale")]
4107 #[test_case(InitialState::Delay; "delay")]
4108 #[test_case(InitialState::Probe; "probe")]
4109 #[test_case(InitialState::Unreachable; "unreachable")]
4110 fn transition_to_stale_on_unsolicited_override_confirmation_with_different_address<
4111 I: TestIpExt,
4112 >(
4113 initial_state: InitialState,
4114 ) {
4115 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4116
4117 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4119
4120 NudHandler::handle_neighbor_update(
4122 &mut core_ctx,
4123 &mut bindings_ctx,
4124 &FakeLinkDeviceId,
4125 I::LOOKUP_ADDR1,
4126 DynamicNeighborUpdateSource::Confirmation {
4127 link_address: Some(LINK_ADDR2),
4128 flags: ConfirmationFlags { solicited_flag: false, override_flag: true },
4129 },
4130 );
4131
4132 assert_neighbor_state(
4134 &core_ctx,
4135 &mut bindings_ctx,
4136 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4137 Some(ExpectedEvent::Changed),
4138 );
4139 }
4140
4141 #[ip_test(I)]
4142 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
4143 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
4144 #[test_case(InitialState::Stale, true; "stale with override flag set")]
4145 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
4146 #[test_case(InitialState::Delay, true; "delay with override flag set")]
4147 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
4148 #[test_case(InitialState::Probe, true; "probe with override flag set")]
4149 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
4150 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
4151 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
4152 fn noop_on_unsolicited_confirmation_with_same_address<I: TestIpExt>(
4153 initial_state: InitialState,
4154 override_flag: bool,
4155 ) {
4156 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4157
4158 let expected_state =
4160 init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4161
4162 NudHandler::handle_neighbor_update(
4164 &mut core_ctx,
4165 &mut bindings_ctx,
4166 &FakeLinkDeviceId,
4167 I::LOOKUP_ADDR1,
4168 DynamicNeighborUpdateSource::Confirmation {
4169 link_address: Some(LINK_ADDR1),
4170 flags: ConfirmationFlags { solicited_flag: false, override_flag },
4171 },
4172 );
4173
4174 assert_neighbor_state(&core_ctx, &mut bindings_ctx, expected_state, None);
4176 }
4177
4178 #[ip_test(I)]
4179 #[test_case(InitialState::Reachable; "reachable")]
4180 #[test_case(InitialState::Stale; "stale")]
4181 #[test_case(InitialState::Delay; "delay")]
4182 #[test_case(InitialState::Probe; "probe")]
4183 #[test_case(InitialState::Unreachable; "unreachable")]
4184 fn transition_to_reachable_on_solicited_override_confirmation_with_different_address<
4185 I: TestIpExt,
4186 >(
4187 initial_state: InitialState,
4188 ) {
4189 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4190
4191 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4193
4194 NudHandler::handle_neighbor_update(
4196 &mut core_ctx,
4197 &mut bindings_ctx,
4198 &FakeLinkDeviceId,
4199 I::LOOKUP_ADDR1,
4200 DynamicNeighborUpdateSource::Confirmation {
4201 link_address: Some(LINK_ADDR2),
4202 flags: ConfirmationFlags { solicited_flag: true, override_flag: true },
4203 },
4204 );
4205
4206 let now = bindings_ctx.now();
4208 assert_neighbor_state(
4209 &core_ctx,
4210 &mut bindings_ctx,
4211 DynamicNeighborState::Reachable(Reachable {
4212 link_address: LINK_ADDR2,
4213 last_confirmed_at: now,
4214 }),
4215 Some(ExpectedEvent::Changed),
4216 );
4217 }
4218
4219 #[ip_test(I)]
4220 fn reachable_to_reachable_on_probe_with_same_address<I: TestIpExt>() {
4221 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4222
4223 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4225
4226 NudHandler::handle_neighbor_update(
4228 &mut core_ctx,
4229 &mut bindings_ctx,
4230 &FakeLinkDeviceId,
4231 I::LOOKUP_ADDR1,
4232 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
4233 );
4234
4235 let now = bindings_ctx.now();
4237 assert_neighbor_state(
4238 &core_ctx,
4239 &mut bindings_ctx,
4240 DynamicNeighborState::Reachable(Reachable {
4241 link_address: LINK_ADDR1,
4242 last_confirmed_at: now,
4243 }),
4244 None,
4245 );
4246 }
4247
4248 #[ip_test(I)]
4249 #[test_case(true; "solicited")]
4250 #[test_case(false; "unsolicited")]
4251 fn reachable_to_stale_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4252 solicited_flag: bool,
4253 ) {
4254 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4255
4256 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4258
4259 NudHandler::handle_neighbor_update(
4261 &mut core_ctx,
4262 &mut bindings_ctx,
4263 &FakeLinkDeviceId,
4264 I::LOOKUP_ADDR1,
4265 DynamicNeighborUpdateSource::Confirmation {
4266 link_address: Some(LINK_ADDR2),
4267 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4268 },
4269 );
4270
4271 assert_neighbor_state(
4274 &core_ctx,
4275 &mut bindings_ctx,
4276 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4277 Some(ExpectedEvent::Changed),
4278 );
4279 }
4280
4281 #[ip_test(I)]
4282 #[test_case(InitialState::Stale, true; "stale solicited")]
4283 #[test_case(InitialState::Stale, false; "stale unsolicited")]
4284 #[test_case(InitialState::Delay, true; "delay solicited")]
4285 #[test_case(InitialState::Delay, false; "delay unsolicited")]
4286 #[test_case(InitialState::Probe, true; "probe solicited")]
4287 #[test_case(InitialState::Probe, false; "probe unsolicited")]
4288 #[test_case(InitialState::Unreachable, true; "unreachable solicited")]
4289 #[test_case(InitialState::Unreachable, false; "unreachable unsolicited")]
4290 fn noop_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4291 initial_state: InitialState,
4292 solicited_flag: bool,
4293 ) {
4294 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4295
4296 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4298
4299 NudHandler::handle_neighbor_update(
4301 &mut core_ctx,
4302 &mut bindings_ctx,
4303 &FakeLinkDeviceId,
4304 I::LOOKUP_ADDR1,
4305 DynamicNeighborUpdateSource::Confirmation {
4306 link_address: Some(LINK_ADDR2),
4307 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4308 },
4309 );
4310
4311 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial_state, None);
4314 }
4315
4316 #[ip_test(I)]
4317 fn stale_to_delay_on_packet_sent<I: TestIpExt>() {
4318 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4319
4320 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4322
4323 let body = 1;
4325 assert_eq!(
4326 NudHandler::send_ip_packet_to_neighbor(
4327 &mut core_ctx,
4328 &mut bindings_ctx,
4329 &FakeLinkDeviceId,
4330 I::LOOKUP_ADDR1,
4331 Buf::new([body], ..),
4332 FakeTxMetadata::default(),
4333 ),
4334 Ok(())
4335 );
4336
4337 assert_neighbor_state(
4339 &core_ctx,
4340 &mut bindings_ctx,
4341 DynamicNeighborState::Delay(Delay { link_address: LINK_ADDR1 }),
4342 Some(ExpectedEvent::Changed),
4343 );
4344 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4345 &mut bindings_ctx,
4346 [(I::LOOKUP_ADDR1, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
4347 );
4348 assert_pending_frame_sent(
4349 &mut core_ctx,
4350 VecDeque::from([Buf::new(vec![body], ..)]),
4351 LINK_ADDR1,
4352 );
4353 }
4354
4355 #[ip_test(I)]
4356 #[test_case(InitialState::Delay,
4357 NudEvent::DelayFirstProbe;
4358 "delay to probe")]
4359 #[test_case(InitialState::Probe,
4360 NudEvent::RetransmitUnicastProbe;
4361 "probe retransmit unicast probe")]
4362 fn delay_or_probe_to_probe_on_timeout<I: TestIpExt>(
4363 initial_state: InitialState,
4364 expected_initial_event: NudEvent,
4365 ) {
4366 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4367
4368 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4370
4371 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4372
4373 let (time, transmit_counter) = match initial_state {
4379 InitialState::Delay => {
4380 (DELAY_FIRST_PROBE_TIME, NonZeroU16::new(max_unicast_solicit - 1))
4381 }
4382 InitialState::Probe => {
4383 (core_ctx.inner.state.retrans_timer, NonZeroU16::new(max_unicast_solicit - 2))
4384 }
4385 other => unreachable!("test only covers DELAY and PROBE, got {:?}", other),
4386 };
4387 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4388 &mut bindings_ctx,
4389 [(I::LOOKUP_ADDR1, expected_initial_event, time.get())],
4390 );
4391 assert_eq!(
4392 bindings_ctx.trigger_timers_for(time.into(), &mut core_ctx,),
4393 [NudTimerId::neighbor()]
4394 );
4395 assert_neighbor_state(
4396 &core_ctx,
4397 &mut bindings_ctx,
4398 DynamicNeighborState::Probe(Probe { link_address: LINK_ADDR1, transmit_counter }),
4399 (initial_state != InitialState::Probe).then_some(ExpectedEvent::Changed),
4400 );
4401 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4402 &mut bindings_ctx,
4403 [(
4404 I::LOOKUP_ADDR1,
4405 NudEvent::RetransmitUnicastProbe,
4406 core_ctx.inner.state.retrans_timer.get(),
4407 )],
4408 );
4409 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4410 }
4411
4412 #[ip_test(I)]
4413 fn unreachable_probes_with_exponential_backoff_while_packets_sent<I: TestIpExt>() {
4414 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4415
4416 init_unreachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4417
4418 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4419 let timer_id = NudTimerId::neighbor();
4420
4421 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), []);
4423 assert_eq!(core_ctx.inner.take_frames(), []);
4424
4425 const BODY: u8 = 0x33;
4427 assert_eq!(
4428 NudHandler::send_ip_packet_to_neighbor(
4429 &mut core_ctx,
4430 &mut bindings_ctx,
4431 &FakeLinkDeviceId,
4432 I::LOOKUP_ADDR1,
4433 Buf::new([BODY], ..),
4434 FakeTxMetadata::default(),
4435 ),
4436 Ok(())
4437 );
4438 assert_eq!(
4439 core_ctx.inner.take_frames(),
4440 [
4441 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4442 (
4443 FakeNudMessageMeta::NeighborSolicitation {
4444 lookup_addr: I::LOOKUP_ADDR1,
4445 remote_link_addr: None,
4446 },
4447 Vec::new()
4448 )
4449 ]
4450 );
4451
4452 let next_backoff_timer = |core_ctx: &mut FakeCoreCtxImpl<I>, probes_sent| {
4453 UnreachableMode::Backoff {
4454 probes_sent: NonZeroU32::new(probes_sent).unwrap(),
4455 packet_sent: false,
4456 }
4457 .next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state)
4458 .get()
4459 };
4460
4461 const ITERATIONS: u8 = 2;
4462 for i in 1..ITERATIONS {
4463 let probes_sent = u32::from(i);
4464
4465 assert_eq!(
4468 NudHandler::send_ip_packet_to_neighbor(
4469 &mut core_ctx,
4470 &mut bindings_ctx,
4471 &FakeLinkDeviceId,
4472 I::LOOKUP_ADDR1,
4473 Buf::new([BODY + i], ..),
4474 FakeTxMetadata::default(),
4475 ),
4476 Ok(())
4477 );
4478 assert_eq!(
4479 core_ctx.inner.take_frames(),
4480 [(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY + i])]
4481 );
4482
4483 assert_eq!(
4488 bindings_ctx.trigger_timers_for(
4489 next_backoff_timer(&mut core_ctx, probes_sent),
4490 &mut core_ctx,
4491 ),
4492 [timer_id]
4493 );
4494 assert_neighbor_probe_sent(&mut core_ctx, None);
4495 bindings_ctx.timers.assert_timers_installed([(
4496 timer_id,
4497 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, probes_sent + 1),
4498 )]);
4499 }
4500
4501 let current_timer = next_backoff_timer(&mut core_ctx, u32::from(ITERATIONS));
4504 assert_eq!(bindings_ctx.trigger_timers_for(current_timer, &mut core_ctx,), [timer_id]);
4505 assert_eq!(core_ctx.inner.take_frames(), []);
4506 bindings_ctx.timers.assert_no_timers_installed();
4507
4508 assert_eq!(
4511 NudHandler::send_ip_packet_to_neighbor(
4512 &mut core_ctx,
4513 &mut bindings_ctx,
4514 &FakeLinkDeviceId,
4515 I::LOOKUP_ADDR1,
4516 Buf::new([BODY], ..),
4517 FakeTxMetadata::default(),
4518 ),
4519 Ok(())
4520 );
4521 assert_eq!(
4522 core_ctx.inner.take_frames(),
4523 [
4524 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4525 (
4526 FakeNudMessageMeta::NeighborSolicitation {
4527 lookup_addr: I::LOOKUP_ADDR1,
4528 remote_link_addr: None,
4529 },
4530 Vec::new()
4531 )
4532 ]
4533 );
4534 bindings_ctx.timers.assert_timers_installed([(
4535 timer_id,
4536 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, 1),
4537 )]);
4538 }
4539
4540 #[ip_test(I)]
4541 #[test_case(true; "solicited confirmation")]
4542 #[test_case(false; "unsolicited confirmation")]
4543 fn confirmation_should_not_create_entry<I: TestIpExt>(solicited_flag: bool) {
4544 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4545
4546 let link_address = Some(LINK_ADDR1);
4547 NudHandler::handle_neighbor_update(
4548 &mut core_ctx,
4549 &mut bindings_ctx,
4550 &FakeLinkDeviceId,
4551 I::LOOKUP_ADDR1,
4552 DynamicNeighborUpdateSource::Confirmation {
4553 link_address,
4554 flags: ConfirmationFlags { solicited_flag, override_flag: false },
4555 },
4556 );
4557 assert_eq!(core_ctx.nud.state.neighbors, HashMap::new());
4558 }
4559
4560 #[ip_test(I)]
4561 #[test_case(true; "set_with_dynamic")]
4562 #[test_case(false; "set_with_static")]
4563 fn pending_frames<I: TestIpExt>(dynamic: bool) {
4564 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4565 assert_eq!(core_ctx.inner.take_frames(), []);
4566
4567 const MAX_PENDING_FRAMES_U8: u8 = MAX_PENDING_FRAMES as u8;
4571 let expected_pending_frames = (0..MAX_PENDING_FRAMES_U8)
4572 .map(|i| (Buf::new(vec![i], ..), FakeTxMetadata::default()))
4573 .collect::<VecDeque<_>>();
4574
4575 for (body, meta) in expected_pending_frames.iter() {
4576 assert_eq!(
4577 NudHandler::send_ip_packet_to_neighbor(
4578 &mut core_ctx,
4579 &mut bindings_ctx,
4580 &FakeLinkDeviceId,
4581 I::LOOKUP_ADDR1,
4582 body.clone(),
4583 meta.clone(),
4584 ),
4585 Ok(())
4586 );
4587 }
4588 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4589 assert_neighbor_probe_sent(&mut core_ctx, None);
4591 assert_neighbor_state(
4592 &core_ctx,
4593 &mut bindings_ctx,
4594 DynamicNeighborState::Incomplete(Incomplete {
4595 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4596 pending_frames: expected_pending_frames.clone(),
4597 notifiers: Vec::new(),
4598 _marker: PhantomData,
4599 }),
4600 Some(ExpectedEvent::Added),
4601 );
4602
4603 assert_eq!(
4605 NudHandler::send_ip_packet_to_neighbor(
4606 &mut core_ctx,
4607 &mut bindings_ctx,
4608 &FakeLinkDeviceId,
4609 I::LOOKUP_ADDR1,
4610 Buf::new([123], ..),
4611 FakeTxMetadata::default(),
4612 ),
4613 Ok(())
4614 );
4615 assert_eq!(core_ctx.inner.take_frames(), []);
4616 assert_neighbor_state(
4617 &core_ctx,
4618 &mut bindings_ctx,
4619 DynamicNeighborState::Incomplete(Incomplete {
4620 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4621 pending_frames: expected_pending_frames.clone(),
4622 notifiers: Vec::new(),
4623 _marker: PhantomData,
4624 }),
4625 None,
4626 );
4627
4628 if dynamic {
4630 NudHandler::handle_neighbor_update(
4631 &mut core_ctx,
4632 &mut bindings_ctx,
4633 &FakeLinkDeviceId,
4634 I::LOOKUP_ADDR1,
4635 DynamicNeighborUpdateSource::Confirmation {
4636 link_address: Some(LINK_ADDR1),
4637 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4638 },
4639 );
4640 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4641 &mut bindings_ctx,
4642 [(
4643 I::LOOKUP_ADDR1,
4644 NudEvent::ReachableTime,
4645 core_ctx.inner.base_reachable_time().get(),
4646 )],
4647 );
4648 let last_confirmed_at = bindings_ctx.now();
4649 assert_neighbor_state(
4650 &core_ctx,
4651 &mut bindings_ctx,
4652 DynamicNeighborState::Reachable(Reachable {
4653 link_address: LINK_ADDR1,
4654 last_confirmed_at,
4655 }),
4656 Some(ExpectedEvent::Changed),
4657 );
4658 } else {
4659 init_static_neighbor(
4660 &mut core_ctx,
4661 &mut bindings_ctx,
4662 LINK_ADDR1,
4663 ExpectedEvent::Changed,
4664 );
4665 bindings_ctx.timers.assert_no_timers_installed();
4666 }
4667 assert_eq!(
4668 core_ctx.inner.take_frames(),
4669 expected_pending_frames
4670 .into_iter()
4671 .map(|(p, FakeTxMetadata)| (
4672 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4673 p.as_ref().to_vec()
4674 ))
4675 .collect::<Vec<_>>()
4676 );
4677 }
4678
4679 #[ip_test(I)]
4680 fn static_neighbor<I: TestIpExt>() {
4681 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4682
4683 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4684 bindings_ctx.timers.assert_no_timers_installed();
4685 assert_eq!(core_ctx.inner.take_frames(), []);
4686 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4687
4688 NudHandler::handle_neighbor_update(
4690 &mut core_ctx,
4691 &mut bindings_ctx,
4692 &FakeLinkDeviceId,
4693 I::LOOKUP_ADDR1,
4694 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4695 );
4696 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4697
4698 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4699
4700 let neighbors = &core_ctx.nud.state.neighbors;
4701 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4702 }
4703
4704 #[ip_test(I)]
4705 fn dynamic_neighbor<I: TestIpExt>() {
4706 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4707
4708 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4709 bindings_ctx.timers.assert_no_timers_installed();
4710 assert_eq!(core_ctx.inner.take_frames(), []);
4711 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4712
4713 NudHandler::handle_neighbor_update(
4715 &mut core_ctx,
4716 &mut bindings_ctx,
4717 &FakeLinkDeviceId,
4718 I::LOOKUP_ADDR1,
4719 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4720 );
4721 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR2);
4722 assert_eq!(core_ctx.inner.take_frames(), []);
4723 assert_neighbor_state(
4724 &core_ctx,
4725 &mut bindings_ctx,
4726 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4727 Some(ExpectedEvent::Changed),
4728 );
4729
4730 init_static_neighbor_with_ip(
4732 &mut core_ctx,
4733 &mut bindings_ctx,
4734 I::LOOKUP_ADDR1,
4735 LINK_ADDR3,
4736 ExpectedEvent::Changed,
4737 );
4738 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR3);
4739 assert_eq!(core_ctx.inner.take_frames(), []);
4740 }
4741
4742 #[ip_test(I)]
4743 fn send_solicitation_on_lookup<I: TestIpExt>() {
4744 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4745 bindings_ctx.timers.assert_no_timers_installed();
4746 assert_eq!(core_ctx.inner.take_frames(), []);
4747
4748 let mut pending_frames = VecDeque::new();
4749
4750 queue_ip_packet_to_unresolved_neighbor(
4751 &mut core_ctx,
4752 &mut bindings_ctx,
4753 I::LOOKUP_ADDR1,
4754 &mut pending_frames,
4755 1,
4756 true, );
4758 assert_neighbor_probe_sent(&mut core_ctx, None);
4759
4760 queue_ip_packet_to_unresolved_neighbor(
4761 &mut core_ctx,
4762 &mut bindings_ctx,
4763 I::LOOKUP_ADDR1,
4764 &mut pending_frames,
4765 2,
4766 false, );
4768 assert_eq!(core_ctx.inner.take_frames(), []);
4769
4770 NudHandler::handle_neighbor_update(
4772 &mut core_ctx,
4773 &mut bindings_ctx,
4774 &FakeLinkDeviceId,
4775 I::LOOKUP_ADDR1,
4776 DynamicNeighborUpdateSource::Confirmation {
4777 link_address: Some(LINK_ADDR1),
4778 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4779 },
4780 );
4781 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4782
4783 let now = bindings_ctx.now();
4784 assert_neighbor_state(
4785 &core_ctx,
4786 &mut bindings_ctx,
4787 DynamicNeighborState::Reachable(Reachable {
4788 link_address: LINK_ADDR1,
4789 last_confirmed_at: now,
4790 }),
4791 Some(ExpectedEvent::Changed),
4792 );
4793 assert_eq!(
4794 core_ctx.inner.take_frames(),
4795 pending_frames
4796 .into_iter()
4797 .map(|f| (
4798 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4799 f.as_ref().to_vec(),
4800 ))
4801 .collect::<Vec<_>>()
4802 );
4803 }
4804
4805 #[ip_test(I)]
4806 fn solicitation_failure_in_incomplete<I: TestIpExt>() {
4807 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4808 bindings_ctx.timers.assert_no_timers_installed();
4809 assert_eq!(core_ctx.inner.take_frames(), []);
4810
4811 let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
4812
4813 let timer_id = NudTimerId::neighbor();
4814
4815 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4816 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4817
4818 for i in 1..=max_multicast_solicit {
4819 assert_neighbor_state(
4820 &core_ctx,
4821 &mut bindings_ctx,
4822 DynamicNeighborState::Incomplete(Incomplete {
4823 transmit_counter: NonZeroU16::new(max_multicast_solicit - i),
4824 pending_frames: pending_frames
4825 .iter()
4826 .cloned()
4827 .map(|b| (b, FakeTxMetadata::default()))
4828 .collect(),
4829 notifiers: Vec::new(),
4830 _marker: PhantomData,
4831 }),
4832 None,
4833 );
4834
4835 bindings_ctx
4836 .timers
4837 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4838 assert_neighbor_probe_sent(&mut core_ctx, None);
4839
4840 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4841 }
4842
4843 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1);
4845 bindings_ctx.timers.assert_no_timers_installed();
4846
4847 assert_eq!(core_ctx.inner.take_frames(), []);
4851 assert_eq!(core_ctx.counters().as_ref().icmp_dest_unreachable_dropped.get(), 1);
4852 }
4853
4854 #[ip_test(I)]
4855 fn solicitation_failure_in_probe<I: TestIpExt>() {
4856 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4857 bindings_ctx.timers.assert_no_timers_installed();
4858 assert_eq!(core_ctx.inner.take_frames(), []);
4859
4860 init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
4861
4862 let timer_id = NudTimerId::neighbor();
4863 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4864 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4865 for i in 1..=max_unicast_solicit {
4866 assert_neighbor_state(
4867 &core_ctx,
4868 &mut bindings_ctx,
4869 DynamicNeighborState::Probe(Probe {
4870 transmit_counter: NonZeroU16::new(max_unicast_solicit - i),
4871 link_address: LINK_ADDR1,
4872 }),
4873 None,
4874 );
4875
4876 bindings_ctx
4877 .timers
4878 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4879 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4880
4881 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4882 }
4883
4884 assert_neighbor_state(
4885 &core_ctx,
4886 &mut bindings_ctx,
4887 DynamicNeighborState::Unreachable(Unreachable {
4888 link_address: LINK_ADDR1,
4889 mode: UnreachableMode::WaitingForPacketSend,
4890 }),
4891 Some(ExpectedEvent::Changed),
4892 );
4893 bindings_ctx.timers.assert_no_timers_installed();
4894 assert_eq!(core_ctx.inner.take_frames(), []);
4895 }
4896
4897 #[ip_test(I)]
4898 fn flush_entries<I: TestIpExt>() {
4899 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4900 bindings_ctx.timers.assert_no_timers_installed();
4901 assert_eq!(core_ctx.inner.take_frames(), []);
4902
4903 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4904 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR2, LINK_ADDR2);
4905 let pending_frames = init_incomplete_neighbor_with_ip(
4906 &mut core_ctx,
4907 &mut bindings_ctx,
4908 I::LOOKUP_ADDR3,
4909 true,
4910 );
4911 let pending_frames =
4912 pending_frames.into_iter().map(|b| (b, FakeTxMetadata::default())).collect();
4913
4914 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4915 assert_eq!(
4916 core_ctx.nud.state.neighbors,
4917 HashMap::from([
4918 (I::LOOKUP_ADDR1, NeighborState::Static(LINK_ADDR1)),
4919 (
4920 I::LOOKUP_ADDR2,
4921 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
4922 link_address: LINK_ADDR2,
4923 })),
4924 ),
4925 (
4926 I::LOOKUP_ADDR3,
4927 NeighborState::Dynamic(DynamicNeighborState::Incomplete(Incomplete {
4928 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4929 pending_frames,
4930 notifiers: Vec::new(),
4931 _marker: PhantomData,
4932 })),
4933 ),
4934 ]),
4935 );
4936 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4937 &mut bindings_ctx,
4938 [(I::LOOKUP_ADDR3, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
4939 );
4940
4941 NudHandler::flush(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId);
4943 let neighbors = &core_ctx.nud.state.neighbors;
4944 assert!(neighbors.is_empty(), "neighbor table should be empty: {:?}", neighbors);
4945 assert_eq!(
4946 bindings_ctx.take_events().into_iter().collect::<HashSet<_>>(),
4947 [I::LOOKUP_ADDR1, I::LOOKUP_ADDR2, I::LOOKUP_ADDR3]
4948 .into_iter()
4949 .map(|addr| { Event::removed(&FakeLinkDeviceId, addr, bindings_ctx.now()) })
4950 .collect(),
4951 );
4952 bindings_ctx.timers.assert_no_timers_installed();
4953 }
4954
4955 #[ip_test(I)]
4956 fn delete_dynamic_entry<I: TestIpExt>() {
4957 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4958 bindings_ctx.timers.assert_no_timers_installed();
4959 assert_eq!(core_ctx.inner.take_frames(), []);
4960
4961 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4962 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4963
4964 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4965
4966 let neighbors = &core_ctx.nud.state.neighbors;
4968 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4969 bindings_ctx.timers.assert_no_timers_installed();
4970 }
4971
4972 #[ip_test(I)]
4973 #[test_case(InitialState::Reachable; "reachable neighbor")]
4974 #[test_case(InitialState::Stale; "stale neighbor")]
4975 #[test_case(InitialState::Delay; "delay neighbor")]
4976 #[test_case(InitialState::Probe; "probe neighbor")]
4977 #[test_case(InitialState::Unreachable; "unreachable neighbor")]
4978 fn resolve_cached_linked_addr<I: TestIpExt>(initial_state: InitialState) {
4979 let mut ctx = new_context::<I>();
4980 ctx.bindings_ctx.timers.assert_no_timers_installed();
4981 assert_eq!(ctx.core_ctx.inner.take_frames(), []);
4982
4983 let _ = init_neighbor_in_state(&mut ctx.core_ctx, &mut ctx.bindings_ctx, initial_state);
4984
4985 let link_addr = assert_matches!(
4986 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4987 &FakeLinkDeviceId,
4988 &I::LOOKUP_ADDR1,
4989 ),
4990 LinkResolutionResult::Resolved(addr) => addr
4991 );
4992 assert_eq!(link_addr, LINK_ADDR1);
4993 if initial_state == InitialState::Stale {
4994 assert_eq!(
4995 ctx.bindings_ctx.take_events(),
4996 [Event::changed(
4997 &FakeLinkDeviceId,
4998 EventState::Dynamic(EventDynamicState::Delay(LINK_ADDR1)),
4999 I::LOOKUP_ADDR1,
5000 ctx.bindings_ctx.now(),
5001 )],
5002 );
5003 }
5004 }
5005
5006 enum ResolutionSuccess {
5007 Confirmation,
5008 StaticEntryAdded,
5009 }
5010
5011 #[ip_test(I)]
5012 #[test_case(ResolutionSuccess::Confirmation; "incomplete entry timed out")]
5013 #[test_case(ResolutionSuccess::StaticEntryAdded; "incomplete entry removed from table")]
5014 fn dynamic_neighbor_resolution_success<I: TestIpExt>(reason: ResolutionSuccess) {
5015 let mut ctx = new_context::<I>();
5016
5017 let observers = (0..10)
5018 .map(|_| {
5019 let observer = assert_matches!(
5020 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
5021 &FakeLinkDeviceId,
5022 &I::LOOKUP_ADDR1,
5023 ),
5024 LinkResolutionResult::Pending(observer) => observer
5025 );
5026 assert_eq!(*observer.lock(), None);
5027 observer
5028 })
5029 .collect::<Vec<_>>();
5030 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
5031 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5032
5033 assert_neighbor_state(
5036 core_ctx,
5037 bindings_ctx,
5038 DynamicNeighborState::Incomplete(Incomplete {
5039 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5040 pending_frames: VecDeque::new(),
5041 notifiers: Vec::new(),
5043 _marker: PhantomData,
5044 }),
5045 Some(ExpectedEvent::Added),
5046 );
5047 assert_neighbor_probe_sent(core_ctx, None);
5048
5049 match reason {
5050 ResolutionSuccess::Confirmation => {
5051 NudHandler::handle_neighbor_update(
5053 core_ctx,
5054 bindings_ctx,
5055 &FakeLinkDeviceId,
5056 I::LOOKUP_ADDR1,
5057 DynamicNeighborUpdateSource::Confirmation {
5058 link_address: Some(LINK_ADDR1),
5059 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
5060 },
5061 );
5062 let now = bindings_ctx.now();
5063 assert_neighbor_state(
5064 core_ctx,
5065 bindings_ctx,
5066 DynamicNeighborState::Reachable(Reachable {
5067 link_address: LINK_ADDR1,
5068 last_confirmed_at: now,
5069 }),
5070 Some(ExpectedEvent::Changed),
5071 );
5072 }
5073 ResolutionSuccess::StaticEntryAdded => {
5074 init_static_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, ExpectedEvent::Changed);
5075 assert_eq!(
5076 core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
5077 Some(&NeighborState::Static(LINK_ADDR1))
5078 );
5079 }
5080 }
5081
5082 for observer in observers {
5084 assert_eq!(*observer.lock(), Some(Ok(LINK_ADDR1)));
5085 }
5086 }
5087
5088 enum ResolutionFailure {
5089 Timeout,
5090 Removed,
5091 }
5092
5093 #[ip_test(I)]
5094 #[test_case(ResolutionFailure::Timeout; "incomplete entry timed out")]
5095 #[test_case(ResolutionFailure::Removed; "incomplete entry removed from table")]
5096 fn dynamic_neighbor_resolution_failure<I: TestIpExt>(reason: ResolutionFailure) {
5097 let mut ctx = new_context::<I>();
5098
5099 let observers = (0..10)
5100 .map(|_| {
5101 let observer = assert_matches!(
5102 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
5103 &FakeLinkDeviceId,
5104 &I::LOOKUP_ADDR1,
5105 ),
5106 LinkResolutionResult::Pending(observer) => observer
5107 );
5108 assert_eq!(*observer.lock(), None);
5109 observer
5110 })
5111 .collect::<Vec<_>>();
5112
5113 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
5114 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5115
5116 assert_neighbor_state(
5119 core_ctx,
5120 bindings_ctx,
5121 DynamicNeighborState::Incomplete(Incomplete {
5122 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5123 pending_frames: VecDeque::new(),
5124 notifiers: Vec::new(),
5126 _marker: PhantomData,
5127 }),
5128 Some(ExpectedEvent::Added),
5129 );
5130 assert_neighbor_probe_sent(core_ctx, None);
5131
5132 match reason {
5133 ResolutionFailure::Timeout => {
5134 for _ in 1..=max_multicast_solicit {
5137 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
5138 assert_eq!(
5139 bindings_ctx.trigger_timers_for(retrans_timer, core_ctx),
5140 [NudTimerId::neighbor()]
5141 );
5142 }
5143 }
5144 ResolutionFailure::Removed => {
5145 NudHandler::flush(core_ctx, bindings_ctx, &FakeLinkDeviceId);
5147 }
5148 }
5149
5150 assert_neighbor_removed_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1);
5151 for observer in observers {
5153 assert_eq!(*observer.lock(), Some(Err(AddressResolutionFailed)));
5154 }
5155 }
5156
5157 #[ip_test(I)]
5158 #[test_case(InitialState::Incomplete, false; "incomplete neighbor")]
5159 #[test_case(InitialState::Reachable, true; "reachable neighbor")]
5160 #[test_case(InitialState::Stale, true; "stale neighbor")]
5161 #[test_case(InitialState::Delay, true; "delay neighbor")]
5162 #[test_case(InitialState::Probe, true; "probe neighbor")]
5163 #[test_case(InitialState::Unreachable, true; "unreachable neighbor")]
5164 fn upper_layer_confirmation<I: TestIpExt>(
5165 initial_state: InitialState,
5166 should_transition_to_reachable: bool,
5167 ) {
5168 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5169 let base_reachable_time = core_ctx.inner.base_reachable_time().get();
5170
5171 let initial = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
5172
5173 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5174
5175 if !should_transition_to_reachable {
5176 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial, None);
5177 return;
5178 }
5179
5180 let now = bindings_ctx.now();
5182 assert_neighbor_state(
5183 &core_ctx,
5184 &mut bindings_ctx,
5185 DynamicNeighborState::Reachable(Reachable {
5186 link_address: LINK_ADDR1,
5187 last_confirmed_at: now,
5188 }),
5189 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
5190 );
5191 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5192 &mut bindings_ctx,
5193 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time)],
5194 );
5195
5196 bindings_ctx.timers.instant.sleep(base_reachable_time / 2);
5200 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5201 let now = bindings_ctx.now();
5202 assert_neighbor_state(
5203 &core_ctx,
5204 &mut bindings_ctx,
5205 DynamicNeighborState::Reachable(Reachable {
5206 link_address: LINK_ADDR1,
5207 last_confirmed_at: now,
5208 }),
5209 None,
5210 );
5211 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5212 &mut bindings_ctx,
5213 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5214 );
5215
5216 assert_eq!(
5219 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5220 [NudTimerId::neighbor()]
5221 );
5222 let now = bindings_ctx.now();
5223 assert_neighbor_state(
5224 &core_ctx,
5225 &mut bindings_ctx,
5226 DynamicNeighborState::Reachable(Reachable {
5227 link_address: LINK_ADDR1,
5228 last_confirmed_at: now - base_reachable_time / 2,
5229 }),
5230 None,
5231 );
5232
5233 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5234 &mut bindings_ctx,
5235 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5236 );
5237
5238 assert_eq!(
5241 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5242 [NudTimerId::neighbor()]
5243 );
5244 assert_neighbor_state(
5245 &core_ctx,
5246 &mut bindings_ctx,
5247 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
5248 Some(ExpectedEvent::Changed),
5249 );
5250 bindings_ctx.timers.assert_no_timers_installed();
5251 }
5252
5253 fn generate_ip_addr<I: Ip>(i: usize) -> SpecifiedAddr<I::Addr> {
5254 I::map_ip_out(
5255 i,
5256 |i| {
5257 let start = u32::from_be_bytes(net_ip_v4!("192.168.0.1").ipv4_bytes());
5258 let bytes = (start + u32::try_from(i).unwrap()).to_be_bytes();
5259 SpecifiedAddr::new(Ipv4Addr::new(bytes)).unwrap()
5260 },
5261 |i| {
5262 let start = u128::from_be_bytes(net_ip_v6!("fe80::1").ipv6_bytes());
5263 let bytes = (start + u128::try_from(i).unwrap()).to_be_bytes();
5264 SpecifiedAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap()
5265 },
5266 )
5267 }
5268
5269 #[ip_test(I)]
5270 fn garbage_collection_retains_static_entries<I: TestIpExt>() {
5271 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5272
5273 for i in 0..GC_THRESHOLD * 2 {
5277 if i % 2 == 0 {
5278 init_stale_neighbor_with_ip(
5279 &mut core_ctx,
5280 &mut bindings_ctx,
5281 generate_ip_addr::<I>(i),
5282 LINK_ADDR1,
5283 );
5284 } else {
5285 init_static_neighbor_with_ip(
5286 &mut core_ctx,
5287 &mut bindings_ctx,
5288 generate_ip_addr::<I>(i),
5289 LINK_ADDR1,
5290 ExpectedEvent::Added,
5291 );
5292 }
5293 }
5294 assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD * 2);
5295
5296 assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5298 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5299 assert_eq!(core_ctx.nud.state.gc_state.is_dirty, false);
5300 for event in bindings_ctx.take_events() {
5301 assert_matches!(event, Event {
5302 device,
5303 addr: _,
5304 kind,
5305 at,
5306 } => {
5307 assert_eq!(kind, EventKind::Removed);
5308 assert_eq!(device, FakeLinkDeviceId);
5309 assert_eq!(at, bindings_ctx.now());
5310 });
5311 }
5312 assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5313 for (_, neighbor) in core_ctx.nud.state.neighbors {
5314 assert_matches!(neighbor, NeighborState::Static(_));
5315 }
5316 }
5317
5318 #[ip_test(I)]
5319 fn garbage_collection_retains_in_use_entries<I: TestIpExt>() {
5320 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5321
5322 for i in 0..GC_THRESHOLD - 1 {
5324 init_static_neighbor_with_ip(
5325 &mut core_ctx,
5326 &mut bindings_ctx,
5327 generate_ip_addr::<I>(i),
5328 LINK_ADDR1,
5329 ExpectedEvent::Added,
5330 );
5331 }
5332
5333 let stale_entry = generate_ip_addr::<I>(GC_THRESHOLD - 1);
5335 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry, LINK_ADDR1);
5336 let reachable_entry = generate_ip_addr::<I>(GC_THRESHOLD);
5338 init_reachable_neighbor_with_ip(
5339 &mut core_ctx,
5340 &mut bindings_ctx,
5341 reachable_entry,
5342 LINK_ADDR1,
5343 );
5344
5345 assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5347 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5348 assert_eq!(core_ctx.nud.state.gc_state.is_dirty, false);
5349 super::testutil::assert_dynamic_neighbor_state(
5350 &mut core_ctx,
5351 FakeLinkDeviceId,
5352 reachable_entry,
5353 DynamicNeighborState::Reachable(Reachable {
5354 link_address: LINK_ADDR1,
5355 last_confirmed_at: bindings_ctx.now(),
5356 }),
5357 );
5358 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry);
5359 }
5360
5361 #[ip_test(I)]
5362 fn is_still_dirty_after_garbage_collection<I: TestIpExt>() {
5363 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5364
5365 for i in 0..GC_THRESHOLD + 1 {
5367 init_stale_neighbor_with_ip(
5368 &mut core_ctx,
5369 &mut bindings_ctx,
5370 generate_ip_addr::<I>(i),
5371 LINK_ADDR1,
5372 );
5373 }
5374
5375 assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5378 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5379 assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5380 assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5381 let events = bindings_ctx.take_events();
5382 let removed_event = assert_matches!(&events[..], [event] => event);
5383 assert_eq!(removed_event.kind, EventKind::Removed);
5384 }
5385
5386 #[ip_test(I)]
5387 fn garbage_collection_triggered_on_new_stale_entry<I: TestIpExt>() {
5388 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5389 core_ctx.nud.state.gc_state.last_gc = Some(bindings_ctx.now());
5391
5392 for i in 0..GC_THRESHOLD {
5394 init_static_neighbor_with_ip(
5395 &mut core_ctx,
5396 &mut bindings_ctx,
5397 generate_ip_addr::<I>(i),
5398 LINK_ADDR1,
5399 ExpectedEvent::Added,
5400 );
5401 }
5402
5403 init_stale_neighbor_with_ip(
5406 &mut core_ctx,
5407 &mut bindings_ctx,
5408 generate_ip_addr::<I>(GC_THRESHOLD + 1),
5409 LINK_ADDR1,
5410 );
5411 let expected_gc_time = bindings_ctx.now() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5412 bindings_ctx
5413 .timers
5414 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5415
5416 bindings_ctx.timers.instant.sleep(ONE_SECOND.get());
5420 init_stale_neighbor_with_ip(
5421 &mut core_ctx,
5422 &mut bindings_ctx,
5423 generate_ip_addr::<I>(GC_THRESHOLD + 2),
5424 LINK_ADDR1,
5425 );
5426 bindings_ctx
5427 .timers
5428 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5429 }
5430
5431 #[ip_test(I)]
5432 fn garbage_collection_triggered_on_transition_to_unreachable<I: TestIpExt>() {
5433 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5434 core_ctx.nud.state.gc_state.last_gc = Some(bindings_ctx.now());
5436
5437 for i in 0..GC_THRESHOLD {
5439 init_static_neighbor_with_ip(
5440 &mut core_ctx,
5441 &mut bindings_ctx,
5442 generate_ip_addr::<I>(i),
5443 LINK_ADDR1,
5444 ExpectedEvent::Added,
5445 );
5446 }
5447 assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5448
5449 init_unreachable_neighbor_with_ip(
5452 &mut core_ctx,
5453 &mut bindings_ctx,
5454 generate_ip_addr::<I>(GC_THRESHOLD),
5455 LINK_ADDR1,
5456 );
5457 let expected_gc_time =
5458 core_ctx.nud.state.gc_state.last_gc.unwrap() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5459 bindings_ctx
5460 .timers
5461 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5462
5463 init_unreachable_neighbor_with_ip(
5466 &mut core_ctx,
5467 &mut bindings_ctx,
5468 generate_ip_addr::<I>(GC_THRESHOLD + 1),
5469 LINK_ADDR1,
5470 );
5471 bindings_ctx
5472 .timers
5473 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5474 }
5475
5476 #[ip_test(I)]
5477 fn garbage_collection_not_triggered_on_new_incomplete_entry<I: TestIpExt>() {
5478 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5479
5480 for i in 0..GC_THRESHOLD {
5482 init_static_neighbor_with_ip(
5483 &mut core_ctx,
5484 &mut bindings_ctx,
5485 generate_ip_addr::<I>(i),
5486 LINK_ADDR1,
5487 ExpectedEvent::Added,
5488 );
5489 }
5490 assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5491
5492 let _: VecDeque<Buf<Vec<u8>>> = init_incomplete_neighbor_with_ip(
5493 &mut core_ctx,
5494 &mut bindings_ctx,
5495 generate_ip_addr::<I>(GC_THRESHOLD),
5496 true,
5497 );
5498 assert_eq!(
5499 bindings_ctx.timers.scheduled_instant(&mut core_ctx.nud.state.timer_heap.gc),
5500 None
5501 );
5502 }
5503
5504 #[ip_test(I)]
5505 fn confirmation_processed_even_if_no_target_link_layer_addr<I: TestIpExt>() {
5506 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5507
5508 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
5510
5511 NudHandler::handle_neighbor_update(
5515 &mut core_ctx,
5516 &mut bindings_ctx,
5517 &FakeLinkDeviceId,
5518 I::LOOKUP_ADDR1,
5519 DynamicNeighborUpdateSource::Confirmation {
5520 link_address: None,
5521 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
5522 },
5523 );
5524 let now = bindings_ctx.now();
5525 assert_neighbor_state(
5526 &core_ctx,
5527 &mut bindings_ctx,
5528 DynamicNeighborState::Reachable(Reachable {
5529 link_address: LINK_ADDR1,
5530 last_confirmed_at: now,
5531 }),
5532 Some(ExpectedEvent::Changed),
5533 );
5534 }
5535
5536 #[ip_test(I)]
5537 #[test_case(InitialState::Stale; "stale")]
5538 #[test_case(InitialState::Reachable; "reachable")]
5539 #[test_case(InitialState::Delay; "delay")]
5540 #[test_case(InitialState::Unreachable; "unreachable")]
5541 fn enter_probe_from_dynamic_state<I: TestIpExt>(initial: InitialState) {
5542 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5543
5544 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial);
5545
5546 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5547 let result = neighbor.enter_probe(
5548 &mut core_ctx.inner.state,
5549 &mut bindings_ctx,
5550 &mut core_ctx.nud.state.timer_heap,
5551 I::LOOKUP_ADDR1,
5552 &FakeLinkDeviceId,
5553 );
5554
5555 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5556 assert_matches!(result, Ok(Some(LINK_ADDR1)));
5557 assert_neighbor_state(
5558 &core_ctx,
5559 &mut bindings_ctx,
5560 DynamicNeighborState::Probe(Probe {
5561 link_address: LINK_ADDR1,
5562 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5563 }),
5564 Some(ExpectedEvent::Changed),
5565 );
5566 }
5567
5568 #[ip_test(I)]
5569 fn enter_probe_from_static_state<I: TestIpExt>() {
5570 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5571
5572 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
5573
5574 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5575 let result = neighbor.enter_probe(
5576 &mut core_ctx.inner.state,
5577 &mut bindings_ctx,
5578 &mut core_ctx.nud.state.timer_heap,
5579 I::LOOKUP_ADDR1,
5580 &FakeLinkDeviceId,
5581 );
5582
5583 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5584 assert_matches!(result, Ok(Some(LINK_ADDR1)));
5585 assert_neighbor_state(
5586 &core_ctx,
5587 &mut bindings_ctx,
5588 DynamicNeighborState::Probe(Probe {
5589 link_address: LINK_ADDR1,
5590 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5591 }),
5592 Some(ExpectedEvent::Changed),
5593 );
5594 }
5595
5596 #[ip_test(I)]
5597 fn enter_probe_from_probe_state<I: TestIpExt>() {
5598 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5599
5600 init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
5601
5602 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5603 let result = neighbor.enter_probe(
5604 &mut core_ctx.inner.state,
5605 &mut bindings_ctx,
5606 &mut core_ctx.nud.state.timer_heap,
5607 I::LOOKUP_ADDR1,
5608 &FakeLinkDeviceId,
5609 );
5610
5611 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5612 assert_matches!(result, Ok(None)); assert_neighbor_state(
5614 &core_ctx,
5615 &mut bindings_ctx,
5616 DynamicNeighborState::Probe(Probe {
5617 link_address: LINK_ADDR1,
5618 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5619 }),
5620 None, );
5622 }
5623
5624 #[ip_test(I)]
5625 fn enter_probe_from_incomplete_state<I: TestIpExt>() {
5626 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5627
5628 let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
5629
5630 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5631 let result = neighbor.enter_probe(
5632 &mut core_ctx.inner.state,
5633 &mut bindings_ctx,
5634 &mut core_ctx.nud.state.timer_heap,
5635 I::LOOKUP_ADDR1,
5636 &FakeLinkDeviceId,
5637 );
5638
5639 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5640 assert_matches!(result, Err(EnterProbeError::LinkAddressUnknown));
5641 assert_neighbor_state(
5642 &core_ctx,
5643 &mut bindings_ctx,
5644 DynamicNeighborState::Incomplete(Incomplete {
5645 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5646 pending_frames: pending_frames
5647 .iter()
5648 .cloned()
5649 .map(|b| (b, FakeTxMetadata::default()))
5650 .collect(),
5651 notifiers: Vec::new(),
5652 _marker: PhantomData,
5653 }),
5654 None, );
5656 }
5657
5658 enum InsertMethod {
5661 ResolveLinkAddr,
5662 InsertStaticEntry,
5663 NeighborUpdate,
5664 SendIpPacket,
5665 }
5666
5667 impl InsertMethod {
5668 fn insert<I: TestIpExt>(
5669 &self,
5670 context: &mut CtxPair<&mut FakeCoreCtxImpl<I>, &mut FakeBindingsCtxImpl<I>>,
5671 link_address: UnicastAddr<FakeLinkAddress>,
5672 ip: SpecifiedAddr<I::Addr>,
5673 expect_err: bool,
5674 ) {
5675 match self {
5676 Self::ResolveLinkAddr => {
5677 let result =
5678 NeighborApi::new(context).resolve_link_addr(&FakeLinkDeviceId, &ip);
5679 let pending = assert_matches!(
5680 result, LinkResolutionResult::Pending(pending) => pending
5681 );
5682 if expect_err {
5683 assert_matches!(
5684 pending.lock().as_ref(),
5685 Some(Err(AddressResolutionFailed))
5686 );
5687 } else {
5688 assert_matches!(pending.lock().as_ref(), None, "should not be notified");
5689 }
5690 }
5691 Self::InsertStaticEntry => {
5692 let result = NeighborApi::new(context).insert_static_entry(
5693 &FakeLinkDeviceId,
5694 *ip,
5695 link_address,
5696 );
5697 if expect_err {
5698 assert_eq!(result, Err(StaticNeighborInsertionError::TableFull))
5699 } else {
5700 assert_eq!(result, Ok(()))
5701 }
5702 }
5703 Self::NeighborUpdate => {
5704 let CtxPair { core_ctx, bindings_ctx } = context;
5705 NudHandler::handle_neighbor_update(
5706 *core_ctx,
5707 *bindings_ctx,
5708 &FakeLinkDeviceId,
5709 ip,
5710 DynamicNeighborUpdateSource::Probe { link_address },
5711 );
5712 }
5715 Self::SendIpPacket => {
5716 let CtxPair { core_ctx, bindings_ctx } = context;
5717 let packet = Buf::new([0; 10], ..);
5718 let result = NudHandler::send_ip_packet_to_neighbor(
5719 *core_ctx,
5720 *bindings_ctx,
5721 &FakeLinkDeviceId,
5722 ip,
5723 packet,
5724 FakeTxMetadata::default(),
5725 );
5726 if expect_err {
5727 assert_matches!(
5728 result,
5729 Err(ErrorAndSerializer {error, ..})
5730 if error == SendFrameErrorReason::AddressResolutionFailed
5731 );
5732 } else {
5733 assert_matches!(result, Ok(()));
5734 }
5735 }
5736 }
5737 }
5738 }
5739
5740 #[ip_test(I)]
5742 #[test_case(InsertMethod::ResolveLinkAddr; "resolve_link_addr")]
5743 #[test_case(InsertMethod::InsertStaticEntry; "insert_static_entry")]
5744 #[test_case(InsertMethod::NeighborUpdate; "neighbor_update")]
5745 #[test_case(InsertMethod::SendIpPacket; "send_ip_packet")]
5746 fn neighbor_table_max_entries_enforced<I: TestIpExt>(insert_method: InsertMethod) {
5747 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5748 for i in 0..MAX_ENTRIES {
5749 init_static_neighbor_with_ip(
5751 &mut core_ctx,
5752 &mut bindings_ctx,
5753 generate_ip_addr::<I>(i),
5754 LINK_ADDR1,
5755 ExpectedEvent::Added,
5756 );
5757 assert_eq!(core_ctx.nud.state.neighbors.len(), i + 1);
5758 }
5759
5760 insert_method.insert(
5762 &mut CtxPair { core_ctx: &mut core_ctx, bindings_ctx: &mut bindings_ctx },
5763 LINK_ADDR1,
5764 generate_ip_addr::<I>(MAX_ENTRIES),
5765 true, );
5767 assert_eq!(bindings_ctx.take_events(), []);
5768 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5769 }
5770
5771 #[ip_test(I)]
5774 #[test_case(InsertMethod::ResolveLinkAddr; "resolve_link_addr")]
5775 #[test_case(InsertMethod::InsertStaticEntry; "insert_static_entry")]
5776 #[test_case(InsertMethod::NeighborUpdate; "neighbor_update")]
5777 #[test_case(InsertMethod::SendIpPacket; "send_ip_packet")]
5778 fn insert_new_entry_may_collect_garbage<I: TestIpExt>(insert_method: InsertMethod) {
5779 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5780
5781 for i in 0..MAX_ENTRIES {
5782 init_stale_neighbor_with_ip(
5784 &mut core_ctx,
5785 &mut bindings_ctx,
5786 generate_ip_addr::<I>(i),
5787 LINK_ADDR1,
5788 );
5789 assert_eq!(core_ctx.nud.state.neighbors.len(), i + 1);
5790 }
5791
5792 insert_method.insert(
5795 &mut CtxPair { core_ctx: &mut core_ctx, bindings_ctx: &mut bindings_ctx },
5796 LINK_ADDR1,
5797 generate_ip_addr::<I>(MAX_ENTRIES),
5798 false, );
5800 assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD + 1);
5801 let mut events = bindings_ctx.take_events();
5802 let add_event = events.pop().expect("should have added event");
5804 assert_eq!(add_event.addr, generate_ip_addr::<I>(MAX_ENTRIES));
5805 assert_matches!(add_event.kind, EventKind::Added(_));
5806 for _ in 0..(MAX_ENTRIES - GC_THRESHOLD) {
5807 assert_matches!(events.pop(), Some(Event{kind, ..}) if kind == EventKind::Removed);
5808 }
5809 assert_matches!(&events[..], []);
5810 }
5811}