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