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<
1652 Mutex<
1653 Option<Result<UnicastAddr<D::Address>, AddressResolutionFailed>>,
1654 netstack3_base::sync::lock_impl::RawMutex,
1655 >,
1656 >,
1657 );
1658
1659 impl<D: LinkDevice> LinkResolutionNotifier<D> for FakeLinkResolutionNotifier<D> {
1660 type Observer = Arc<
1661 Mutex<
1662 Option<Result<UnicastAddr<D::Address>, AddressResolutionFailed>>,
1663 netstack3_base::sync::lock_impl::RawMutex,
1664 >,
1665 >;
1666
1667 fn new() -> (Self, Self::Observer) {
1668 let inner = Arc::new(Mutex::new(None));
1669 (Self(inner.clone()), inner)
1670 }
1671
1672 fn notify(self, result: Result<UnicastAddr<D::Address>, AddressResolutionFailed>) {
1673 let Self(inner) = self;
1674 let mut inner = inner.lock();
1675 assert_eq!(*inner, None, "resolved link address was set more than once");
1676 *inner = Some(result);
1677 }
1678 }
1679
1680 impl<S, Meta, DeviceId> UseDelegateNudContext for FakeCoreCtx<S, Meta, DeviceId> where
1681 S: UseDelegateNudContext
1682 {
1683 }
1684 impl<I: Ip, S, Meta, DeviceId> DelegateNudContext<I> for FakeCoreCtx<S, Meta, DeviceId>
1685 where
1686 S: DelegateNudContext<I>,
1687 {
1688 type Delegate<T> = S::Delegate<T>;
1689 }
1690}
1691
1692#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1693enum NudEvent {
1694 RetransmitMulticastProbe,
1695 ReachableTime,
1696 DelayFirstProbe,
1697 RetransmitUnicastProbe,
1698}
1699
1700#[derive(GenericOverIp, Copy, Clone, Debug, Eq, PartialEq, Hash)]
1702#[generic_over_ip(I, Ip)]
1703pub struct NudTimerId<I: Ip, L: LinkDevice, D: WeakDeviceIdentifier> {
1704 device_id: D,
1705 timer_type: NudTimerType,
1706 _marker: PhantomData<(I, L)>,
1707}
1708
1709#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1710enum NudTimerType {
1711 Neighbor,
1712 GarbageCollection,
1713}
1714
1715#[derive(Debug)]
1717pub(crate) struct TimerHeap<I: Ip, BT: TimerBindingsTypes + InstantBindingsTypes> {
1718 gc: BT::Timer,
1719 neighbor: LocalTimerHeap<SpecifiedAddr<I::Addr>, NudEvent, BT>,
1720}
1721
1722impl<I: Ip, BC: TimerContext> TimerHeap<I, BC> {
1723 fn new<
1724 DeviceId: WeakDeviceIdentifier,
1725 L: LinkDevice,
1726 CC: CoreTimerContext<NudTimerId<I, L, DeviceId>, BC>,
1727 >(
1728 bindings_ctx: &mut BC,
1729 device_id: DeviceId,
1730 ) -> Self {
1731 Self {
1732 neighbor: LocalTimerHeap::new_with_context::<_, CC>(
1733 bindings_ctx,
1734 NudTimerId {
1735 device_id: device_id.clone(),
1736 timer_type: NudTimerType::Neighbor,
1737 _marker: PhantomData,
1738 },
1739 ),
1740 gc: CC::new_timer(
1741 bindings_ctx,
1742 NudTimerId {
1743 device_id,
1744 timer_type: NudTimerType::GarbageCollection,
1745 _marker: PhantomData,
1746 },
1747 ),
1748 }
1749 }
1750
1751 fn schedule_neighbor(
1752 &mut self,
1753 bindings_ctx: &mut BC,
1754 after: NonZeroDuration,
1755 neighbor: SpecifiedAddr<I::Addr>,
1756 event: NudEvent,
1757 ) {
1758 let Self { neighbor: heap, gc: _ } = self;
1759 assert_eq!(heap.schedule_after(bindings_ctx, neighbor, event, after.get()), None);
1760 }
1761
1762 fn schedule_neighbor_at(
1763 &mut self,
1764 bindings_ctx: &mut BC,
1765 at: BC::Instant,
1766 neighbor: SpecifiedAddr<I::Addr>,
1767 event: NudEvent,
1768 ) {
1769 let Self { neighbor: heap, gc: _ } = self;
1770 assert_eq!(heap.schedule_instant(bindings_ctx, neighbor, event, at), None);
1771 }
1772
1773 fn cancel_neighbor(
1775 &mut self,
1776 bindings_ctx: &mut BC,
1777 neighbor: SpecifiedAddr<I::Addr>,
1778 ) -> Option<NudEvent> {
1779 let Self { neighbor: heap, gc: _ } = self;
1780 heap.cancel(bindings_ctx, &neighbor).map(|(_instant, v)| v)
1781 }
1782
1783 fn pop_neighbor(
1784 &mut self,
1785 bindings_ctx: &mut BC,
1786 ) -> Option<(SpecifiedAddr<I::Addr>, NudEvent)> {
1787 let Self { neighbor: heap, gc: _ } = self;
1788 heap.pop(bindings_ctx)
1789 }
1790
1791 fn maybe_schedule_gc(
1794 &mut self,
1795 bindings_ctx: &mut BC,
1796 num_entries: usize,
1797 gc_state: &mut GarbageCollectionState<BC::Instant>,
1798 ) {
1799 let GarbageCollectionState { is_dirty, last_gc } = gc_state;
1800 *is_dirty = true;
1801 let Self { gc, neighbor: _ } = self;
1802 if num_entries > GC_THRESHOLD && bindings_ctx.scheduled_instant(gc).is_none() {
1803 let instant = if let Some(last_gc) = last_gc {
1804 last_gc.panicking_add(MIN_GARBAGE_COLLECTION_INTERVAL.get())
1805 } else {
1806 bindings_ctx.now()
1807 };
1808 assert_eq!(bindings_ctx.schedule_timer_instant(instant, gc), None);
1812 }
1813 }
1814
1815 fn cancel_gc(&mut self, bindings_ctx: &mut BC) {
1816 let Self { gc, neighbor: _ } = self;
1817 let _: Option<BC::Instant> = bindings_ctx.cancel_timer(gc);
1818 }
1819}
1820
1821#[derive(Debug)]
1823pub struct GarbageCollectionState<Instant> {
1824 last_gc: Option<Instant>,
1826 is_dirty: bool,
1829}
1830
1831#[derive(Debug)]
1833pub struct NudState<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> {
1834 neighbors: HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>>,
1836 gc_state: GarbageCollectionState<BT::Instant>,
1837 timer_heap: TimerHeap<I, BT>,
1838}
1839
1840impl<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> NudState<I, D, BT> {
1841 #[cfg(any(test, feature = "testutils"))]
1843 pub fn neighbors(&self) -> &HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>> {
1844 &self.neighbors
1845 }
1846}
1847
1848impl<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D> + TimerContext> NudState<I, D, BC> {
1849 pub fn new<
1851 DeviceId: WeakDeviceIdentifier,
1852 CC: CoreTimerContext<NudTimerId<I, D, DeviceId>, BC>,
1853 >(
1854 bindings_ctx: &mut BC,
1855 device_id: DeviceId,
1856 ) -> Self {
1857 Self {
1858 neighbors: Default::default(),
1859 gc_state: GarbageCollectionState { last_gc: None, is_dirty: false },
1860 timer_heap: TimerHeap::new::<_, _, CC>(bindings_ctx, device_id),
1861 }
1862 }
1863}
1864
1865pub trait NudBindingsContext<I: Ip, D: LinkDevice, DeviceId>:
1867 TimerContext
1868 + LinkResolutionContext<D>
1869 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1870 + NudBindingsTypes<D>
1871{
1872}
1873
1874impl<
1875 I: Ip,
1876 D: LinkDevice,
1877 DeviceId,
1878 BC: TimerContext
1879 + LinkResolutionContext<D>
1880 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1881 + NudBindingsTypes<D>,
1882> NudBindingsContext<I, D, DeviceId> for BC
1883{
1884}
1885
1886pub trait NudBindingsTypes<D: LinkDevice>:
1888 LinkResolutionContext<D> + InstantBindingsTypes + TimerBindingsTypes + TxMetadataBindingsTypes
1889{
1890}
1891
1892impl<BT, D> NudBindingsTypes<D> for BT
1893where
1894 D: LinkDevice,
1895 BT: LinkResolutionContext<D>
1896 + InstantBindingsTypes
1897 + TimerBindingsTypes
1898 + TxMetadataBindingsTypes,
1899{
1900}
1901
1902pub trait LinkResolutionContext<D: LinkDevice> {
1904 type Notifier: LinkResolutionNotifier<D>;
1907}
1908
1909pub trait LinkResolutionNotifier<D: LinkDevice>: Debug + Sized + Send {
1912 type Observer;
1915
1916 fn new() -> (Self, Self::Observer);
1918
1919 fn notify(self, result: Result<UnicastAddr<D::Address>, AddressResolutionFailed>);
1922}
1923
1924pub trait NudContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
1926 type ConfigCtx<'a>: NudConfigContext<I>;
1928 type SenderCtx<'a>: NudSenderContext<I, D, BC, DeviceId = Self::DeviceId>;
1930
1931 fn with_nud_state_mut_and_sender_ctx<
1934 O,
1935 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1936 >(
1937 &mut self,
1938 device_id: &Self::DeviceId,
1939 cb: F,
1940 ) -> O;
1941
1942 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1945 &mut self,
1946 device_id: &Self::DeviceId,
1947 cb: F,
1948 ) -> O;
1949
1950 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1952 &mut self,
1953 device_id: &Self::DeviceId,
1954 cb: F,
1955 ) -> O;
1956
1957 fn send_neighbor_solicitation(
1962 &mut self,
1963 bindings_ctx: &mut BC,
1964 device_id: &Self::DeviceId,
1965 lookup_addr: SpecifiedAddr<I::Addr>,
1966 remote_link_addr: Option<UnicastAddr<D::Address>>,
1967 );
1968}
1969
1970pub trait UseDelegateNudContext {}
1973
1974pub trait DelegateNudContext<I: Ip>: UseDelegateNudContext + Sized {
1980 type Delegate<T>: ref_cast::RefCast<From = T>;
1982 fn wrap(&mut self) -> &mut Self::Delegate<Self> {
1984 <Self::Delegate<Self> as ref_cast::RefCast>::ref_cast_mut(self)
1985 }
1986}
1987
1988impl<I, D, BC, CC> NudContext<I, D, BC> for CC
1989where
1990 I: Ip,
1991 D: LinkDevice,
1992 BC: NudBindingsTypes<D>,
1993 CC: DelegateNudContext<I, Delegate<CC>: NudContext<I, D, BC, DeviceId = CC::DeviceId>>
1994 + UseDelegateNudContext
1997 + DeviceIdContext<D>,
1998{
1999 type ConfigCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::ConfigCtx<'a>;
2000 type SenderCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::SenderCtx<'a>;
2001 fn with_nud_state_mut_and_sender_ctx<
2002 O,
2003 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
2004 >(
2005 &mut self,
2006 device_id: &Self::DeviceId,
2007 cb: F,
2008 ) -> O {
2009 self.wrap().with_nud_state_mut_and_sender_ctx(device_id, cb)
2010 }
2011
2012 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
2013 &mut self,
2014 device_id: &Self::DeviceId,
2015 cb: F,
2016 ) -> O {
2017 self.wrap().with_nud_state_mut(device_id, cb)
2018 }
2019 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
2020 &mut self,
2021 device_id: &Self::DeviceId,
2022 cb: F,
2023 ) -> O {
2024 self.wrap().with_nud_state(device_id, cb)
2025 }
2026 fn send_neighbor_solicitation(
2027 &mut self,
2028 bindings_ctx: &mut BC,
2029 device_id: &Self::DeviceId,
2030 lookup_addr: SpecifiedAddr<I::Addr>,
2031 remote_link_addr: Option<UnicastAddr<D::Address>>,
2032 ) {
2033 self.wrap().send_neighbor_solicitation(
2034 bindings_ctx,
2035 device_id,
2036 lookup_addr,
2037 remote_link_addr,
2038 )
2039 }
2040}
2041
2042pub trait NudIcmpIpExt: packet_formats::ip::IpExt {
2044 type Metadata;
2047
2048 fn extract_metadata<B: SplitByteSlice>(packet: &Self::Packet<B>) -> Self::Metadata;
2050}
2051
2052impl NudIcmpIpExt for Ipv4 {
2053 type Metadata = Ipv4FragmentType;
2054
2055 fn extract_metadata<B: SplitByteSlice>(packet: &Ipv4Packet<B>) -> Self::Metadata {
2056 packet.fragment_type()
2057 }
2058}
2059
2060impl NudIcmpIpExt for Ipv6 {
2061 type Metadata = ();
2062
2063 fn extract_metadata<B: SplitByteSlice>(_packet: &Ipv6Packet<B>) -> Self::Metadata {
2064 ()
2065 }
2066}
2067
2068pub trait NudIcmpContext<I: NudIcmpIpExt, D: LinkDevice, BC>: DeviceIdContext<D> {
2071 fn send_icmp_dest_unreachable(
2078 &mut self,
2079 bindings_ctx: &mut BC,
2080 frame: Buf<Vec<u8>>,
2081 device_id: Option<&Self::DeviceId>,
2082 original_src_ip: SocketIpAddr<I::Addr>,
2083 original_dst_ip: SocketIpAddr<I::Addr>,
2084 header_len: usize,
2085 proto: I::Proto,
2086 metadata: I::Metadata,
2087 );
2088}
2089
2090#[derive(Clone, Debug)]
2092pub struct NudUserConfig {
2093 pub max_unicast_solicitations: NonZeroU16,
2098 pub max_multicast_solicitations: NonZeroU16,
2103 pub base_reachable_time: NonZeroDuration,
2109 pub retrans_timer: NonZeroDuration,
2115}
2116
2117impl Default for NudUserConfig {
2118 fn default() -> Self {
2119 NudUserConfig {
2120 max_unicast_solicitations: DEFAULT_MAX_UNICAST_SOLICIT,
2121 max_multicast_solicitations: DEFAULT_MAX_MULTICAST_SOLICIT,
2122 base_reachable_time: DEFAULT_BASE_REACHABLE_TIME,
2123 retrans_timer: DEFAULT_RETRANS_TIMER,
2124 }
2125 }
2126}
2127
2128#[allow(missing_docs)]
2132#[derive(Clone, Debug, Eq, PartialEq, Default)]
2133pub struct NudUserConfigUpdate {
2134 pub max_unicast_solicitations: Option<NonZeroU16>,
2135 pub max_multicast_solicitations: Option<NonZeroU16>,
2136 pub base_reachable_time: Option<NonZeroDuration>,
2137 pub retrans_timer: Option<NonZeroDuration>,
2138}
2139
2140impl NudUserConfigUpdate {
2141 pub fn apply_and_take_previous(mut self, config: &mut NudUserConfig) -> Self {
2144 fn swap_if_set<T>(opt: &mut Option<T>, target: &mut T) {
2145 if let Some(opt) = opt.as_mut() {
2146 core::mem::swap(opt, target)
2147 }
2148 }
2149 let Self {
2150 max_unicast_solicitations,
2151 max_multicast_solicitations,
2152 base_reachable_time,
2153 retrans_timer,
2154 } = &mut self;
2155 swap_if_set(max_unicast_solicitations, &mut config.max_unicast_solicitations);
2156 swap_if_set(max_multicast_solicitations, &mut config.max_multicast_solicitations);
2157 swap_if_set(base_reachable_time, &mut config.base_reachable_time);
2158 swap_if_set(retrans_timer, &mut config.retrans_timer);
2159
2160 self
2161 }
2162}
2163
2164pub trait NudConfigContext<I: Ip> {
2167 fn retransmit_timeout(&mut self) -> NonZeroDuration;
2174
2175 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O;
2177
2178 fn max_unicast_solicit(&mut self) -> NonZeroU16 {
2180 self.with_nud_user_config(|NudUserConfig { max_unicast_solicitations, .. }| {
2181 *max_unicast_solicitations
2182 })
2183 }
2184
2185 fn max_multicast_solicit(&mut self) -> NonZeroU16 {
2187 self.with_nud_user_config(|NudUserConfig { max_multicast_solicitations, .. }| {
2188 *max_multicast_solicitations
2189 })
2190 }
2191
2192 fn base_reachable_time(&mut self) -> NonZeroDuration {
2195 self.with_nud_user_config(|NudUserConfig { base_reachable_time, .. }| *base_reachable_time)
2196 }
2197
2198 fn override_lock_time(&mut self) -> Duration;
2201}
2202
2203pub trait NudSenderContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>:
2206 NudConfigContext<I> + DeviceIdContext<D>
2207{
2208 fn send_ip_packet_to_neighbor_link_addr<S>(
2210 &mut self,
2211 bindings_ctx: &mut BC,
2212 neighbor_link_addr: UnicastAddr<D::Address>,
2213 body: S,
2214 meta: BC::TxMetadata,
2215 ) -> Result<(), SendFrameError<S>>
2216 where
2217 S: NetworkSerializer,
2218 S::Buffer: BufferMut;
2219}
2220
2221pub trait NudIpHandler<I: Ip, BC>: DeviceIdContext<AnyDevice> {
2223 fn handle_neighbor_probe(
2228 &mut self,
2229 bindings_ctx: &mut BC,
2230 device_id: &Self::DeviceId,
2231 neighbor: SpecifiedAddr<I::Addr>,
2232 link_addr: &[u8],
2233 );
2234
2235 fn handle_neighbor_confirmation(
2239 &mut self,
2240 bindings_ctx: &mut BC,
2241 device_id: &Self::DeviceId,
2242 neighbor: SpecifiedAddr<I::Addr>,
2243 link_addr: Option<&[u8]>,
2244 flags: ConfirmationFlags,
2245 );
2246
2247 fn flush_neighbor_table(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2249}
2250
2251#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2253pub enum LinkResolutionResult<A, Observer> {
2254 Resolved(A),
2256 Pending(Observer),
2258}
2259
2260pub trait NudHandler<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
2262 fn handle_neighbor_update(
2265 &mut self,
2266 bindings_ctx: &mut BC,
2267 device_id: &Self::DeviceId,
2268 neighbor: SpecifiedAddr<I::Addr>,
2273 source: DynamicNeighborUpdateSource<D::Address>,
2274 );
2275
2276 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2278
2279 fn send_ip_packet_to_neighbor<S>(
2284 &mut self,
2285 bindings_ctx: &mut BC,
2286 device_id: &Self::DeviceId,
2287 neighbor: SpecifiedAddr<I::Addr>,
2288 body: S,
2289 meta: BC::TxMetadata,
2290 ) -> Result<(), SendFrameError<S>>
2291 where
2292 S: NetworkSerializer,
2293 S::Buffer: BufferMut;
2294}
2295
2296enum TransmitProbe<A> {
2297 Multicast,
2298 Unicast(A),
2299}
2300
2301impl<
2302 I: NudIcmpIpExt,
2303 D: LinkDevice,
2304 BC: NudBindingsContext<I, D, CC::DeviceId>,
2305 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2306> HandleableTimer<CC, BC> for NudTimerId<I, D, CC::WeakDeviceId>
2307{
2308 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
2309 let Self { device_id, timer_type, _marker: PhantomData } = self;
2310 let Some(device_id) = device_id.upgrade() else {
2311 return;
2312 };
2313 match timer_type {
2314 NudTimerType::Neighbor => handle_neighbor_timer(core_ctx, bindings_ctx, device_id),
2315 NudTimerType::GarbageCollection => collect_garbage(core_ctx, bindings_ctx, device_id),
2316 }
2317 }
2318}
2319
2320fn handle_neighbor_timer<I, D, CC, BC>(
2321 core_ctx: &mut CC,
2322 bindings_ctx: &mut BC,
2323 device_id: CC::DeviceId,
2324) where
2325 I: NudIcmpIpExt,
2326 D: LinkDevice,
2327 BC: NudBindingsContext<I, D, CC::DeviceId>,
2328 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2329{
2330 enum Action<L, A, M> {
2331 TransmitProbe { probe: TransmitProbe<L>, to: A },
2332 SendIcmpDestUnreachable(VecDeque<(Buf<Vec<u8>>, M)>),
2333 }
2334 let action = core_ctx.with_nud_state_mut(
2335 &device_id,
2336 |NudState { neighbors, gc_state, timer_heap }, core_ctx| {
2337 let (lookup_addr, event) = timer_heap.pop_neighbor(bindings_ctx)?;
2338 let num_entries = neighbors.len();
2339 let mut entry = match neighbors.entry(lookup_addr) {
2340 Entry::Occupied(entry) => entry,
2341 Entry::Vacant(_) => panic!("timer fired for invalid entry"),
2342 };
2343
2344 match entry.get_mut() {
2345 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)) => {
2346 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2347
2348 if incomplete.schedule_timer_if_should_retransmit(
2349 core_ctx,
2350 bindings_ctx,
2351 timer_heap,
2352 lookup_addr,
2353 ) {
2354 Some(Action::TransmitProbe {
2355 probe: TransmitProbe::Multicast,
2356 to: lookup_addr,
2357 })
2358 } else {
2359 debug!("neighbor resolution failed for {lookup_addr}; removing entry");
2367 let Incomplete {
2368 transmit_counter: _,
2369 ref mut pending_frames,
2370 notifiers: _,
2371 _marker,
2372 } = assert_matches!(
2373 entry.remove(),
2374 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete))
2375 => incomplete
2376 );
2377 let pending_frames = core::mem::take(pending_frames);
2378 bindings_ctx.on_event(Event::removed(
2379 &device_id,
2380 lookup_addr,
2381 bindings_ctx.now(),
2382 ));
2383 Some(Action::SendIcmpDestUnreachable(pending_frames))
2384 }
2385 }
2386 NeighborState::Dynamic(DynamicNeighborState::Probe(probe)) => {
2387 assert_eq!(event, NudEvent::RetransmitUnicastProbe);
2388
2389 let Probe { link_address, transmit_counter: _ } = probe;
2390 let link_address = *link_address;
2391 if probe.schedule_timer_if_should_retransmit(
2392 core_ctx,
2393 bindings_ctx,
2394 timer_heap,
2395 lookup_addr,
2396 ) {
2397 Some(Action::TransmitProbe {
2398 probe: TransmitProbe::Unicast(link_address),
2399 to: lookup_addr,
2400 })
2401 } else {
2402 let unreachable = probe.enter_unreachable(
2403 bindings_ctx,
2404 timer_heap,
2405 num_entries,
2406 gc_state,
2407 );
2408 *entry.get_mut() =
2409 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable));
2410 let event_state = entry.get_mut().to_event_state();
2411 let event = Event::changed(
2412 &device_id,
2413 event_state,
2414 lookup_addr,
2415 bindings_ctx.now(),
2416 );
2417 bindings_ctx.on_event(event);
2418 None
2419 }
2420 }
2421 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable)) => {
2422 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2423 unreachable
2424 .handle_timer(core_ctx, bindings_ctx, timer_heap, &device_id, lookup_addr)
2425 .map(|probe| Action::TransmitProbe { probe, to: lookup_addr })
2426 }
2427 NeighborState::Dynamic(DynamicNeighborState::Reachable(Reachable {
2428 link_address,
2429 last_confirmed_at,
2430 })) => {
2431 assert_eq!(event, NudEvent::ReachableTime);
2432 let link_address = *link_address;
2433
2434 let expiration =
2435 last_confirmed_at.saturating_add(core_ctx.base_reachable_time().get());
2436 if expiration > bindings_ctx.now() {
2437 timer_heap.schedule_neighbor_at(
2438 bindings_ctx,
2439 expiration,
2440 lookup_addr,
2441 NudEvent::ReachableTime,
2442 );
2443 } else {
2444 *entry.get_mut() =
2452 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2453 link_address,
2454 }));
2455 let event_state = entry.get_mut().to_event_state();
2456 let event = Event::changed(
2457 &device_id,
2458 event_state,
2459 lookup_addr,
2460 bindings_ctx.now(),
2461 );
2462 bindings_ctx.on_event(event);
2463
2464 timer_heap.maybe_schedule_gc(bindings_ctx, num_entries, gc_state);
2468 }
2469
2470 None
2471 }
2472 NeighborState::Dynamic(DynamicNeighborState::Delay(delay)) => {
2473 assert_eq!(event, NudEvent::DelayFirstProbe);
2474
2475 let probe @ Probe { link_address, transmit_counter: _ } =
2482 delay.enter_probe(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2483 *entry.get_mut() = NeighborState::Dynamic(DynamicNeighborState::Probe(probe));
2484 let event_state = entry.get_mut().to_event_state();
2485 bindings_ctx.on_event(Event::changed(
2486 &device_id,
2487 event_state,
2488 lookup_addr,
2489 bindings_ctx.now(),
2490 ));
2491
2492 Some(Action::TransmitProbe {
2493 probe: TransmitProbe::Unicast(link_address),
2494 to: lookup_addr,
2495 })
2496 }
2497 state @ (NeighborState::Static(_)
2498 | NeighborState::Dynamic(DynamicNeighborState::Stale(_))) => {
2499 panic!("timer unexpectedly fired in state {state:?}")
2500 }
2501 }
2502 },
2503 );
2504
2505 match action {
2506 Some(Action::SendIcmpDestUnreachable(mut pending_frames)) => {
2507 for (mut frame, meta) in pending_frames.drain(..) {
2508 core::mem::drop(meta);
2511
2512 let Some((packet, original_src_ip, original_dst_ip)) = frame
2516 .parse_mut::<I::Packet<_>>()
2517 .map_err(|e| {
2518 warn!("not sending ICMP dest unreachable due to parsing error: {:?}", e);
2519 })
2520 .ok()
2521 .and_then(|packet| {
2522 let original_src_ip = SocketIpAddr::new(packet.src_ip())?;
2523 let original_dst_ip = SocketIpAddr::new(packet.dst_ip())?;
2524 Some((packet, original_src_ip, original_dst_ip))
2525 })
2526 .or_else(|| {
2527 core_ctx.counters().icmp_dest_unreachable_dropped.increment();
2528 None
2529 })
2530 else {
2531 continue;
2532 };
2533 let header_metadata = I::extract_metadata(&packet);
2534 let header_len = packet.parse_metadata().header_len();
2535 let proto = packet.proto();
2536 let metadata = packet.parse_metadata();
2537 core::mem::drop(packet);
2538 frame.undo_parse(metadata);
2539 core_ctx.send_icmp_dest_unreachable(
2540 bindings_ctx,
2541 frame,
2542 original_src_ip.as_ref().must_have_zone().then_some(&device_id),
2554 original_src_ip,
2555 original_dst_ip,
2556 header_len,
2557 proto,
2558 header_metadata,
2559 );
2560 }
2561 }
2562 Some(Action::TransmitProbe { probe, to }) => {
2563 let remote_link_addr = match probe {
2564 TransmitProbe::Multicast => None,
2565 TransmitProbe::Unicast(link_addr) => Some(link_addr),
2566 };
2567 core_ctx.send_neighbor_solicitation(bindings_ctx, &device_id, to, remote_link_addr);
2568 }
2569 None => {}
2570 }
2571}
2572
2573impl<I: Ip, D: LinkDevice, BC: NudBindingsContext<I, D, CC::DeviceId>, CC: NudContext<I, D, BC>>
2574 NudHandler<I, D, BC> for CC
2575{
2576 fn handle_neighbor_update(
2577 &mut self,
2578 bindings_ctx: &mut BC,
2579 device_id: &CC::DeviceId,
2580 neighbor: SpecifiedAddr<I::Addr>,
2581 source: DynamicNeighborUpdateSource<D::Address>,
2582 ) {
2583 debug!("received neighbor {:?} from {}", source, neighbor);
2584 self.with_nud_state_mut_and_sender_ctx(
2585 device_id,
2586 |NudState { neighbors, gc_state, timer_heap }, core_ctx| {
2587 let num_entries = neighbors.len();
2588 match neighbors.get_mut(&neighbor) {
2589 None => match source {
2590 DynamicNeighborUpdateSource::Probe { link_address } => {
2591 let result = insert_new_entry(
2599 neighbors,
2600 gc_state,
2601 timer_heap,
2602 bindings_ctx,
2603 device_id,
2604 neighbor,
2605 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2606 link_address,
2607 })),
2608 );
2609 match result {
2610 Ok(_entry) => {}
2611 Err(TableFullError { entry }) => {
2612 debug!("Neighbor table full; failed to insert {entry:?}");
2613 return;
2614 }
2615 }
2616
2617 timer_heap.maybe_schedule_gc(bindings_ctx, neighbors.len(), gc_state);
2620 }
2621 DynamicNeighborUpdateSource::Confirmation { .. } => {}
2630 },
2631 Some(entry) => match entry {
2632 NeighborState::Dynamic(e) => match source {
2633 DynamicNeighborUpdateSource::Probe { link_address } => e.handle_probe(
2634 core_ctx,
2635 bindings_ctx,
2636 timer_heap,
2637 device_id,
2638 neighbor,
2639 link_address,
2640 num_entries,
2641 gc_state,
2642 ),
2643 DynamicNeighborUpdateSource::Confirmation { link_address, flags } => e
2644 .handle_confirmation(
2645 core_ctx,
2646 bindings_ctx,
2647 timer_heap,
2648 device_id,
2649 neighbor,
2650 link_address,
2651 flags,
2652 num_entries,
2653 gc_state,
2654 ),
2655 },
2656 NeighborState::Static(_) => {}
2657 },
2658 }
2659 },
2660 );
2661 }
2662
2663 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
2664 self.with_nud_state_mut(
2665 device_id,
2666 |NudState { neighbors, gc_state: _, timer_heap }, _config| {
2667 neighbors.drain().for_each(|(neighbor, state)| {
2668 match state {
2669 NeighborState::Dynamic(mut entry) => {
2670 entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
2671 }
2672 NeighborState::Static(_) => {}
2673 }
2674 bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
2675 });
2676 },
2677 );
2678 }
2679
2680 fn send_ip_packet_to_neighbor<S>(
2681 &mut self,
2682 bindings_ctx: &mut BC,
2683 device_id: &Self::DeviceId,
2684 lookup_addr: SpecifiedAddr<I::Addr>,
2685 body: S,
2686 meta: BC::TxMetadata,
2687 ) -> Result<(), SendFrameError<S>>
2688 where
2689 S: NetworkSerializer,
2690 S::Buffer: BufferMut,
2691 {
2692 let do_multicast_solicit = self.with_nud_state_mut_and_sender_ctx(
2693 device_id,
2694 |NudState { neighbors, gc_state, timer_heap },
2695 core_ctx|
2696 -> Result<_, SendFrameError<S>> {
2697 match neighbors.get_mut(&lookup_addr) {
2698 None => {
2699 let incomplete =
2700 Incomplete::new(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2701 let result = insert_new_entry(
2702 neighbors,
2703 gc_state,
2704 timer_heap,
2705 bindings_ctx,
2706 device_id,
2707 lookup_addr,
2708 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)),
2709 );
2710 match result {
2711 Err(TableFullError { entry }) => {
2712 debug!("Neighbor table full; failed to insert {entry:?}");
2713 return Err(ErrorAndSerializer {
2714 serializer: body,
2715 error: SendFrameErrorReason::AddressResolutionFailed,
2716 });
2717 }
2718 Ok(mut entry) => {
2719 let dynamic = assert_matches!(
2720 entry.get_mut(),
2721 NeighborState::Dynamic(d) => d,
2722 "newly inserted entry must still be dynamic"
2723 );
2724 let incomplete = assert_matches!(
2725 dynamic,
2726 DynamicNeighborState::Incomplete(i) => i,
2727 "newly inserted entry must still be incomplete"
2728 );
2729 match incomplete.queue_packet(body, meta) {
2731 Ok(()) => Ok(true),
2732 Err(e) => {
2733 dynamic.cancel_timer(bindings_ctx, timer_heap, lookup_addr);
2734 let _entry = entry.remove();
2735 Err(e.err_into())
2736 }
2737 }
2738 }
2739 }
2740 }
2741 Some(entry) => {
2742 match entry {
2743 NeighborState::Static(link_address) => {
2744 core_ctx.send_ip_packet_to_neighbor_link_addr(
2752 bindings_ctx,
2753 *link_address,
2754 body,
2755 meta,
2756 )?;
2757
2758 Ok(false)
2759 }
2760 NeighborState::Dynamic(e) => {
2761 let do_multicast_solicit = e.handle_packet_queued_to_send(
2762 core_ctx,
2763 bindings_ctx,
2764 timer_heap,
2765 device_id,
2766 lookup_addr,
2767 body,
2768 meta,
2769 )?;
2770
2771 Ok(do_multicast_solicit)
2772 }
2773 }
2774 }
2775 }
2776 },
2777 )?;
2778
2779 if do_multicast_solicit {
2780 self.send_neighbor_solicitation(
2781 bindings_ctx,
2782 &device_id,
2783 lookup_addr,
2784 None,
2785 );
2786 }
2787
2788 Ok(())
2789 }
2790}
2791
2792pub(crate) struct TableFullError<E> {
2793 entry: E,
2794}
2795
2796pub(crate) fn insert_new_entry<
2810 'a,
2811 I: Ip,
2812 D: LinkDevice,
2813 DeviceId: StrongDeviceIdentifier,
2814 BC: NudBindingsContext<I, D, DeviceId>,
2815>(
2816 neighbors: &'a mut HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2817 gc_state: &mut GarbageCollectionState<BC::Instant>,
2818 timer_heap: &mut TimerHeap<I, BC>,
2819 bindings_ctx: &mut BC,
2820 device_id: &DeviceId,
2821 ip: SpecifiedAddr<I::Addr>,
2822 entry: NeighborState<D, BC>,
2823) -> Result<
2824 OccupiedEntry<'a, SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2825 TableFullError<NeighborState<D, BC>>,
2826> {
2827 if neighbors.len() >= MAX_ENTRIES {
2828 timer_heap.cancel_gc(bindings_ctx);
2833 collect_garbage_inner(neighbors, gc_state, timer_heap, bindings_ctx, device_id);
2834 }
2835
2836 if neighbors.len() >= MAX_ENTRIES {
2837 return Err(TableFullError { entry });
2838 }
2839
2840 match neighbors.entry(ip) {
2841 Entry::Occupied(_) => panic!("neighbor entry unexpectedly existed"),
2842 Entry::Vacant(e) => {
2843 let event_state = entry.to_event_state();
2844 let entry = e.insert_entry(entry);
2845 let event = Event::added(device_id, event_state, ip, bindings_ctx.now());
2846 bindings_ctx.on_event(event);
2847 Ok(entry)
2848 }
2849 }
2850}
2851
2852pub fn confirm_reachable<I, D, CC, BC>(
2855 core_ctx: &mut CC,
2856 bindings_ctx: &mut BC,
2857 device_id: &CC::DeviceId,
2858 neighbor: SpecifiedAddr<I::Addr>,
2859) where
2860 I: Ip,
2861 D: LinkDevice,
2862 BC: NudBindingsContext<I, D, CC::DeviceId>,
2863 CC: NudContext<I, D, BC>,
2864{
2865 core_ctx.with_nud_state_mut_and_sender_ctx(
2866 device_id,
2867 |NudState { neighbors, timer_heap, .. }, core_ctx| {
2868 match neighbors.entry(neighbor) {
2869 Entry::Vacant(_) => {
2870 debug!(
2871 "got an upper-layer confirmation for non-existent neighbor entry {}",
2872 neighbor
2873 );
2874 }
2875 Entry::Occupied(e) => match e.into_mut() {
2876 NeighborState::Static(_) => {}
2877 NeighborState::Dynamic(e) => {
2878 let link_address = match e {
2888 DynamicNeighborState::Incomplete(_) => return,
2889 DynamicNeighborState::Reachable(Reachable {
2890 link_address,
2891 last_confirmed_at: _,
2892 })
2893 | DynamicNeighborState::Stale(Stale { link_address })
2894 | DynamicNeighborState::Delay(Delay { link_address })
2895 | DynamicNeighborState::Probe(Probe {
2896 link_address,
2897 transmit_counter: _,
2898 })
2899 | DynamicNeighborState::Unreachable(Unreachable {
2900 link_address,
2901 mode: _,
2902 }) => *link_address,
2903 };
2904 e.enter_reachable(
2905 core_ctx,
2906 bindings_ctx,
2907 timer_heap,
2908 device_id,
2909 neighbor,
2910 link_address,
2911 );
2912 }
2913 },
2914 }
2915 },
2916 );
2917}
2918
2919fn collect_garbage_inner<I, D, DeviceId, BC>(
2930 neighbors: &mut HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2931 gc_state: &mut GarbageCollectionState<BC::Instant>,
2932 timer_heap: &mut TimerHeap<I, BC>,
2933 bindings_ctx: &mut BC,
2934 device_id: &DeviceId,
2935) where
2936 I: Ip,
2937 D: LinkDevice,
2938 DeviceId: StrongDeviceIdentifier,
2939 BC: NudBindingsContext<I, D, DeviceId>,
2940{
2941 let GarbageCollectionState { last_gc, is_dirty } = gc_state;
2942 if !*is_dirty {
2944 return;
2945 }
2946
2947 let max_to_remove = neighbors.len().saturating_sub(GC_THRESHOLD);
2948 if max_to_remove == 0 {
2949 return;
2950 }
2951
2952 let mut is_still_dirty = false;
2953
2954 fn gc_priority<D: LinkDevice, BT: NudBindingsTypes<D>>(
2963 state: &DynamicNeighborState<D, BT>,
2964 ) -> usize {
2965 match state {
2966 DynamicNeighborState::Incomplete(_)
2967 | DynamicNeighborState::Reachable(_)
2968 | DynamicNeighborState::Delay(_)
2969 | DynamicNeighborState::Probe(_) => unreachable!(
2970 "the netstack should only ever discard STALE or UNREACHABLE entries; \
2971 found {:?}",
2972 state,
2973 ),
2974 DynamicNeighborState::Stale(_) => 0,
2975 DynamicNeighborState::Unreachable(Unreachable {
2976 link_address: _,
2977 mode: UnreachableMode::Backoff { probes_sent: _, packet_sent: _ },
2978 }) => 1,
2979 DynamicNeighborState::Unreachable(Unreachable {
2980 link_address: _,
2981 mode: UnreachableMode::WaitingForPacketSend,
2982 }) => 2,
2983 }
2984 }
2985
2986 struct SortEntry<'a, K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> {
2987 key: K,
2988 state: &'a mut DynamicNeighborState<D, BT>,
2989 }
2990
2991 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialEq for SortEntry<'_, K, D, BT> {
2992 fn eq(&self, other: &Self) -> bool {
2993 self.key == other.key && gc_priority(self.state) == gc_priority(other.state)
2994 }
2995 }
2996 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Eq for SortEntry<'_, K, D, BT> {}
2997 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Ord for SortEntry<'_, K, D, BT> {
2998 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2999 gc_priority(self.state).cmp(&gc_priority(other.state)).reverse()
3003 }
3004 }
3005 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialOrd for SortEntry<'_, K, D, BT> {
3006 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
3007 Some(self.cmp(&other))
3008 }
3009 }
3010
3011 let mut entries_to_remove = BinaryHeap::with_capacity(max_to_remove);
3012 for (ip, neighbor) in neighbors.iter_mut() {
3013 match neighbor {
3014 NeighborState::Static(_) => {
3015 continue;
3017 }
3018 NeighborState::Dynamic(state) => {
3019 match state {
3020 DynamicNeighborState::Incomplete(_)
3021 | DynamicNeighborState::Reachable(_)
3022 | DynamicNeighborState::Delay(_)
3023 | DynamicNeighborState::Probe(_) => {
3024 continue;
3026 }
3027 DynamicNeighborState::Stale(_) | DynamicNeighborState::Unreachable(_) => {
3028 if entries_to_remove.len() < max_to_remove {
3030 entries_to_remove.push(SortEntry { key: ip, state });
3031 continue;
3032 }
3033 is_still_dirty = true;
3038 let minimum =
3039 entries_to_remove.peek().expect("heap should have at least 1 entry");
3040 let candidate = SortEntry { key: ip, state };
3041 if &candidate > minimum {
3042 let _: SortEntry<'_, _, _, _> = entries_to_remove.pop().unwrap();
3043 entries_to_remove.push(candidate);
3044 }
3045 }
3046 }
3047 }
3048 }
3049 }
3050
3051 let entries_to_remove = entries_to_remove
3052 .into_iter()
3053 .map(|SortEntry { key: neighbor, state }| {
3054 state.cancel_timer(bindings_ctx, timer_heap, *neighbor);
3055 *neighbor
3056 })
3057 .collect::<Vec<_>>();
3058
3059 for neighbor in entries_to_remove {
3060 assert_matches!(neighbors.remove(&neighbor), Some(_));
3061 bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
3062 }
3063
3064 *last_gc = Some(bindings_ctx.now());
3065 *is_dirty = is_still_dirty;
3066}
3067
3068fn collect_garbage<I, D, CC, BC>(core_ctx: &mut CC, bindings_ctx: &mut BC, device_id: CC::DeviceId)
3069where
3070 I: Ip,
3071 D: LinkDevice,
3072 BC: NudBindingsContext<I, D, CC::DeviceId>,
3073 CC: NudContext<I, D, BC>,
3074{
3075 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, gc_state, timer_heap }, _| {
3076 collect_garbage_inner(neighbors, gc_state, timer_heap, bindings_ctx, &device_id);
3077 })
3078}
3079
3080#[cfg(test)]
3081mod tests {
3082 use alloc::vec;
3083
3084 use ip_test_macro::ip_test;
3085 use net_declare::{net_ip_v4, net_ip_v6};
3086 use net_types::UnicastAddr;
3087 use net_types::ip::{Ipv4Addr, Ipv6Addr};
3088 use netstack3_base::testutil::{
3089 FakeBindingsCtx, FakeCoreCtx, FakeInstant, FakeLinkAddress, FakeLinkDevice,
3090 FakeLinkDeviceId, FakeTimerCtxExt as _, FakeTxMetadata, FakeWeakDeviceId,
3091 };
3092 use netstack3_base::{
3093 CtxPair, InstantContext, IntoCoreTimerCtx, SendFrameContext as _, SendFrameErrorReason,
3094 };
3095 use netstack3_hashmap::HashSet;
3096 use test_case::test_case;
3097
3098 use super::*;
3099 use crate::internal::device::nud::api::{NeighborApi, StaticNeighborInsertionError};
3100 use packet::NestableSerializer as _;
3101
3102 struct FakeNudContext<I: Ip, D: LinkDevice> {
3103 state: NudState<I, D, FakeBindingsCtxImpl<I>>,
3104 counters: NudCounters<I>,
3105 }
3106
3107 struct FakeConfigContext {
3108 retrans_timer: NonZeroDuration,
3109 nud_config: NudUserConfig,
3110 }
3111
3112 struct FakeCoreCtxImpl<I: Ip> {
3113 nud: FakeNudContext<I, FakeLinkDevice>,
3114 inner: FakeInnerCtxImpl<I>,
3115 }
3116
3117 type FakeInnerCtxImpl<I> =
3118 FakeCoreCtx<FakeConfigContext, FakeNudMessageMeta<I>, FakeLinkDeviceId>;
3119
3120 #[derive(Debug, PartialEq, Eq)]
3121 enum FakeNudMessageMeta<I: Ip> {
3122 NeighborSolicitation {
3123 lookup_addr: SpecifiedAddr<I::Addr>,
3124 remote_link_addr: Option<UnicastAddr<FakeLinkAddress>>,
3125 },
3126 IpFrame {
3127 dst_link_address: UnicastAddr<FakeLinkAddress>,
3128 },
3129 IcmpDestUnreachable,
3130 }
3131
3132 type FakeBindingsCtxImpl<I> = FakeBindingsCtx<
3133 NudTimerId<I, FakeLinkDevice, FakeWeakDeviceId<FakeLinkDeviceId>>,
3134 Event<FakeLinkAddress, FakeLinkDeviceId, I, FakeInstant>,
3135 (),
3136 (),
3137 >;
3138
3139 impl<I: Ip> FakeCoreCtxImpl<I> {
3140 fn new(bindings_ctx: &mut FakeBindingsCtxImpl<I>) -> Self {
3141 Self {
3142 nud: {
3143 FakeNudContext {
3144 state: NudState::new::<_, IntoCoreTimerCtx>(
3145 bindings_ctx,
3146 FakeWeakDeviceId(FakeLinkDeviceId),
3147 ),
3148 counters: Default::default(),
3149 }
3150 },
3151 inner: FakeInnerCtxImpl::with_state(FakeConfigContext {
3152 retrans_timer: ONE_SECOND,
3153 nud_config: NudUserConfig {
3157 max_unicast_solicitations: NonZeroU16::new(4).unwrap(),
3158 max_multicast_solicitations: NonZeroU16::new(5).unwrap(),
3159 base_reachable_time: NonZeroDuration::from_secs(23).unwrap(),
3160 retrans_timer: NonZeroDuration::from_secs(3).unwrap(),
3161 },
3162 }),
3163 }
3164 }
3165 }
3166
3167 fn new_context<I: Ip>() -> CtxPair<FakeCoreCtxImpl<I>, FakeBindingsCtxImpl<I>> {
3168 CtxPair::with_default_bindings_ctx(|bindings_ctx| FakeCoreCtxImpl::<I>::new(bindings_ctx))
3169 }
3170
3171 impl<I: Ip> DeviceIdContext<FakeLinkDevice> for FakeCoreCtxImpl<I> {
3172 type DeviceId = FakeLinkDeviceId;
3173 type WeakDeviceId = FakeWeakDeviceId<FakeLinkDeviceId>;
3174 }
3175
3176 impl<I: Ip> NudContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeCoreCtxImpl<I> {
3177 type ConfigCtx<'a> = FakeConfigContext;
3178
3179 type SenderCtx<'a> = FakeInnerCtxImpl<I>;
3180
3181 fn with_nud_state_mut_and_sender_ctx<
3182 O,
3183 F: FnOnce(
3184 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3185 &mut Self::SenderCtx<'_>,
3186 ) -> O,
3187 >(
3188 &mut self,
3189 _device_id: &Self::DeviceId,
3190 cb: F,
3191 ) -> O {
3192 cb(&mut self.nud.state, &mut self.inner)
3193 }
3194
3195 fn with_nud_state_mut<
3196 O,
3197 F: FnOnce(
3198 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3199 &mut Self::ConfigCtx<'_>,
3200 ) -> O,
3201 >(
3202 &mut self,
3203 &FakeLinkDeviceId: &FakeLinkDeviceId,
3204 cb: F,
3205 ) -> O {
3206 cb(&mut self.nud.state, &mut self.inner.state)
3207 }
3208
3209 fn with_nud_state<
3210 O,
3211 F: FnOnce(&NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>) -> O,
3212 >(
3213 &mut self,
3214 &FakeLinkDeviceId: &FakeLinkDeviceId,
3215 cb: F,
3216 ) -> O {
3217 cb(&self.nud.state)
3218 }
3219
3220 fn send_neighbor_solicitation(
3221 &mut self,
3222 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3223 &FakeLinkDeviceId: &FakeLinkDeviceId,
3224 lookup_addr: SpecifiedAddr<I::Addr>,
3225 remote_link_addr: Option<UnicastAddr<FakeLinkAddress>>,
3226 ) {
3227 self.inner
3228 .send_frame(
3229 bindings_ctx,
3230 FakeNudMessageMeta::NeighborSolicitation { lookup_addr, remote_link_addr },
3231 Buf::new(Vec::new(), ..),
3232 )
3233 .unwrap()
3234 }
3235 }
3236
3237 impl<I: NudIcmpIpExt> NudIcmpContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>
3238 for FakeCoreCtxImpl<I>
3239 {
3240 fn send_icmp_dest_unreachable(
3241 &mut self,
3242 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3243 frame: Buf<Vec<u8>>,
3244 _device_id: Option<&Self::DeviceId>,
3245 _original_src_ip: SocketIpAddr<I::Addr>,
3246 _original_dst_ip: SocketIpAddr<I::Addr>,
3247 _header_len: usize,
3248 _proto: I::Proto,
3249 _metadata: I::Metadata,
3250 ) {
3251 self.inner
3252 .send_frame(bindings_ctx, FakeNudMessageMeta::IcmpDestUnreachable, frame)
3253 .unwrap()
3254 }
3255 }
3256
3257 impl<I: Ip> CounterContext<NudCounters<I>> for FakeCoreCtxImpl<I> {
3258 fn counters(&self) -> &NudCounters<I> {
3259 &self.nud.counters
3260 }
3261 }
3262
3263 impl<I: Ip> NudConfigContext<I> for FakeConfigContext {
3264 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3265 self.retrans_timer
3266 }
3267
3268 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3269 cb(&self.nud_config)
3270 }
3271
3272 fn override_lock_time(&mut self) -> Duration {
3273 Duration::ZERO
3274 }
3275 }
3276
3277 impl<I: Ip> NudSenderContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeInnerCtxImpl<I> {
3278 fn send_ip_packet_to_neighbor_link_addr<S>(
3279 &mut self,
3280 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3281 dst_link_address: UnicastAddr<FakeLinkAddress>,
3282 body: S,
3283 _tx_meta: FakeTxMetadata,
3284 ) -> Result<(), SendFrameError<S>>
3285 where
3286 S: NetworkSerializer,
3287 S::Buffer: BufferMut,
3288 {
3289 self.send_frame(bindings_ctx, FakeNudMessageMeta::IpFrame { dst_link_address }, body)
3290 }
3291 }
3292
3293 impl<I: Ip> NudConfigContext<I> for FakeInnerCtxImpl<I> {
3294 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3295 <FakeConfigContext as NudConfigContext<I>>::retransmit_timeout(&mut self.state)
3296 }
3297
3298 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3299 <FakeConfigContext as NudConfigContext<I>>::with_nud_user_config(&mut self.state, cb)
3300 }
3301
3302 fn override_lock_time(&mut self) -> Duration {
3303 <FakeConfigContext as NudConfigContext<I>>::override_lock_time(&mut self.state)
3304 }
3305 }
3306
3307 const ONE_SECOND: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
3308
3309 #[track_caller]
3310 fn check_lookup_has<I: Ip>(
3311 core_ctx: &mut FakeCoreCtxImpl<I>,
3312 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3313 lookup_addr: SpecifiedAddr<I::Addr>,
3314 expected_link_addr: UnicastAddr<FakeLinkAddress>,
3315 ) {
3316 let entry = assert_matches!(
3317 core_ctx.nud.state.neighbors.get(&lookup_addr),
3318 Some(entry @ (
3319 NeighborState::Dynamic(
3320 DynamicNeighborState::Reachable (Reachable { link_address, last_confirmed_at: _ })
3321 | DynamicNeighborState::Stale (Stale { link_address })
3322 | DynamicNeighborState::Delay (Delay { link_address })
3323 | DynamicNeighborState::Probe (Probe { link_address, transmit_counter: _ })
3324 | DynamicNeighborState::Unreachable (Unreachable { link_address, mode: _ })
3325 )
3326 | NeighborState::Static(link_address)
3327 )) => {
3328 assert_eq!(link_address, &expected_link_addr);
3329 entry
3330 }
3331 );
3332 match entry {
3333 NeighborState::Dynamic(DynamicNeighborState::Incomplete { .. }) => {
3334 unreachable!("entry must be static, REACHABLE, or STALE")
3335 }
3336 NeighborState::Dynamic(DynamicNeighborState::Reachable { .. }) => {
3337 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3338 bindings_ctx,
3339 [(
3340 lookup_addr,
3341 NudEvent::ReachableTime,
3342 core_ctx.inner.base_reachable_time().get(),
3343 )],
3344 );
3345 }
3346 NeighborState::Dynamic(DynamicNeighborState::Delay { .. }) => {
3347 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3348 bindings_ctx,
3349 [(lookup_addr, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
3350 );
3351 }
3352 NeighborState::Dynamic(DynamicNeighborState::Probe { .. }) => {
3353 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3354 bindings_ctx,
3355 [(
3356 lookup_addr,
3357 NudEvent::RetransmitUnicastProbe,
3358 core_ctx.inner.state.retrans_timer.get(),
3359 )],
3360 );
3361 }
3362 NeighborState::Dynamic(DynamicNeighborState::Unreachable(Unreachable {
3363 link_address: _,
3364 mode,
3365 })) => {
3366 let instant = match mode {
3367 UnreachableMode::WaitingForPacketSend => None,
3368 mode @ UnreachableMode::Backoff { .. } => {
3369 let duration =
3370 mode.next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state);
3371 Some(bindings_ctx.now() + duration.get())
3372 }
3373 };
3374 if let Some(instant) = instant {
3375 core_ctx.nud.state.timer_heap.neighbor.assert_timers([(
3376 lookup_addr,
3377 NudEvent::RetransmitUnicastProbe,
3378 instant,
3379 )]);
3380 }
3381 }
3382 NeighborState::Dynamic(DynamicNeighborState::Stale { .. })
3383 | NeighborState::Static(_) => bindings_ctx.timers.assert_no_timers_installed(),
3384 }
3385 }
3386
3387 trait TestIpExt: NudIcmpIpExt {
3388 const LOOKUP_ADDR1: SpecifiedAddr<Self::Addr>;
3389 const LOOKUP_ADDR2: SpecifiedAddr<Self::Addr>;
3390 const LOOKUP_ADDR3: SpecifiedAddr<Self::Addr>;
3391 }
3392
3393 impl TestIpExt for Ipv4 {
3394 const LOOKUP_ADDR1: SpecifiedAddr<Ipv4Addr> =
3396 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.1")) };
3397 const LOOKUP_ADDR2: SpecifiedAddr<Ipv4Addr> =
3398 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.2")) };
3399 const LOOKUP_ADDR3: SpecifiedAddr<Ipv4Addr> =
3400 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.3")) };
3401 }
3402
3403 impl TestIpExt for Ipv6 {
3404 const LOOKUP_ADDR1: SpecifiedAddr<Ipv6Addr> =
3406 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::1")) };
3407 const LOOKUP_ADDR2: SpecifiedAddr<Ipv6Addr> =
3408 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::2")) };
3409 const LOOKUP_ADDR3: SpecifiedAddr<Ipv6Addr> =
3410 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::3")) };
3411 }
3412
3413 const LINK_ADDR1: UnicastAddr<FakeLinkAddress> =
3414 unsafe { UnicastAddr::new_unchecked(FakeLinkAddress([2])) };
3415 const LINK_ADDR2: UnicastAddr<FakeLinkAddress> =
3416 unsafe { UnicastAddr::new_unchecked(FakeLinkAddress([4])) };
3417 const LINK_ADDR3: UnicastAddr<FakeLinkAddress> =
3418 unsafe { UnicastAddr::new_unchecked(FakeLinkAddress([6])) };
3419
3420 impl<I: Ip, L: LinkDevice> NudTimerId<I, L, FakeWeakDeviceId<FakeLinkDeviceId>> {
3421 fn neighbor() -> Self {
3422 Self {
3423 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3424 timer_type: NudTimerType::Neighbor,
3425 _marker: PhantomData,
3426 }
3427 }
3428
3429 fn garbage_collection() -> Self {
3430 Self {
3431 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3432 timer_type: NudTimerType::GarbageCollection,
3433 _marker: PhantomData,
3434 }
3435 }
3436 }
3437
3438 fn queue_ip_packet_to_unresolved_neighbor<I: TestIpExt>(
3439 core_ctx: &mut FakeCoreCtxImpl<I>,
3440 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3441 neighbor: SpecifiedAddr<I::Addr>,
3442 pending_frames: &mut VecDeque<Buf<Vec<u8>>>,
3443 body: u8,
3444 expect_event: bool,
3445 ) {
3446 let body = [body];
3447 assert_eq!(
3448 NudHandler::send_ip_packet_to_neighbor(
3449 core_ctx,
3450 bindings_ctx,
3451 &FakeLinkDeviceId,
3452 neighbor,
3453 Buf::new(body, ..),
3454 FakeTxMetadata::default(),
3455 ),
3456 Ok(())
3457 );
3458
3459 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
3460
3461 pending_frames.push_back(Buf::new(body.to_vec(), ..));
3462
3463 assert_neighbor_state_with_ip(
3464 core_ctx,
3465 bindings_ctx,
3466 neighbor,
3467 DynamicNeighborState::Incomplete(Incomplete {
3468 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
3469 pending_frames: pending_frames
3470 .iter()
3471 .cloned()
3472 .map(|buf| (buf, FakeTxMetadata::default()))
3473 .collect(),
3474 notifiers: Vec::new(),
3475 _marker: PhantomData,
3476 }),
3477 expect_event.then_some(ExpectedEvent::Added),
3478 );
3479
3480 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3481 bindings_ctx,
3482 [(neighbor, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
3483 );
3484 }
3485
3486 fn init_incomplete_neighbor_with_ip<I: TestIpExt>(
3487 core_ctx: &mut FakeCoreCtxImpl<I>,
3488 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3489 ip_address: SpecifiedAddr<I::Addr>,
3490 take_probe: bool,
3491 ) -> VecDeque<Buf<Vec<u8>>> {
3492 let mut pending_frames = VecDeque::new();
3493 queue_ip_packet_to_unresolved_neighbor(
3494 core_ctx,
3495 bindings_ctx,
3496 ip_address,
3497 &mut pending_frames,
3498 1,
3499 true, );
3501 if take_probe {
3502 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, None);
3503 }
3504 pending_frames
3505 }
3506
3507 fn init_incomplete_neighbor<I: TestIpExt>(
3508 core_ctx: &mut FakeCoreCtxImpl<I>,
3509 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3510 take_probe: bool,
3511 ) -> VecDeque<Buf<Vec<u8>>> {
3512 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, take_probe)
3513 }
3514
3515 fn init_stale_neighbor_with_ip<I: TestIpExt>(
3516 core_ctx: &mut FakeCoreCtxImpl<I>,
3517 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3518 ip_address: SpecifiedAddr<I::Addr>,
3519 link_address: UnicastAddr<FakeLinkAddress>,
3520 ) {
3521 NudHandler::handle_neighbor_update(
3522 core_ctx,
3523 bindings_ctx,
3524 &FakeLinkDeviceId,
3525 ip_address,
3526 DynamicNeighborUpdateSource::Probe { link_address },
3527 );
3528 assert_neighbor_state_with_ip(
3529 core_ctx,
3530 bindings_ctx,
3531 ip_address,
3532 DynamicNeighborState::Stale(Stale { link_address }),
3533 Some(ExpectedEvent::Added),
3534 );
3535 }
3536
3537 fn init_stale_neighbor<I: TestIpExt>(
3538 core_ctx: &mut FakeCoreCtxImpl<I>,
3539 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3540 link_address: UnicastAddr<FakeLinkAddress>,
3541 ) {
3542 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3543 }
3544
3545 fn init_reachable_neighbor_with_ip<I: TestIpExt>(
3546 core_ctx: &mut FakeCoreCtxImpl<I>,
3547 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3548 ip_address: SpecifiedAddr<I::Addr>,
3549 link_address: UnicastAddr<FakeLinkAddress>,
3550 ) {
3551 let queued_frame =
3552 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, true);
3553 NudHandler::handle_neighbor_update(
3554 core_ctx,
3555 bindings_ctx,
3556 &FakeLinkDeviceId,
3557 ip_address,
3558 DynamicNeighborUpdateSource::Confirmation {
3559 link_address: Some(link_address),
3560 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
3561 },
3562 );
3563 assert_neighbor_state_with_ip(
3564 core_ctx,
3565 bindings_ctx,
3566 ip_address,
3567 DynamicNeighborState::Reachable(Reachable {
3568 link_address,
3569 last_confirmed_at: bindings_ctx.now(),
3570 }),
3571 Some(ExpectedEvent::Changed),
3572 );
3573 assert_pending_frame_sent(core_ctx, queued_frame, link_address);
3574 }
3575
3576 fn init_reachable_neighbor<I: TestIpExt>(
3577 core_ctx: &mut FakeCoreCtxImpl<I>,
3578 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3579 link_address: UnicastAddr<FakeLinkAddress>,
3580 ) {
3581 init_reachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3582 }
3583
3584 fn init_delay_neighbor_with_ip<I: TestIpExt>(
3585 core_ctx: &mut FakeCoreCtxImpl<I>,
3586 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3587 ip_address: SpecifiedAddr<I::Addr>,
3588 link_address: UnicastAddr<FakeLinkAddress>,
3589 ) {
3590 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3591 assert_eq!(
3592 NudHandler::send_ip_packet_to_neighbor(
3593 core_ctx,
3594 bindings_ctx,
3595 &FakeLinkDeviceId,
3596 ip_address,
3597 Buf::new([1], ..),
3598 FakeTxMetadata::default(),
3599 ),
3600 Ok(())
3601 );
3602 assert_neighbor_state_with_ip(
3603 core_ctx,
3604 bindings_ctx,
3605 ip_address,
3606 DynamicNeighborState::Delay(Delay { link_address }),
3607 Some(ExpectedEvent::Changed),
3608 );
3609 assert_eq!(
3610 core_ctx.inner.take_frames(),
3611 vec![(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![1])],
3612 );
3613 }
3614
3615 fn init_delay_neighbor<I: TestIpExt>(
3616 core_ctx: &mut FakeCoreCtxImpl<I>,
3617 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3618 link_address: UnicastAddr<FakeLinkAddress>,
3619 ) {
3620 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3621 }
3622
3623 fn init_probe_neighbor_with_ip<I: TestIpExt>(
3624 core_ctx: &mut FakeCoreCtxImpl<I>,
3625 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3626 ip_address: SpecifiedAddr<I::Addr>,
3627 link_address: UnicastAddr<FakeLinkAddress>,
3628 take_probe: bool,
3629 ) {
3630 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3631 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3632 core_ctx.nud.state.timer_heap.neighbor.assert_top(&ip_address, &NudEvent::DelayFirstProbe);
3633 assert_eq!(
3634 bindings_ctx.trigger_timers_for(DELAY_FIRST_PROBE_TIME.into(), core_ctx),
3635 [NudTimerId::neighbor()]
3636 );
3637 assert_neighbor_state_with_ip(
3638 core_ctx,
3639 bindings_ctx,
3640 ip_address,
3641 DynamicNeighborState::Probe(Probe {
3642 link_address,
3643 transmit_counter: NonZeroU16::new(max_unicast_solicit - 1),
3644 }),
3645 Some(ExpectedEvent::Changed),
3646 );
3647 if take_probe {
3648 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3649 }
3650 }
3651
3652 fn init_probe_neighbor<I: TestIpExt>(
3653 core_ctx: &mut FakeCoreCtxImpl<I>,
3654 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3655 link_address: UnicastAddr<FakeLinkAddress>,
3656 take_probe: bool,
3657 ) {
3658 init_probe_neighbor_with_ip(
3659 core_ctx,
3660 bindings_ctx,
3661 I::LOOKUP_ADDR1,
3662 link_address,
3663 take_probe,
3664 );
3665 }
3666
3667 fn init_unreachable_neighbor_with_ip<I: TestIpExt>(
3668 core_ctx: &mut FakeCoreCtxImpl<I>,
3669 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3670 ip_address: SpecifiedAddr<I::Addr>,
3671 link_address: UnicastAddr<FakeLinkAddress>,
3672 ) {
3673 init_probe_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address, false);
3674 let retransmit_timeout = core_ctx.inner.retransmit_timeout();
3675 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3676 for _ in 0..max_unicast_solicit {
3677 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3678 assert_eq!(
3679 bindings_ctx.trigger_timers_for(retransmit_timeout.into(), core_ctx),
3680 [NudTimerId::neighbor()]
3681 );
3682 }
3683 assert_neighbor_state_with_ip(
3684 core_ctx,
3685 bindings_ctx,
3686 ip_address,
3687 DynamicNeighborState::Unreachable(Unreachable {
3688 link_address,
3689 mode: UnreachableMode::WaitingForPacketSend,
3690 }),
3691 Some(ExpectedEvent::Changed),
3692 );
3693 }
3694
3695 fn init_unreachable_neighbor<I: TestIpExt>(
3696 core_ctx: &mut FakeCoreCtxImpl<I>,
3697 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3698 link_address: UnicastAddr<FakeLinkAddress>,
3699 ) {
3700 init_unreachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3701 }
3702
3703 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
3704 enum InitialState {
3705 Incomplete,
3706 Stale,
3707 Reachable,
3708 Delay,
3709 Probe,
3710 Unreachable,
3711 }
3712
3713 fn init_neighbor_in_state<I: TestIpExt>(
3714 core_ctx: &mut FakeCoreCtxImpl<I>,
3715 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3716 state: InitialState,
3717 ) -> DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>> {
3718 match state {
3719 InitialState::Incomplete => {
3720 let _: VecDeque<Buf<Vec<u8>>> =
3721 init_incomplete_neighbor(core_ctx, bindings_ctx, true);
3722 }
3723 InitialState::Reachable => {
3724 init_reachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3725 }
3726 InitialState::Stale => {
3727 init_stale_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3728 }
3729 InitialState::Delay => {
3730 init_delay_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3731 }
3732 InitialState::Probe => {
3733 init_probe_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, true);
3734 }
3735 InitialState::Unreachable => {
3736 init_unreachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3737 }
3738 }
3739 assert_matches!(core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
3740 Some(NeighborState::Dynamic(state)) => state.clone()
3741 )
3742 }
3743
3744 #[track_caller]
3745 fn init_static_neighbor_with_ip<I: TestIpExt>(
3746 core_ctx: &mut FakeCoreCtxImpl<I>,
3747 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3748 ip_address: SpecifiedAddr<I::Addr>,
3749 link_address: UnicastAddr<FakeLinkAddress>,
3750 expected_event: ExpectedEvent,
3751 ) {
3752 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3753 NeighborApi::new(&mut ctx)
3754 .insert_static_entry(&FakeLinkDeviceId, *ip_address, link_address)
3755 .unwrap();
3756 assert_eq!(
3757 ctx.bindings_ctx.take_events(),
3758 [Event {
3759 device: FakeLinkDeviceId,
3760 addr: ip_address,
3761 kind: match expected_event {
3762 ExpectedEvent::Added => EventKind::Added(EventState::Static(link_address)),
3763 ExpectedEvent::Changed => EventKind::Changed(EventState::Static(link_address)),
3764 },
3765 at: ctx.bindings_ctx.now(),
3766 }],
3767 );
3768 }
3769
3770 #[track_caller]
3771 fn init_static_neighbor<I: TestIpExt>(
3772 core_ctx: &mut FakeCoreCtxImpl<I>,
3773 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3774 link_address: UnicastAddr<FakeLinkAddress>,
3775 expected_event: ExpectedEvent,
3776 ) {
3777 init_static_neighbor_with_ip(
3778 core_ctx,
3779 bindings_ctx,
3780 I::LOOKUP_ADDR1,
3781 link_address,
3782 expected_event,
3783 );
3784 }
3785
3786 #[track_caller]
3787 fn delete_neighbor<I: TestIpExt>(
3788 core_ctx: &mut FakeCoreCtxImpl<I>,
3789 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3790 ) {
3791 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3792 NeighborApi::new(&mut ctx)
3793 .remove_entry(&FakeLinkDeviceId, *I::LOOKUP_ADDR1)
3794 .expect("neighbor entry should exist");
3795 assert_eq!(
3796 ctx.bindings_ctx.take_events(),
3797 [Event::removed(&FakeLinkDeviceId, I::LOOKUP_ADDR1, ctx.bindings_ctx.now())],
3798 );
3799 }
3800
3801 #[track_caller]
3802 fn assert_neighbor_state<I: TestIpExt>(
3803 core_ctx: &FakeCoreCtxImpl<I>,
3804 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3805 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3806 event_kind: Option<ExpectedEvent>,
3807 ) {
3808 assert_neighbor_state_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, state, event_kind);
3809 }
3810
3811 #[derive(Clone, Copy, Debug)]
3812 enum ExpectedEvent {
3813 Added,
3814 Changed,
3815 }
3816
3817 #[track_caller]
3818 fn assert_neighbor_state_with_ip<I: TestIpExt>(
3819 core_ctx: &FakeCoreCtxImpl<I>,
3820 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3821 neighbor: SpecifiedAddr<I::Addr>,
3822 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3823 expected_event: Option<ExpectedEvent>,
3824 ) {
3825 if let Some(expected_event) = expected_event {
3826 let event_state = EventState::Dynamic(state.to_event_dynamic_state());
3827 assert_eq!(
3828 bindings_ctx.take_events(),
3829 [Event {
3830 device: FakeLinkDeviceId,
3831 addr: neighbor,
3832 kind: match expected_event {
3833 ExpectedEvent::Added => EventKind::Added(event_state),
3834 ExpectedEvent::Changed => EventKind::Changed(event_state),
3835 },
3836 at: bindings_ctx.now(),
3837 }],
3838 );
3839 }
3840
3841 assert_eq!(
3842 core_ctx.nud.state.neighbors.get(&neighbor),
3843 Some(&NeighborState::Dynamic(state))
3844 );
3845 }
3846
3847 #[track_caller]
3848 fn assert_pending_frame_sent<I: TestIpExt>(
3849 core_ctx: &mut FakeCoreCtxImpl<I>,
3850 pending_frames: VecDeque<Buf<Vec<u8>>>,
3851 link_address: UnicastAddr<FakeLinkAddress>,
3852 ) {
3853 assert_eq!(
3854 core_ctx.inner.take_frames(),
3855 pending_frames
3856 .into_iter()
3857 .map(|f| (
3858 FakeNudMessageMeta::IpFrame { dst_link_address: link_address },
3859 f.as_ref().to_vec(),
3860 ))
3861 .collect::<Vec<_>>()
3862 );
3863 }
3864
3865 #[track_caller]
3866 fn assert_neighbor_probe_sent_for_ip<I: TestIpExt>(
3867 core_ctx: &mut FakeCoreCtxImpl<I>,
3868 ip_address: SpecifiedAddr<I::Addr>,
3869 link_address: Option<UnicastAddr<FakeLinkAddress>>,
3870 ) {
3871 assert_eq!(
3872 core_ctx.inner.take_frames(),
3873 [(
3874 FakeNudMessageMeta::NeighborSolicitation {
3875 lookup_addr: ip_address,
3876 remote_link_addr: link_address,
3877 },
3878 Vec::new()
3879 )]
3880 );
3881 }
3882
3883 #[track_caller]
3884 fn assert_neighbor_probe_sent<I: TestIpExt>(
3885 core_ctx: &mut FakeCoreCtxImpl<I>,
3886 link_address: Option<UnicastAddr<FakeLinkAddress>>,
3887 ) {
3888 assert_neighbor_probe_sent_for_ip(core_ctx, I::LOOKUP_ADDR1, link_address);
3889 }
3890
3891 #[track_caller]
3892 fn assert_neighbor_removed_with_ip<I: TestIpExt>(
3893 core_ctx: &mut FakeCoreCtxImpl<I>,
3894 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3895 neighbor: SpecifiedAddr<I::Addr>,
3896 ) {
3897 super::testutil::assert_neighbor_unknown(core_ctx, FakeLinkDeviceId, neighbor);
3898 assert_eq!(
3899 bindings_ctx.take_events(),
3900 [Event::removed(&FakeLinkDeviceId, neighbor, bindings_ctx.now())],
3901 );
3902 }
3903
3904 #[ip_test(I)]
3905 fn serialization_failure_doesnt_schedule_timer<I: TestIpExt>() {
3906 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3907
3908 let packet = Buf::new([0; 2], ..).with_size_limit(1);
3911
3912 let err = assert_matches!(
3913 NudHandler::send_ip_packet_to_neighbor(
3914 &mut core_ctx,
3915 &mut bindings_ctx,
3916 &FakeLinkDeviceId,
3917 I::LOOKUP_ADDR1,
3918 packet,
3919 FakeTxMetadata::default(),
3920 ),
3921 Err(ErrorAndSerializer { error, serializer: _ }) => error
3922 );
3923 assert_eq!(err, SendFrameErrorReason::SizeConstraintsViolation);
3924
3925 super::testutil::assert_neighbor_unknown(&mut core_ctx, FakeLinkDeviceId, I::LOOKUP_ADDR1);
3928 assert_eq!(core_ctx.inner.take_frames(), []);
3929 bindings_ctx.timers.assert_no_timers_installed();
3930 }
3931
3932 #[ip_test(I)]
3933 fn incomplete_to_stale_on_probe<I: TestIpExt>() {
3934 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3935
3936 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3938
3939 NudHandler::handle_neighbor_update(
3941 &mut core_ctx,
3942 &mut bindings_ctx,
3943 &FakeLinkDeviceId,
3944 I::LOOKUP_ADDR1,
3945 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
3946 );
3947
3948 assert_neighbor_state(
3950 &core_ctx,
3951 &mut bindings_ctx,
3952 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3953 Some(ExpectedEvent::Changed),
3954 );
3955 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3956 }
3957
3958 #[ip_test(I)]
3959 #[test_case(true, true; "solicited override")]
3960 #[test_case(true, false; "solicited non-override")]
3961 #[test_case(false, true; "unsolicited override")]
3962 #[test_case(false, false; "unsolicited non-override")]
3963 fn incomplete_on_confirmation<I: TestIpExt>(solicited_flag: bool, override_flag: bool) {
3964 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3965
3966 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3968
3969 NudHandler::handle_neighbor_update(
3971 &mut core_ctx,
3972 &mut bindings_ctx,
3973 &FakeLinkDeviceId,
3974 I::LOOKUP_ADDR1,
3975 DynamicNeighborUpdateSource::Confirmation {
3976 link_address: Some(LINK_ADDR1),
3977 flags: ConfirmationFlags { solicited_flag, override_flag },
3978 },
3979 );
3980
3981 let expected_state = if solicited_flag {
3982 DynamicNeighborState::Reachable(Reachable {
3983 link_address: LINK_ADDR1,
3984 last_confirmed_at: bindings_ctx.now(),
3985 })
3986 } else {
3987 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 })
3988 };
3989 assert_neighbor_state(
3990 &core_ctx,
3991 &mut bindings_ctx,
3992 expected_state,
3993 Some(ExpectedEvent::Changed),
3994 );
3995 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3996 }
3997
3998 #[ip_test(I)]
3999 fn reachable_to_stale_on_timeout<I: TestIpExt>() {
4000 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4001
4002 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4004
4005 assert_eq!(
4007 bindings_ctx
4008 .trigger_timers_for(core_ctx.inner.base_reachable_time().into(), &mut core_ctx,),
4009 [NudTimerId::neighbor()]
4010 );
4011 assert_neighbor_state(
4012 &core_ctx,
4013 &mut bindings_ctx,
4014 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4015 Some(ExpectedEvent::Changed),
4016 );
4017 }
4018
4019 #[ip_test(I)]
4020 #[test_case(InitialState::Reachable, true; "reachable with different address")]
4021 #[test_case(InitialState::Reachable, false; "reachable with same address")]
4022 #[test_case(InitialState::Stale, true; "stale with different address")]
4023 #[test_case(InitialState::Stale, false; "stale with same address")]
4024 #[test_case(InitialState::Delay, true; "delay with different address")]
4025 #[test_case(InitialState::Delay, false; "delay with same address")]
4026 #[test_case(InitialState::Probe, true; "probe with different address")]
4027 #[test_case(InitialState::Probe, false; "probe with same address")]
4028 #[test_case(InitialState::Unreachable, true; "unreachable with different address")]
4029 #[test_case(InitialState::Unreachable, false; "unreachable with same address")]
4030 fn transition_to_stale_on_probe_with_different_address<I: TestIpExt>(
4031 initial_state: InitialState,
4032 update_link_address: bool,
4033 ) {
4034 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4035
4036 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4038
4039 NudHandler::handle_neighbor_update(
4041 &mut core_ctx,
4042 &mut bindings_ctx,
4043 &FakeLinkDeviceId,
4044 I::LOOKUP_ADDR1,
4045 DynamicNeighborUpdateSource::Probe {
4046 link_address: if update_link_address { LINK_ADDR2 } else { LINK_ADDR1 },
4047 },
4048 );
4049
4050 let expected_state = if update_link_address {
4056 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 })
4057 } else {
4058 initial_state
4059 };
4060 assert_neighbor_state(
4061 &core_ctx,
4062 &mut bindings_ctx,
4063 expected_state,
4064 update_link_address.then_some(ExpectedEvent::Changed),
4065 );
4066 }
4067
4068 #[ip_test(I)]
4069 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
4070 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
4071 #[test_case(InitialState::Stale, true; "stale with override flag set")]
4072 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
4073 #[test_case(InitialState::Delay, true; "delay with override flag set")]
4074 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
4075 #[test_case(InitialState::Probe, true; "probe with override flag set")]
4076 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
4077 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
4078 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
4079 fn transition_to_reachable_on_solicited_confirmation_same_address<I: TestIpExt>(
4080 initial_state: InitialState,
4081 override_flag: bool,
4082 ) {
4083 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4084
4085 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4087
4088 NudHandler::handle_neighbor_update(
4090 &mut core_ctx,
4091 &mut bindings_ctx,
4092 &FakeLinkDeviceId,
4093 I::LOOKUP_ADDR1,
4094 DynamicNeighborUpdateSource::Confirmation {
4095 link_address: Some(LINK_ADDR1),
4096 flags: ConfirmationFlags { solicited_flag: true, override_flag },
4097 },
4098 );
4099
4100 let now = bindings_ctx.now();
4102 assert_neighbor_state(
4103 &core_ctx,
4104 &mut bindings_ctx,
4105 DynamicNeighborState::Reachable(Reachable {
4106 link_address: LINK_ADDR1,
4107 last_confirmed_at: now,
4108 }),
4109 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
4110 );
4111 }
4112
4113 #[ip_test(I)]
4114 #[test_case(InitialState::Reachable; "reachable")]
4115 #[test_case(InitialState::Stale; "stale")]
4116 #[test_case(InitialState::Delay; "delay")]
4117 #[test_case(InitialState::Probe; "probe")]
4118 #[test_case(InitialState::Unreachable; "unreachable")]
4119 fn transition_to_stale_on_unsolicited_override_confirmation_with_different_address<
4120 I: TestIpExt,
4121 >(
4122 initial_state: InitialState,
4123 ) {
4124 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4125
4126 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4128
4129 NudHandler::handle_neighbor_update(
4131 &mut core_ctx,
4132 &mut bindings_ctx,
4133 &FakeLinkDeviceId,
4134 I::LOOKUP_ADDR1,
4135 DynamicNeighborUpdateSource::Confirmation {
4136 link_address: Some(LINK_ADDR2),
4137 flags: ConfirmationFlags { solicited_flag: false, override_flag: true },
4138 },
4139 );
4140
4141 assert_neighbor_state(
4143 &core_ctx,
4144 &mut bindings_ctx,
4145 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4146 Some(ExpectedEvent::Changed),
4147 );
4148 }
4149
4150 #[ip_test(I)]
4151 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
4152 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
4153 #[test_case(InitialState::Stale, true; "stale with override flag set")]
4154 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
4155 #[test_case(InitialState::Delay, true; "delay with override flag set")]
4156 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
4157 #[test_case(InitialState::Probe, true; "probe with override flag set")]
4158 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
4159 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
4160 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
4161 fn noop_on_unsolicited_confirmation_with_same_address<I: TestIpExt>(
4162 initial_state: InitialState,
4163 override_flag: bool,
4164 ) {
4165 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4166
4167 let expected_state =
4169 init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4170
4171 NudHandler::handle_neighbor_update(
4173 &mut core_ctx,
4174 &mut bindings_ctx,
4175 &FakeLinkDeviceId,
4176 I::LOOKUP_ADDR1,
4177 DynamicNeighborUpdateSource::Confirmation {
4178 link_address: Some(LINK_ADDR1),
4179 flags: ConfirmationFlags { solicited_flag: false, override_flag },
4180 },
4181 );
4182
4183 assert_neighbor_state(&core_ctx, &mut bindings_ctx, expected_state, None);
4185 }
4186
4187 #[ip_test(I)]
4188 #[test_case(InitialState::Reachable; "reachable")]
4189 #[test_case(InitialState::Stale; "stale")]
4190 #[test_case(InitialState::Delay; "delay")]
4191 #[test_case(InitialState::Probe; "probe")]
4192 #[test_case(InitialState::Unreachable; "unreachable")]
4193 fn transition_to_reachable_on_solicited_override_confirmation_with_different_address<
4194 I: TestIpExt,
4195 >(
4196 initial_state: InitialState,
4197 ) {
4198 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4199
4200 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4202
4203 NudHandler::handle_neighbor_update(
4205 &mut core_ctx,
4206 &mut bindings_ctx,
4207 &FakeLinkDeviceId,
4208 I::LOOKUP_ADDR1,
4209 DynamicNeighborUpdateSource::Confirmation {
4210 link_address: Some(LINK_ADDR2),
4211 flags: ConfirmationFlags { solicited_flag: true, override_flag: true },
4212 },
4213 );
4214
4215 let now = bindings_ctx.now();
4217 assert_neighbor_state(
4218 &core_ctx,
4219 &mut bindings_ctx,
4220 DynamicNeighborState::Reachable(Reachable {
4221 link_address: LINK_ADDR2,
4222 last_confirmed_at: now,
4223 }),
4224 Some(ExpectedEvent::Changed),
4225 );
4226 }
4227
4228 #[ip_test(I)]
4229 fn reachable_to_reachable_on_probe_with_same_address<I: TestIpExt>() {
4230 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4231
4232 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4234
4235 NudHandler::handle_neighbor_update(
4237 &mut core_ctx,
4238 &mut bindings_ctx,
4239 &FakeLinkDeviceId,
4240 I::LOOKUP_ADDR1,
4241 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
4242 );
4243
4244 let now = bindings_ctx.now();
4246 assert_neighbor_state(
4247 &core_ctx,
4248 &mut bindings_ctx,
4249 DynamicNeighborState::Reachable(Reachable {
4250 link_address: LINK_ADDR1,
4251 last_confirmed_at: now,
4252 }),
4253 None,
4254 );
4255 }
4256
4257 #[ip_test(I)]
4258 #[test_case(true; "solicited")]
4259 #[test_case(false; "unsolicited")]
4260 fn reachable_to_stale_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4261 solicited_flag: bool,
4262 ) {
4263 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4264
4265 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4267
4268 NudHandler::handle_neighbor_update(
4270 &mut core_ctx,
4271 &mut bindings_ctx,
4272 &FakeLinkDeviceId,
4273 I::LOOKUP_ADDR1,
4274 DynamicNeighborUpdateSource::Confirmation {
4275 link_address: Some(LINK_ADDR2),
4276 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4277 },
4278 );
4279
4280 assert_neighbor_state(
4283 &core_ctx,
4284 &mut bindings_ctx,
4285 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4286 Some(ExpectedEvent::Changed),
4287 );
4288 }
4289
4290 #[ip_test(I)]
4291 #[test_case(InitialState::Stale, true; "stale solicited")]
4292 #[test_case(InitialState::Stale, false; "stale unsolicited")]
4293 #[test_case(InitialState::Delay, true; "delay solicited")]
4294 #[test_case(InitialState::Delay, false; "delay unsolicited")]
4295 #[test_case(InitialState::Probe, true; "probe solicited")]
4296 #[test_case(InitialState::Probe, false; "probe unsolicited")]
4297 #[test_case(InitialState::Unreachable, true; "unreachable solicited")]
4298 #[test_case(InitialState::Unreachable, false; "unreachable unsolicited")]
4299 fn noop_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4300 initial_state: InitialState,
4301 solicited_flag: bool,
4302 ) {
4303 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4304
4305 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4307
4308 NudHandler::handle_neighbor_update(
4310 &mut core_ctx,
4311 &mut bindings_ctx,
4312 &FakeLinkDeviceId,
4313 I::LOOKUP_ADDR1,
4314 DynamicNeighborUpdateSource::Confirmation {
4315 link_address: Some(LINK_ADDR2),
4316 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4317 },
4318 );
4319
4320 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial_state, None);
4323 }
4324
4325 #[ip_test(I)]
4326 fn stale_to_delay_on_packet_sent<I: TestIpExt>() {
4327 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4328
4329 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4331
4332 let body = 1;
4334 assert_eq!(
4335 NudHandler::send_ip_packet_to_neighbor(
4336 &mut core_ctx,
4337 &mut bindings_ctx,
4338 &FakeLinkDeviceId,
4339 I::LOOKUP_ADDR1,
4340 Buf::new([body], ..),
4341 FakeTxMetadata::default(),
4342 ),
4343 Ok(())
4344 );
4345
4346 assert_neighbor_state(
4348 &core_ctx,
4349 &mut bindings_ctx,
4350 DynamicNeighborState::Delay(Delay { link_address: LINK_ADDR1 }),
4351 Some(ExpectedEvent::Changed),
4352 );
4353 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4354 &mut bindings_ctx,
4355 [(I::LOOKUP_ADDR1, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
4356 );
4357 assert_pending_frame_sent(
4358 &mut core_ctx,
4359 VecDeque::from([Buf::new(vec![body], ..)]),
4360 LINK_ADDR1,
4361 );
4362 }
4363
4364 #[ip_test(I)]
4365 #[test_case(InitialState::Delay,
4366 NudEvent::DelayFirstProbe;
4367 "delay to probe")]
4368 #[test_case(InitialState::Probe,
4369 NudEvent::RetransmitUnicastProbe;
4370 "probe retransmit unicast probe")]
4371 fn delay_or_probe_to_probe_on_timeout<I: TestIpExt>(
4372 initial_state: InitialState,
4373 expected_initial_event: NudEvent,
4374 ) {
4375 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4376
4377 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4379
4380 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4381
4382 let (time, transmit_counter) = match initial_state {
4388 InitialState::Delay => {
4389 (DELAY_FIRST_PROBE_TIME, NonZeroU16::new(max_unicast_solicit - 1))
4390 }
4391 InitialState::Probe => {
4392 (core_ctx.inner.state.retrans_timer, NonZeroU16::new(max_unicast_solicit - 2))
4393 }
4394 other => unreachable!("test only covers DELAY and PROBE, got {:?}", other),
4395 };
4396 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4397 &mut bindings_ctx,
4398 [(I::LOOKUP_ADDR1, expected_initial_event, time.get())],
4399 );
4400 assert_eq!(
4401 bindings_ctx.trigger_timers_for(time.into(), &mut core_ctx,),
4402 [NudTimerId::neighbor()]
4403 );
4404 assert_neighbor_state(
4405 &core_ctx,
4406 &mut bindings_ctx,
4407 DynamicNeighborState::Probe(Probe { link_address: LINK_ADDR1, transmit_counter }),
4408 (initial_state != InitialState::Probe).then_some(ExpectedEvent::Changed),
4409 );
4410 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4411 &mut bindings_ctx,
4412 [(
4413 I::LOOKUP_ADDR1,
4414 NudEvent::RetransmitUnicastProbe,
4415 core_ctx.inner.state.retrans_timer.get(),
4416 )],
4417 );
4418 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4419 }
4420
4421 #[ip_test(I)]
4422 fn unreachable_probes_with_exponential_backoff_while_packets_sent<I: TestIpExt>() {
4423 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4424
4425 init_unreachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4426
4427 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4428 let timer_id = NudTimerId::neighbor();
4429
4430 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), []);
4432 assert_eq!(core_ctx.inner.take_frames(), []);
4433
4434 const BODY: u8 = 0x33;
4436 assert_eq!(
4437 NudHandler::send_ip_packet_to_neighbor(
4438 &mut core_ctx,
4439 &mut bindings_ctx,
4440 &FakeLinkDeviceId,
4441 I::LOOKUP_ADDR1,
4442 Buf::new([BODY], ..),
4443 FakeTxMetadata::default(),
4444 ),
4445 Ok(())
4446 );
4447 assert_eq!(
4448 core_ctx.inner.take_frames(),
4449 [
4450 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4451 (
4452 FakeNudMessageMeta::NeighborSolicitation {
4453 lookup_addr: I::LOOKUP_ADDR1,
4454 remote_link_addr: None,
4455 },
4456 Vec::new()
4457 )
4458 ]
4459 );
4460
4461 let next_backoff_timer = |core_ctx: &mut FakeCoreCtxImpl<I>, probes_sent| {
4462 UnreachableMode::Backoff {
4463 probes_sent: NonZeroU32::new(probes_sent).unwrap(),
4464 packet_sent: false,
4465 }
4466 .next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state)
4467 .get()
4468 };
4469
4470 const ITERATIONS: u8 = 2;
4471 for i in 1..ITERATIONS {
4472 let probes_sent = u32::from(i);
4473
4474 assert_eq!(
4477 NudHandler::send_ip_packet_to_neighbor(
4478 &mut core_ctx,
4479 &mut bindings_ctx,
4480 &FakeLinkDeviceId,
4481 I::LOOKUP_ADDR1,
4482 Buf::new([BODY + i], ..),
4483 FakeTxMetadata::default(),
4484 ),
4485 Ok(())
4486 );
4487 assert_eq!(
4488 core_ctx.inner.take_frames(),
4489 [(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY + i])]
4490 );
4491
4492 assert_eq!(
4497 bindings_ctx.trigger_timers_for(
4498 next_backoff_timer(&mut core_ctx, probes_sent),
4499 &mut core_ctx,
4500 ),
4501 [timer_id]
4502 );
4503 assert_neighbor_probe_sent(&mut core_ctx, None);
4504 bindings_ctx.timers.assert_timers_installed([(
4505 timer_id,
4506 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, probes_sent + 1),
4507 )]);
4508 }
4509
4510 let current_timer = next_backoff_timer(&mut core_ctx, u32::from(ITERATIONS));
4513 assert_eq!(bindings_ctx.trigger_timers_for(current_timer, &mut core_ctx,), [timer_id]);
4514 assert_eq!(core_ctx.inner.take_frames(), []);
4515 bindings_ctx.timers.assert_no_timers_installed();
4516
4517 assert_eq!(
4520 NudHandler::send_ip_packet_to_neighbor(
4521 &mut core_ctx,
4522 &mut bindings_ctx,
4523 &FakeLinkDeviceId,
4524 I::LOOKUP_ADDR1,
4525 Buf::new([BODY], ..),
4526 FakeTxMetadata::default(),
4527 ),
4528 Ok(())
4529 );
4530 assert_eq!(
4531 core_ctx.inner.take_frames(),
4532 [
4533 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4534 (
4535 FakeNudMessageMeta::NeighborSolicitation {
4536 lookup_addr: I::LOOKUP_ADDR1,
4537 remote_link_addr: None,
4538 },
4539 Vec::new()
4540 )
4541 ]
4542 );
4543 bindings_ctx.timers.assert_timers_installed([(
4544 timer_id,
4545 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, 1),
4546 )]);
4547 }
4548
4549 #[ip_test(I)]
4550 #[test_case(true; "solicited confirmation")]
4551 #[test_case(false; "unsolicited confirmation")]
4552 fn confirmation_should_not_create_entry<I: TestIpExt>(solicited_flag: bool) {
4553 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4554
4555 let link_address = Some(LINK_ADDR1);
4556 NudHandler::handle_neighbor_update(
4557 &mut core_ctx,
4558 &mut bindings_ctx,
4559 &FakeLinkDeviceId,
4560 I::LOOKUP_ADDR1,
4561 DynamicNeighborUpdateSource::Confirmation {
4562 link_address,
4563 flags: ConfirmationFlags { solicited_flag, override_flag: false },
4564 },
4565 );
4566 assert_eq!(core_ctx.nud.state.neighbors, HashMap::new());
4567 }
4568
4569 #[ip_test(I)]
4570 #[test_case(true; "set_with_dynamic")]
4571 #[test_case(false; "set_with_static")]
4572 fn pending_frames<I: TestIpExt>(dynamic: bool) {
4573 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4574 assert_eq!(core_ctx.inner.take_frames(), []);
4575
4576 const MAX_PENDING_FRAMES_U8: u8 = MAX_PENDING_FRAMES as u8;
4580 let expected_pending_frames = (0..MAX_PENDING_FRAMES_U8)
4581 .map(|i| (Buf::new(vec![i], ..), FakeTxMetadata::default()))
4582 .collect::<VecDeque<_>>();
4583
4584 for (body, meta) in expected_pending_frames.iter() {
4585 assert_eq!(
4586 NudHandler::send_ip_packet_to_neighbor(
4587 &mut core_ctx,
4588 &mut bindings_ctx,
4589 &FakeLinkDeviceId,
4590 I::LOOKUP_ADDR1,
4591 body.clone(),
4592 meta.clone(),
4593 ),
4594 Ok(())
4595 );
4596 }
4597 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4598 assert_neighbor_probe_sent(&mut core_ctx, None);
4600 assert_neighbor_state(
4601 &core_ctx,
4602 &mut bindings_ctx,
4603 DynamicNeighborState::Incomplete(Incomplete {
4604 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4605 pending_frames: expected_pending_frames.clone(),
4606 notifiers: Vec::new(),
4607 _marker: PhantomData,
4608 }),
4609 Some(ExpectedEvent::Added),
4610 );
4611
4612 assert_eq!(
4614 NudHandler::send_ip_packet_to_neighbor(
4615 &mut core_ctx,
4616 &mut bindings_ctx,
4617 &FakeLinkDeviceId,
4618 I::LOOKUP_ADDR1,
4619 Buf::new([123], ..),
4620 FakeTxMetadata::default(),
4621 ),
4622 Ok(())
4623 );
4624 assert_eq!(core_ctx.inner.take_frames(), []);
4625 assert_neighbor_state(
4626 &core_ctx,
4627 &mut bindings_ctx,
4628 DynamicNeighborState::Incomplete(Incomplete {
4629 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4630 pending_frames: expected_pending_frames.clone(),
4631 notifiers: Vec::new(),
4632 _marker: PhantomData,
4633 }),
4634 None,
4635 );
4636
4637 if dynamic {
4639 NudHandler::handle_neighbor_update(
4640 &mut core_ctx,
4641 &mut bindings_ctx,
4642 &FakeLinkDeviceId,
4643 I::LOOKUP_ADDR1,
4644 DynamicNeighborUpdateSource::Confirmation {
4645 link_address: Some(LINK_ADDR1),
4646 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4647 },
4648 );
4649 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4650 &mut bindings_ctx,
4651 [(
4652 I::LOOKUP_ADDR1,
4653 NudEvent::ReachableTime,
4654 core_ctx.inner.base_reachable_time().get(),
4655 )],
4656 );
4657 let last_confirmed_at = bindings_ctx.now();
4658 assert_neighbor_state(
4659 &core_ctx,
4660 &mut bindings_ctx,
4661 DynamicNeighborState::Reachable(Reachable {
4662 link_address: LINK_ADDR1,
4663 last_confirmed_at,
4664 }),
4665 Some(ExpectedEvent::Changed),
4666 );
4667 } else {
4668 init_static_neighbor(
4669 &mut core_ctx,
4670 &mut bindings_ctx,
4671 LINK_ADDR1,
4672 ExpectedEvent::Changed,
4673 );
4674 bindings_ctx.timers.assert_no_timers_installed();
4675 }
4676 assert_eq!(
4677 core_ctx.inner.take_frames(),
4678 expected_pending_frames
4679 .into_iter()
4680 .map(|(p, FakeTxMetadata)| (
4681 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4682 p.as_ref().to_vec()
4683 ))
4684 .collect::<Vec<_>>()
4685 );
4686 }
4687
4688 #[ip_test(I)]
4689 fn static_neighbor<I: TestIpExt>() {
4690 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4691
4692 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4693 bindings_ctx.timers.assert_no_timers_installed();
4694 assert_eq!(core_ctx.inner.take_frames(), []);
4695 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4696
4697 NudHandler::handle_neighbor_update(
4699 &mut core_ctx,
4700 &mut bindings_ctx,
4701 &FakeLinkDeviceId,
4702 I::LOOKUP_ADDR1,
4703 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4704 );
4705 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4706
4707 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4708
4709 let neighbors = &core_ctx.nud.state.neighbors;
4710 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4711 }
4712
4713 #[ip_test(I)]
4714 fn dynamic_neighbor<I: TestIpExt>() {
4715 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4716
4717 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4718 bindings_ctx.timers.assert_no_timers_installed();
4719 assert_eq!(core_ctx.inner.take_frames(), []);
4720 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4721
4722 NudHandler::handle_neighbor_update(
4724 &mut core_ctx,
4725 &mut bindings_ctx,
4726 &FakeLinkDeviceId,
4727 I::LOOKUP_ADDR1,
4728 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4729 );
4730 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR2);
4731 assert_eq!(core_ctx.inner.take_frames(), []);
4732 assert_neighbor_state(
4733 &core_ctx,
4734 &mut bindings_ctx,
4735 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4736 Some(ExpectedEvent::Changed),
4737 );
4738
4739 init_static_neighbor_with_ip(
4741 &mut core_ctx,
4742 &mut bindings_ctx,
4743 I::LOOKUP_ADDR1,
4744 LINK_ADDR3,
4745 ExpectedEvent::Changed,
4746 );
4747 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR3);
4748 assert_eq!(core_ctx.inner.take_frames(), []);
4749 }
4750
4751 #[ip_test(I)]
4752 fn send_solicitation_on_lookup<I: TestIpExt>() {
4753 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4754 bindings_ctx.timers.assert_no_timers_installed();
4755 assert_eq!(core_ctx.inner.take_frames(), []);
4756
4757 let mut pending_frames = VecDeque::new();
4758
4759 queue_ip_packet_to_unresolved_neighbor(
4760 &mut core_ctx,
4761 &mut bindings_ctx,
4762 I::LOOKUP_ADDR1,
4763 &mut pending_frames,
4764 1,
4765 true, );
4767 assert_neighbor_probe_sent(&mut core_ctx, None);
4768
4769 queue_ip_packet_to_unresolved_neighbor(
4770 &mut core_ctx,
4771 &mut bindings_ctx,
4772 I::LOOKUP_ADDR1,
4773 &mut pending_frames,
4774 2,
4775 false, );
4777 assert_eq!(core_ctx.inner.take_frames(), []);
4778
4779 NudHandler::handle_neighbor_update(
4781 &mut core_ctx,
4782 &mut bindings_ctx,
4783 &FakeLinkDeviceId,
4784 I::LOOKUP_ADDR1,
4785 DynamicNeighborUpdateSource::Confirmation {
4786 link_address: Some(LINK_ADDR1),
4787 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4788 },
4789 );
4790 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4791
4792 let now = bindings_ctx.now();
4793 assert_neighbor_state(
4794 &core_ctx,
4795 &mut bindings_ctx,
4796 DynamicNeighborState::Reachable(Reachable {
4797 link_address: LINK_ADDR1,
4798 last_confirmed_at: now,
4799 }),
4800 Some(ExpectedEvent::Changed),
4801 );
4802 assert_eq!(
4803 core_ctx.inner.take_frames(),
4804 pending_frames
4805 .into_iter()
4806 .map(|f| (
4807 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4808 f.as_ref().to_vec(),
4809 ))
4810 .collect::<Vec<_>>()
4811 );
4812 }
4813
4814 #[ip_test(I)]
4815 fn solicitation_failure_in_incomplete<I: TestIpExt>() {
4816 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4817 bindings_ctx.timers.assert_no_timers_installed();
4818 assert_eq!(core_ctx.inner.take_frames(), []);
4819
4820 let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
4821
4822 let timer_id = NudTimerId::neighbor();
4823
4824 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4825 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4826
4827 for i in 1..=max_multicast_solicit {
4828 assert_neighbor_state(
4829 &core_ctx,
4830 &mut bindings_ctx,
4831 DynamicNeighborState::Incomplete(Incomplete {
4832 transmit_counter: NonZeroU16::new(max_multicast_solicit - i),
4833 pending_frames: pending_frames
4834 .iter()
4835 .cloned()
4836 .map(|b| (b, FakeTxMetadata::default()))
4837 .collect(),
4838 notifiers: Vec::new(),
4839 _marker: PhantomData,
4840 }),
4841 None,
4842 );
4843
4844 bindings_ctx
4845 .timers
4846 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4847 assert_neighbor_probe_sent(&mut core_ctx, None);
4848
4849 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4850 }
4851
4852 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1);
4854 bindings_ctx.timers.assert_no_timers_installed();
4855
4856 assert_eq!(core_ctx.inner.take_frames(), []);
4860 assert_eq!(core_ctx.counters().as_ref().icmp_dest_unreachable_dropped.get(), 1);
4861 }
4862
4863 #[ip_test(I)]
4864 fn solicitation_failure_in_probe<I: TestIpExt>() {
4865 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4866 bindings_ctx.timers.assert_no_timers_installed();
4867 assert_eq!(core_ctx.inner.take_frames(), []);
4868
4869 init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
4870
4871 let timer_id = NudTimerId::neighbor();
4872 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4873 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4874 for i in 1..=max_unicast_solicit {
4875 assert_neighbor_state(
4876 &core_ctx,
4877 &mut bindings_ctx,
4878 DynamicNeighborState::Probe(Probe {
4879 transmit_counter: NonZeroU16::new(max_unicast_solicit - i),
4880 link_address: LINK_ADDR1,
4881 }),
4882 None,
4883 );
4884
4885 bindings_ctx
4886 .timers
4887 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4888 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4889
4890 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4891 }
4892
4893 assert_neighbor_state(
4894 &core_ctx,
4895 &mut bindings_ctx,
4896 DynamicNeighborState::Unreachable(Unreachable {
4897 link_address: LINK_ADDR1,
4898 mode: UnreachableMode::WaitingForPacketSend,
4899 }),
4900 Some(ExpectedEvent::Changed),
4901 );
4902 bindings_ctx.timers.assert_no_timers_installed();
4903 assert_eq!(core_ctx.inner.take_frames(), []);
4904 }
4905
4906 #[ip_test(I)]
4907 fn flush_entries<I: TestIpExt>() {
4908 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4909 bindings_ctx.timers.assert_no_timers_installed();
4910 assert_eq!(core_ctx.inner.take_frames(), []);
4911
4912 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4913 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR2, LINK_ADDR2);
4914 let pending_frames = init_incomplete_neighbor_with_ip(
4915 &mut core_ctx,
4916 &mut bindings_ctx,
4917 I::LOOKUP_ADDR3,
4918 true,
4919 );
4920 let pending_frames =
4921 pending_frames.into_iter().map(|b| (b, FakeTxMetadata::default())).collect();
4922
4923 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4924 assert_eq!(
4925 core_ctx.nud.state.neighbors,
4926 HashMap::from([
4927 (I::LOOKUP_ADDR1, NeighborState::Static(LINK_ADDR1)),
4928 (
4929 I::LOOKUP_ADDR2,
4930 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
4931 link_address: LINK_ADDR2,
4932 })),
4933 ),
4934 (
4935 I::LOOKUP_ADDR3,
4936 NeighborState::Dynamic(DynamicNeighborState::Incomplete(Incomplete {
4937 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4938 pending_frames,
4939 notifiers: Vec::new(),
4940 _marker: PhantomData,
4941 })),
4942 ),
4943 ]),
4944 );
4945 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4946 &mut bindings_ctx,
4947 [(I::LOOKUP_ADDR3, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
4948 );
4949
4950 NudHandler::flush(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId);
4952 let neighbors = &core_ctx.nud.state.neighbors;
4953 assert!(neighbors.is_empty(), "neighbor table should be empty: {:?}", neighbors);
4954 assert_eq!(
4955 bindings_ctx.take_events().into_iter().collect::<HashSet<_>>(),
4956 [I::LOOKUP_ADDR1, I::LOOKUP_ADDR2, I::LOOKUP_ADDR3]
4957 .into_iter()
4958 .map(|addr| { Event::removed(&FakeLinkDeviceId, addr, bindings_ctx.now()) })
4959 .collect(),
4960 );
4961 bindings_ctx.timers.assert_no_timers_installed();
4962 }
4963
4964 #[ip_test(I)]
4965 fn delete_dynamic_entry<I: TestIpExt>() {
4966 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4967 bindings_ctx.timers.assert_no_timers_installed();
4968 assert_eq!(core_ctx.inner.take_frames(), []);
4969
4970 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4971 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4972
4973 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4974
4975 let neighbors = &core_ctx.nud.state.neighbors;
4977 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4978 bindings_ctx.timers.assert_no_timers_installed();
4979 }
4980
4981 #[ip_test(I)]
4982 #[test_case(InitialState::Reachable; "reachable neighbor")]
4983 #[test_case(InitialState::Stale; "stale neighbor")]
4984 #[test_case(InitialState::Delay; "delay neighbor")]
4985 #[test_case(InitialState::Probe; "probe neighbor")]
4986 #[test_case(InitialState::Unreachable; "unreachable neighbor")]
4987 fn resolve_cached_linked_addr<I: TestIpExt>(initial_state: InitialState) {
4988 let mut ctx = new_context::<I>();
4989 ctx.bindings_ctx.timers.assert_no_timers_installed();
4990 assert_eq!(ctx.core_ctx.inner.take_frames(), []);
4991
4992 let _ = init_neighbor_in_state(&mut ctx.core_ctx, &mut ctx.bindings_ctx, initial_state);
4993
4994 let link_addr = assert_matches!(
4995 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4996 &FakeLinkDeviceId,
4997 &I::LOOKUP_ADDR1,
4998 ),
4999 LinkResolutionResult::Resolved(addr) => addr
5000 );
5001 assert_eq!(link_addr, LINK_ADDR1);
5002 if initial_state == InitialState::Stale {
5003 assert_eq!(
5004 ctx.bindings_ctx.take_events(),
5005 [Event::changed(
5006 &FakeLinkDeviceId,
5007 EventState::Dynamic(EventDynamicState::Delay(LINK_ADDR1)),
5008 I::LOOKUP_ADDR1,
5009 ctx.bindings_ctx.now(),
5010 )],
5011 );
5012 }
5013 }
5014
5015 enum ResolutionSuccess {
5016 Confirmation,
5017 StaticEntryAdded,
5018 }
5019
5020 #[ip_test(I)]
5021 #[test_case(ResolutionSuccess::Confirmation; "incomplete entry timed out")]
5022 #[test_case(ResolutionSuccess::StaticEntryAdded; "incomplete entry removed from table")]
5023 fn dynamic_neighbor_resolution_success<I: TestIpExt>(reason: ResolutionSuccess) {
5024 let mut ctx = new_context::<I>();
5025
5026 let observers = (0..10)
5027 .map(|_| {
5028 let observer = assert_matches!(
5029 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
5030 &FakeLinkDeviceId,
5031 &I::LOOKUP_ADDR1,
5032 ),
5033 LinkResolutionResult::Pending(observer) => observer
5034 );
5035 assert_eq!(*observer.lock(), None);
5036 observer
5037 })
5038 .collect::<Vec<_>>();
5039 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
5040 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5041
5042 assert_neighbor_state(
5045 core_ctx,
5046 bindings_ctx,
5047 DynamicNeighborState::Incomplete(Incomplete {
5048 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5049 pending_frames: VecDeque::new(),
5050 notifiers: Vec::new(),
5052 _marker: PhantomData,
5053 }),
5054 Some(ExpectedEvent::Added),
5055 );
5056 assert_neighbor_probe_sent(core_ctx, None);
5057
5058 match reason {
5059 ResolutionSuccess::Confirmation => {
5060 NudHandler::handle_neighbor_update(
5062 core_ctx,
5063 bindings_ctx,
5064 &FakeLinkDeviceId,
5065 I::LOOKUP_ADDR1,
5066 DynamicNeighborUpdateSource::Confirmation {
5067 link_address: Some(LINK_ADDR1),
5068 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
5069 },
5070 );
5071 let now = bindings_ctx.now();
5072 assert_neighbor_state(
5073 core_ctx,
5074 bindings_ctx,
5075 DynamicNeighborState::Reachable(Reachable {
5076 link_address: LINK_ADDR1,
5077 last_confirmed_at: now,
5078 }),
5079 Some(ExpectedEvent::Changed),
5080 );
5081 }
5082 ResolutionSuccess::StaticEntryAdded => {
5083 init_static_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, ExpectedEvent::Changed);
5084 assert_eq!(
5085 core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
5086 Some(&NeighborState::Static(LINK_ADDR1))
5087 );
5088 }
5089 }
5090
5091 for observer in observers {
5093 assert_eq!(*observer.lock(), Some(Ok(LINK_ADDR1)));
5094 }
5095 }
5096
5097 enum ResolutionFailure {
5098 Timeout,
5099 Removed,
5100 }
5101
5102 #[ip_test(I)]
5103 #[test_case(ResolutionFailure::Timeout; "incomplete entry timed out")]
5104 #[test_case(ResolutionFailure::Removed; "incomplete entry removed from table")]
5105 fn dynamic_neighbor_resolution_failure<I: TestIpExt>(reason: ResolutionFailure) {
5106 let mut ctx = new_context::<I>();
5107
5108 let observers = (0..10)
5109 .map(|_| {
5110 let observer = assert_matches!(
5111 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
5112 &FakeLinkDeviceId,
5113 &I::LOOKUP_ADDR1,
5114 ),
5115 LinkResolutionResult::Pending(observer) => observer
5116 );
5117 assert_eq!(*observer.lock(), None);
5118 observer
5119 })
5120 .collect::<Vec<_>>();
5121
5122 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
5123 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5124
5125 assert_neighbor_state(
5128 core_ctx,
5129 bindings_ctx,
5130 DynamicNeighborState::Incomplete(Incomplete {
5131 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5132 pending_frames: VecDeque::new(),
5133 notifiers: Vec::new(),
5135 _marker: PhantomData,
5136 }),
5137 Some(ExpectedEvent::Added),
5138 );
5139 assert_neighbor_probe_sent(core_ctx, None);
5140
5141 match reason {
5142 ResolutionFailure::Timeout => {
5143 for _ in 1..=max_multicast_solicit {
5146 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
5147 assert_eq!(
5148 bindings_ctx.trigger_timers_for(retrans_timer, core_ctx),
5149 [NudTimerId::neighbor()]
5150 );
5151 }
5152 }
5153 ResolutionFailure::Removed => {
5154 NudHandler::flush(core_ctx, bindings_ctx, &FakeLinkDeviceId);
5156 }
5157 }
5158
5159 assert_neighbor_removed_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1);
5160 for observer in observers {
5162 assert_eq!(*observer.lock(), Some(Err(AddressResolutionFailed)));
5163 }
5164 }
5165
5166 #[ip_test(I)]
5167 #[test_case(InitialState::Incomplete, false; "incomplete neighbor")]
5168 #[test_case(InitialState::Reachable, true; "reachable neighbor")]
5169 #[test_case(InitialState::Stale, true; "stale neighbor")]
5170 #[test_case(InitialState::Delay, true; "delay neighbor")]
5171 #[test_case(InitialState::Probe, true; "probe neighbor")]
5172 #[test_case(InitialState::Unreachable, true; "unreachable neighbor")]
5173 fn upper_layer_confirmation<I: TestIpExt>(
5174 initial_state: InitialState,
5175 should_transition_to_reachable: bool,
5176 ) {
5177 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5178 let base_reachable_time = core_ctx.inner.base_reachable_time().get();
5179
5180 let initial = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
5181
5182 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5183
5184 if !should_transition_to_reachable {
5185 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial, None);
5186 return;
5187 }
5188
5189 let now = bindings_ctx.now();
5191 assert_neighbor_state(
5192 &core_ctx,
5193 &mut bindings_ctx,
5194 DynamicNeighborState::Reachable(Reachable {
5195 link_address: LINK_ADDR1,
5196 last_confirmed_at: now,
5197 }),
5198 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
5199 );
5200 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5201 &mut bindings_ctx,
5202 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time)],
5203 );
5204
5205 bindings_ctx.timers.instant.sleep(base_reachable_time / 2);
5209 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5210 let now = bindings_ctx.now();
5211 assert_neighbor_state(
5212 &core_ctx,
5213 &mut bindings_ctx,
5214 DynamicNeighborState::Reachable(Reachable {
5215 link_address: LINK_ADDR1,
5216 last_confirmed_at: now,
5217 }),
5218 None,
5219 );
5220 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5221 &mut bindings_ctx,
5222 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5223 );
5224
5225 assert_eq!(
5228 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5229 [NudTimerId::neighbor()]
5230 );
5231 let now = bindings_ctx.now();
5232 assert_neighbor_state(
5233 &core_ctx,
5234 &mut bindings_ctx,
5235 DynamicNeighborState::Reachable(Reachable {
5236 link_address: LINK_ADDR1,
5237 last_confirmed_at: now - base_reachable_time / 2,
5238 }),
5239 None,
5240 );
5241
5242 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5243 &mut bindings_ctx,
5244 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5245 );
5246
5247 assert_eq!(
5250 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5251 [NudTimerId::neighbor()]
5252 );
5253 assert_neighbor_state(
5254 &core_ctx,
5255 &mut bindings_ctx,
5256 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
5257 Some(ExpectedEvent::Changed),
5258 );
5259 bindings_ctx.timers.assert_no_timers_installed();
5260 }
5261
5262 fn generate_ip_addr<I: Ip>(i: usize) -> SpecifiedAddr<I::Addr> {
5263 I::map_ip_out(
5264 i,
5265 |i| {
5266 let start = u32::from_be_bytes(net_ip_v4!("192.168.0.1").ipv4_bytes());
5267 let bytes = (start + u32::try_from(i).unwrap()).to_be_bytes();
5268 SpecifiedAddr::new(Ipv4Addr::new(bytes)).unwrap()
5269 },
5270 |i| {
5271 let start = u128::from_be_bytes(net_ip_v6!("fe80::1").ipv6_bytes());
5272 let bytes = (start + u128::try_from(i).unwrap()).to_be_bytes();
5273 SpecifiedAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap()
5274 },
5275 )
5276 }
5277
5278 #[ip_test(I)]
5279 fn garbage_collection_retains_static_entries<I: TestIpExt>() {
5280 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5281
5282 for i in 0..GC_THRESHOLD * 2 {
5286 if i % 2 == 0 {
5287 init_stale_neighbor_with_ip(
5288 &mut core_ctx,
5289 &mut bindings_ctx,
5290 generate_ip_addr::<I>(i),
5291 LINK_ADDR1,
5292 );
5293 } else {
5294 init_static_neighbor_with_ip(
5295 &mut core_ctx,
5296 &mut bindings_ctx,
5297 generate_ip_addr::<I>(i),
5298 LINK_ADDR1,
5299 ExpectedEvent::Added,
5300 );
5301 }
5302 }
5303 assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD * 2);
5304
5305 assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5307 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5308 assert_eq!(core_ctx.nud.state.gc_state.is_dirty, false);
5309 for event in bindings_ctx.take_events() {
5310 assert_matches!(event, Event {
5311 device,
5312 addr: _,
5313 kind,
5314 at,
5315 } => {
5316 assert_eq!(kind, EventKind::Removed);
5317 assert_eq!(device, FakeLinkDeviceId);
5318 assert_eq!(at, bindings_ctx.now());
5319 });
5320 }
5321 assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5322 for (_, neighbor) in core_ctx.nud.state.neighbors {
5323 assert_matches!(neighbor, NeighborState::Static(_));
5324 }
5325 }
5326
5327 #[ip_test(I)]
5328 fn garbage_collection_retains_in_use_entries<I: TestIpExt>() {
5329 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5330
5331 for i in 0..GC_THRESHOLD - 1 {
5333 init_static_neighbor_with_ip(
5334 &mut core_ctx,
5335 &mut bindings_ctx,
5336 generate_ip_addr::<I>(i),
5337 LINK_ADDR1,
5338 ExpectedEvent::Added,
5339 );
5340 }
5341
5342 let stale_entry = generate_ip_addr::<I>(GC_THRESHOLD - 1);
5344 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry, LINK_ADDR1);
5345 let reachable_entry = generate_ip_addr::<I>(GC_THRESHOLD);
5347 init_reachable_neighbor_with_ip(
5348 &mut core_ctx,
5349 &mut bindings_ctx,
5350 reachable_entry,
5351 LINK_ADDR1,
5352 );
5353
5354 assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5356 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5357 assert_eq!(core_ctx.nud.state.gc_state.is_dirty, false);
5358 super::testutil::assert_dynamic_neighbor_state(
5359 &mut core_ctx,
5360 FakeLinkDeviceId,
5361 reachable_entry,
5362 DynamicNeighborState::Reachable(Reachable {
5363 link_address: LINK_ADDR1,
5364 last_confirmed_at: bindings_ctx.now(),
5365 }),
5366 );
5367 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry);
5368 }
5369
5370 #[ip_test(I)]
5371 fn is_still_dirty_after_garbage_collection<I: TestIpExt>() {
5372 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5373
5374 for i in 0..GC_THRESHOLD + 1 {
5376 init_stale_neighbor_with_ip(
5377 &mut core_ctx,
5378 &mut bindings_ctx,
5379 generate_ip_addr::<I>(i),
5380 LINK_ADDR1,
5381 );
5382 }
5383
5384 assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5387 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5388 assert_eq!(core_ctx.nud.state.gc_state.is_dirty, true);
5389 assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5390 let events = bindings_ctx.take_events();
5391 let removed_event = assert_matches!(&events[..], [event] => event);
5392 assert_eq!(removed_event.kind, EventKind::Removed);
5393 }
5394
5395 #[ip_test(I)]
5396 fn garbage_collection_triggered_on_new_stale_entry<I: TestIpExt>() {
5397 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5398 core_ctx.nud.state.gc_state.last_gc = Some(bindings_ctx.now());
5400
5401 for i in 0..GC_THRESHOLD {
5403 init_static_neighbor_with_ip(
5404 &mut core_ctx,
5405 &mut bindings_ctx,
5406 generate_ip_addr::<I>(i),
5407 LINK_ADDR1,
5408 ExpectedEvent::Added,
5409 );
5410 }
5411
5412 init_stale_neighbor_with_ip(
5415 &mut core_ctx,
5416 &mut bindings_ctx,
5417 generate_ip_addr::<I>(GC_THRESHOLD + 1),
5418 LINK_ADDR1,
5419 );
5420 let expected_gc_time = bindings_ctx.now() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5421 bindings_ctx
5422 .timers
5423 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5424
5425 bindings_ctx.timers.instant.sleep(ONE_SECOND.get());
5429 init_stale_neighbor_with_ip(
5430 &mut core_ctx,
5431 &mut bindings_ctx,
5432 generate_ip_addr::<I>(GC_THRESHOLD + 2),
5433 LINK_ADDR1,
5434 );
5435 bindings_ctx
5436 .timers
5437 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5438 }
5439
5440 #[ip_test(I)]
5441 fn garbage_collection_triggered_on_transition_to_unreachable<I: TestIpExt>() {
5442 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5443 core_ctx.nud.state.gc_state.last_gc = Some(bindings_ctx.now());
5445
5446 for i in 0..GC_THRESHOLD {
5448 init_static_neighbor_with_ip(
5449 &mut core_ctx,
5450 &mut bindings_ctx,
5451 generate_ip_addr::<I>(i),
5452 LINK_ADDR1,
5453 ExpectedEvent::Added,
5454 );
5455 }
5456 assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5457
5458 init_unreachable_neighbor_with_ip(
5461 &mut core_ctx,
5462 &mut bindings_ctx,
5463 generate_ip_addr::<I>(GC_THRESHOLD),
5464 LINK_ADDR1,
5465 );
5466 let expected_gc_time =
5467 core_ctx.nud.state.gc_state.last_gc.unwrap() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5468 bindings_ctx
5469 .timers
5470 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5471
5472 init_unreachable_neighbor_with_ip(
5475 &mut core_ctx,
5476 &mut bindings_ctx,
5477 generate_ip_addr::<I>(GC_THRESHOLD + 1),
5478 LINK_ADDR1,
5479 );
5480 bindings_ctx
5481 .timers
5482 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5483 }
5484
5485 #[ip_test(I)]
5486 fn garbage_collection_not_triggered_on_new_incomplete_entry<I: TestIpExt>() {
5487 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5488
5489 for i in 0..GC_THRESHOLD {
5491 init_static_neighbor_with_ip(
5492 &mut core_ctx,
5493 &mut bindings_ctx,
5494 generate_ip_addr::<I>(i),
5495 LINK_ADDR1,
5496 ExpectedEvent::Added,
5497 );
5498 }
5499 assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD);
5500
5501 let _: VecDeque<Buf<Vec<u8>>> = init_incomplete_neighbor_with_ip(
5502 &mut core_ctx,
5503 &mut bindings_ctx,
5504 generate_ip_addr::<I>(GC_THRESHOLD),
5505 true,
5506 );
5507 assert_eq!(
5508 bindings_ctx.timers.scheduled_instant(&mut core_ctx.nud.state.timer_heap.gc),
5509 None
5510 );
5511 }
5512
5513 #[ip_test(I)]
5514 fn confirmation_processed_even_if_no_target_link_layer_addr<I: TestIpExt>() {
5515 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5516
5517 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
5519
5520 NudHandler::handle_neighbor_update(
5524 &mut core_ctx,
5525 &mut bindings_ctx,
5526 &FakeLinkDeviceId,
5527 I::LOOKUP_ADDR1,
5528 DynamicNeighborUpdateSource::Confirmation {
5529 link_address: None,
5530 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
5531 },
5532 );
5533 let now = bindings_ctx.now();
5534 assert_neighbor_state(
5535 &core_ctx,
5536 &mut bindings_ctx,
5537 DynamicNeighborState::Reachable(Reachable {
5538 link_address: LINK_ADDR1,
5539 last_confirmed_at: now,
5540 }),
5541 Some(ExpectedEvent::Changed),
5542 );
5543 }
5544
5545 #[ip_test(I)]
5546 #[test_case(InitialState::Stale; "stale")]
5547 #[test_case(InitialState::Reachable; "reachable")]
5548 #[test_case(InitialState::Delay; "delay")]
5549 #[test_case(InitialState::Unreachable; "unreachable")]
5550 fn enter_probe_from_dynamic_state<I: TestIpExt>(initial: InitialState) {
5551 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5552
5553 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial);
5554
5555 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5556 let result = neighbor.enter_probe(
5557 &mut core_ctx.inner.state,
5558 &mut bindings_ctx,
5559 &mut core_ctx.nud.state.timer_heap,
5560 I::LOOKUP_ADDR1,
5561 &FakeLinkDeviceId,
5562 );
5563
5564 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5565 assert_matches!(result, Ok(Some(LINK_ADDR1)));
5566 assert_neighbor_state(
5567 &core_ctx,
5568 &mut bindings_ctx,
5569 DynamicNeighborState::Probe(Probe {
5570 link_address: LINK_ADDR1,
5571 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5572 }),
5573 Some(ExpectedEvent::Changed),
5574 );
5575 }
5576
5577 #[ip_test(I)]
5578 fn enter_probe_from_static_state<I: TestIpExt>() {
5579 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5580
5581 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
5582
5583 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5584 let result = neighbor.enter_probe(
5585 &mut core_ctx.inner.state,
5586 &mut bindings_ctx,
5587 &mut core_ctx.nud.state.timer_heap,
5588 I::LOOKUP_ADDR1,
5589 &FakeLinkDeviceId,
5590 );
5591
5592 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5593 assert_matches!(result, Ok(Some(LINK_ADDR1)));
5594 assert_neighbor_state(
5595 &core_ctx,
5596 &mut bindings_ctx,
5597 DynamicNeighborState::Probe(Probe {
5598 link_address: LINK_ADDR1,
5599 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5600 }),
5601 Some(ExpectedEvent::Changed),
5602 );
5603 }
5604
5605 #[ip_test(I)]
5606 fn enter_probe_from_probe_state<I: TestIpExt>() {
5607 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5608
5609 init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
5610
5611 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5612 let result = neighbor.enter_probe(
5613 &mut core_ctx.inner.state,
5614 &mut bindings_ctx,
5615 &mut core_ctx.nud.state.timer_heap,
5616 I::LOOKUP_ADDR1,
5617 &FakeLinkDeviceId,
5618 );
5619
5620 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5621 assert_matches!(result, Ok(None)); assert_neighbor_state(
5623 &core_ctx,
5624 &mut bindings_ctx,
5625 DynamicNeighborState::Probe(Probe {
5626 link_address: LINK_ADDR1,
5627 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5628 }),
5629 None, );
5631 }
5632
5633 #[ip_test(I)]
5634 fn enter_probe_from_incomplete_state<I: TestIpExt>() {
5635 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5636
5637 let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
5638
5639 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5640 let result = neighbor.enter_probe(
5641 &mut core_ctx.inner.state,
5642 &mut bindings_ctx,
5643 &mut core_ctx.nud.state.timer_heap,
5644 I::LOOKUP_ADDR1,
5645 &FakeLinkDeviceId,
5646 );
5647
5648 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5649 assert_matches!(result, Err(EnterProbeError::LinkAddressUnknown));
5650 assert_neighbor_state(
5651 &core_ctx,
5652 &mut bindings_ctx,
5653 DynamicNeighborState::Incomplete(Incomplete {
5654 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5655 pending_frames: pending_frames
5656 .iter()
5657 .cloned()
5658 .map(|b| (b, FakeTxMetadata::default()))
5659 .collect(),
5660 notifiers: Vec::new(),
5661 _marker: PhantomData,
5662 }),
5663 None, );
5665 }
5666
5667 enum InsertMethod {
5670 ResolveLinkAddr,
5671 InsertStaticEntry,
5672 NeighborUpdate,
5673 SendIpPacket,
5674 }
5675
5676 impl InsertMethod {
5677 fn insert<I: TestIpExt>(
5678 &self,
5679 context: &mut CtxPair<&mut FakeCoreCtxImpl<I>, &mut FakeBindingsCtxImpl<I>>,
5680 link_address: UnicastAddr<FakeLinkAddress>,
5681 ip: SpecifiedAddr<I::Addr>,
5682 expect_err: bool,
5683 ) {
5684 match self {
5685 Self::ResolveLinkAddr => {
5686 let result =
5687 NeighborApi::new(context).resolve_link_addr(&FakeLinkDeviceId, &ip);
5688 let pending = assert_matches!(
5689 result, LinkResolutionResult::Pending(pending) => pending
5690 );
5691 if expect_err {
5692 assert_matches!(
5693 pending.lock().as_ref(),
5694 Some(Err(AddressResolutionFailed))
5695 );
5696 } else {
5697 assert_matches!(pending.lock().as_ref(), None, "should not be notified");
5698 }
5699 }
5700 Self::InsertStaticEntry => {
5701 let result = NeighborApi::new(context).insert_static_entry(
5702 &FakeLinkDeviceId,
5703 *ip,
5704 link_address,
5705 );
5706 if expect_err {
5707 assert_eq!(result, Err(StaticNeighborInsertionError::TableFull))
5708 } else {
5709 assert_eq!(result, Ok(()))
5710 }
5711 }
5712 Self::NeighborUpdate => {
5713 let CtxPair { core_ctx, bindings_ctx } = context;
5714 NudHandler::handle_neighbor_update(
5715 *core_ctx,
5716 *bindings_ctx,
5717 &FakeLinkDeviceId,
5718 ip,
5719 DynamicNeighborUpdateSource::Probe { link_address },
5720 );
5721 }
5724 Self::SendIpPacket => {
5725 let CtxPair { core_ctx, bindings_ctx } = context;
5726 let packet = Buf::new([0; 10], ..);
5727 let result = NudHandler::send_ip_packet_to_neighbor(
5728 *core_ctx,
5729 *bindings_ctx,
5730 &FakeLinkDeviceId,
5731 ip,
5732 packet,
5733 FakeTxMetadata::default(),
5734 );
5735 if expect_err {
5736 assert_matches!(
5737 result,
5738 Err(ErrorAndSerializer {error, ..})
5739 if error == SendFrameErrorReason::AddressResolutionFailed
5740 );
5741 } else {
5742 assert_matches!(result, Ok(()));
5743 }
5744 }
5745 }
5746 }
5747 }
5748
5749 #[ip_test(I)]
5751 #[test_case(InsertMethod::ResolveLinkAddr; "resolve_link_addr")]
5752 #[test_case(InsertMethod::InsertStaticEntry; "insert_static_entry")]
5753 #[test_case(InsertMethod::NeighborUpdate; "neighbor_update")]
5754 #[test_case(InsertMethod::SendIpPacket; "send_ip_packet")]
5755 fn neighbor_table_max_entries_enforced<I: TestIpExt>(insert_method: InsertMethod) {
5756 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5757 for i in 0..MAX_ENTRIES {
5758 init_static_neighbor_with_ip(
5760 &mut core_ctx,
5761 &mut bindings_ctx,
5762 generate_ip_addr::<I>(i),
5763 LINK_ADDR1,
5764 ExpectedEvent::Added,
5765 );
5766 assert_eq!(core_ctx.nud.state.neighbors.len(), i + 1);
5767 }
5768
5769 insert_method.insert(
5771 &mut CtxPair { core_ctx: &mut core_ctx, bindings_ctx: &mut bindings_ctx },
5772 LINK_ADDR1,
5773 generate_ip_addr::<I>(MAX_ENTRIES),
5774 true, );
5776 assert_eq!(bindings_ctx.take_events(), []);
5777 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5778 }
5779
5780 #[ip_test(I)]
5783 #[test_case(InsertMethod::ResolveLinkAddr; "resolve_link_addr")]
5784 #[test_case(InsertMethod::InsertStaticEntry; "insert_static_entry")]
5785 #[test_case(InsertMethod::NeighborUpdate; "neighbor_update")]
5786 #[test_case(InsertMethod::SendIpPacket; "send_ip_packet")]
5787 fn insert_new_entry_may_collect_garbage<I: TestIpExt>(insert_method: InsertMethod) {
5788 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5789
5790 for i in 0..MAX_ENTRIES {
5791 init_stale_neighbor_with_ip(
5793 &mut core_ctx,
5794 &mut bindings_ctx,
5795 generate_ip_addr::<I>(i),
5796 LINK_ADDR1,
5797 );
5798 assert_eq!(core_ctx.nud.state.neighbors.len(), i + 1);
5799 }
5800
5801 insert_method.insert(
5804 &mut CtxPair { core_ctx: &mut core_ctx, bindings_ctx: &mut bindings_ctx },
5805 LINK_ADDR1,
5806 generate_ip_addr::<I>(MAX_ENTRIES),
5807 false, );
5809 assert_eq!(core_ctx.nud.state.neighbors.len(), GC_THRESHOLD + 1);
5810 let mut events = bindings_ctx.take_events();
5811 let add_event = events.pop().expect("should have added event");
5813 assert_eq!(add_event.addr, generate_ip_addr::<I>(MAX_ENTRIES));
5814 assert_matches!(add_event.kind, EventKind::Added(_));
5815 for _ in 0..(MAX_ENTRIES - GC_THRESHOLD) {
5816 assert_matches!(events.pop(), Some(Event{kind, ..}) if kind == EventKind::Removed);
5817 }
5818 assert_matches!(&events[..], []);
5819 }
5820}