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