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::SpecifiedAddr;
20use net_types::ip::{GenericOverIp, Ip, IpMarked, Ipv4, Ipv6};
21use netstack3_base::socket::{SocketIpAddr, SocketIpAddrExt as _};
22use netstack3_base::{
23 AddressResolutionFailed, AnyDevice, CoreTimerContext, Counter, CounterContext, DeviceIdContext,
24 DeviceIdentifier, ErrorAndSerializer, EventContext, HandleableTimer, Instant,
25 InstantBindingsTypes, LinkAddress, LinkDevice, LinkUnicastAddress, LocalTimerHeap,
26 SendFrameError, StrongDeviceIdentifier, TimerBindingsTypes, TimerContext,
27 TxMetadataBindingsTypes, WeakDeviceIdentifier,
28};
29use netstack3_hashmap::hash_map::{self, Entry, HashMap};
30use packet::{
31 Buf, BufferMut, GrowBuffer as _, ParsablePacket as _, ParseBufferMut as _, SerializeError,
32 Serializer,
33};
34use packet_formats::ip::IpPacket as _;
35use packet_formats::ipv4::{Ipv4FragmentType, Ipv4Header as _, Ipv4Packet};
36use packet_formats::ipv6::Ipv6Packet;
37use packet_formats::utils::NonZeroDuration;
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 MAX_ENTRIES: usize = 512;
94
95const MIN_GARBAGE_COLLECTION_INTERVAL: NonZeroDuration = NonZeroDuration::from_secs(30).unwrap();
98
99#[derive(Default)]
101pub struct NudCountersInner {
102 pub icmp_dest_unreachable_dropped: Counter,
104}
105
106pub type NudCounters<I> = IpMarked<I, NudCountersInner>;
108
109#[derive(Debug, Copy, Clone)]
111pub struct ConfirmationFlags {
112 pub solicited_flag: bool,
114 pub override_flag: bool,
116}
117
118#[derive(Debug, Copy, Clone)]
120pub enum DynamicNeighborUpdateSource<A> {
121 Probe {
125 link_address: A,
128 },
129
130 Confirmation {
134 link_address: Option<A>,
137 flags: ConfirmationFlags,
139 },
140}
141
142#[derive(Derivative)]
144#[derivative(Debug(bound = ""))]
145#[cfg_attr(
146 any(test, feature = "testutils"),
147 derivative(
148 Clone(bound = "BT::TxMetadata: Clone"),
149 PartialEq(bound = "BT::TxMetadata: PartialEq"),
150 Eq(bound = "BT::TxMetadata: Eq")
151 )
152)]
153#[allow(missing_docs)]
154pub enum NeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
155 Dynamic(DynamicNeighborState<D, BT>),
156 Static(D::Address),
157}
158
159#[derive(Derivative)]
166#[derivative(Debug(bound = ""))]
167#[cfg_attr(
168 any(test, feature = "testutils"),
169 derivative(
170 Clone(bound = "BT::TxMetadata: Clone"),
171 PartialEq(bound = "BT::TxMetadata: PartialEq"),
172 Eq(bound = "BT::TxMetadata: Eq")
173 )
174)]
175pub enum DynamicNeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
176 Incomplete(Incomplete<D, BT::Notifier, BT::TxMetadata>),
182
183 Reachable(Reachable<D, BT::Instant>),
188
189 Stale(Stale<D>),
200
201 Delay(Delay<D>),
213
214 Probe(Probe<D>),
218
219 Unreachable(Unreachable<D>),
226}
227
228#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
243pub enum EventDynamicState<L: LinkUnicastAddress> {
244 Incomplete,
247 Reachable(L),
250 Stale(L),
258 Delay(L),
266 Probe(L),
271 Unreachable(L),
275}
276
277#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
285pub enum EventState<L: LinkUnicastAddress> {
286 Dynamic(EventDynamicState<L>),
288 Static(L),
290}
291
292#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
294pub enum EventKind<L: LinkUnicastAddress> {
295 Added(EventState<L>),
297 Changed(EventState<L>),
299 Removed,
301}
302
303#[derive(Debug, Eq, Hash, PartialEq, GenericOverIp)]
305#[generic_over_ip(I, Ip)]
306pub struct Event<L: LinkUnicastAddress, DeviceId, I: Ip, Instant> {
307 pub device: DeviceId,
309 pub addr: SpecifiedAddr<I::Addr>,
311 pub kind: EventKind<L>,
313 pub at: Instant,
315}
316
317impl<L: LinkUnicastAddress, DeviceId, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
318 pub fn map_device<N, F: FnOnce(DeviceId) -> N>(self, map: F) -> Event<L, N, I, Instant> {
320 let Self { device, kind, addr, at } = self;
321 Event { device: map(device), kind, addr, at }
322 }
323}
324
325impl<L: LinkUnicastAddress, DeviceId: Clone, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
326 fn changed(
327 device: &DeviceId,
328 event_state: EventState<L>,
329 addr: SpecifiedAddr<I::Addr>,
330 at: Instant,
331 ) -> Self {
332 Self { device: device.clone(), kind: EventKind::Changed(event_state), addr, at }
333 }
334
335 fn added(
336 device: &DeviceId,
337 event_state: EventState<L>,
338 addr: SpecifiedAddr<I::Addr>,
339 at: Instant,
340 ) -> Self {
341 Self { device: device.clone(), kind: EventKind::Added(event_state), addr, at }
342 }
343
344 fn removed(device: &DeviceId, addr: SpecifiedAddr<I::Addr>, at: Instant) -> Self {
345 Self { device: device.clone(), kind: EventKind::Removed, addr, at }
346 }
347}
348
349fn schedule_timer_if_should_retransmit<I, D, DeviceId, CC, BC>(
350 core_ctx: &mut CC,
351 bindings_ctx: &mut BC,
352 timers: &mut TimerHeap<I, BC>,
353 neighbor: SpecifiedAddr<I::Addr>,
354 event: NudEvent,
355 counter: &mut Option<NonZeroU16>,
356) -> bool
357where
358 I: Ip,
359 D: LinkDevice,
360 DeviceId: StrongDeviceIdentifier,
361 BC: NudBindingsContext<I, D, DeviceId>,
362 CC: NudConfigContext<I>,
363{
364 match counter {
365 Some(c) => {
366 *counter = NonZeroU16::new(c.get() - 1);
367 let retransmit_timeout = core_ctx.retransmit_timeout();
368 timers.schedule_neighbor(bindings_ctx, retransmit_timeout, neighbor, event);
369 true
370 }
371 None => false,
372 }
373}
374
375#[derive(Debug, Derivative)]
377#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq(bound = "M: PartialEq"), Eq))]
378pub struct Incomplete<D: LinkDevice, N: LinkResolutionNotifier<D>, M> {
379 transmit_counter: Option<NonZeroU16>,
380 pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
381 #[derivative(PartialEq = "ignore")]
382 notifiers: Vec<N>,
383 _marker: PhantomData<D>,
384}
385
386#[cfg(any(test, feature = "testutils"))]
387impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M: Clone> Clone for Incomplete<D, N, M> {
388 fn clone(&self) -> Self {
389 let Self { transmit_counter, pending_frames, notifiers: _, _marker } = self;
393 Self {
394 transmit_counter: transmit_counter.clone(),
395 pending_frames: pending_frames.clone(),
396 notifiers: Vec::new(),
397 _marker: PhantomData,
398 }
399 }
400}
401
402impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Drop for Incomplete<D, N, M> {
403 fn drop(&mut self) {
404 let Self { transmit_counter: _, pending_frames: _, notifiers, _marker } = self;
405 for notifier in notifiers.drain(..) {
406 notifier.notify(Err(AddressResolutionFailed));
407 }
408 }
409}
410
411impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Incomplete<D, N, M> {
412 #[cfg(any(test, feature = "testutils"))]
415 pub fn new_with_pending_frames_and_transmit_counter(
416 pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
417 transmit_counter: Option<NonZeroU16>,
418 ) -> Self {
419 Self {
420 transmit_counter,
421 pending_frames,
422 notifiers: Default::default(),
423 _marker: PhantomData,
424 }
425 }
426
427 fn new_with_packet<I, CC, BC, DeviceId, B, S>(
428 core_ctx: &mut CC,
429 bindings_ctx: &mut BC,
430 timers: &mut TimerHeap<I, BC>,
431 neighbor: SpecifiedAddr<I::Addr>,
432 packet: S,
433 meta: M,
434 ) -> Result<Self, ErrorAndSerializer<SerializeError<Never>, S>>
435 where
436 I: Ip,
437 D: LinkDevice,
438 BC: NudBindingsContext<I, D, DeviceId>,
439 CC: NudConfigContext<I>,
440 DeviceId: StrongDeviceIdentifier,
441 B: BufferMut,
442 S: Serializer<Buffer = B>,
443 {
444 let packet = packet
448 .serialize_vec_outer()
449 .map_err(|(error, serializer)| ErrorAndSerializer { error, serializer })?
450 .map_a(|b| Buf::new(b.as_ref().to_vec(), ..))
451 .into_inner();
452
453 let mut this = Incomplete {
454 transmit_counter: Some(core_ctx.max_multicast_solicit()),
455 pending_frames: VecDeque::from([(packet, meta)]),
456 notifiers: Vec::new(),
457 _marker: PhantomData,
458 };
459 assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
463
464 Ok(this)
465 }
466
467 fn new_with_notifier<I, CC, BC, DeviceId>(
468 core_ctx: &mut CC,
469 bindings_ctx: &mut BC,
470 timers: &mut TimerHeap<I, BC>,
471 neighbor: SpecifiedAddr<I::Addr>,
472 notifier: BC::Notifier,
473 ) -> Self
474 where
475 I: Ip,
476 D: LinkDevice,
477 BC: NudBindingsContext<I, D, DeviceId, Notifier = N>,
478 CC: NudConfigContext<I>,
479 DeviceId: StrongDeviceIdentifier,
480 {
481 let mut this = Incomplete {
482 transmit_counter: Some(core_ctx.max_multicast_solicit()),
483 pending_frames: VecDeque::new(),
484 notifiers: [notifier].into(),
485 _marker: PhantomData,
486 };
487 assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
491
492 this
493 }
494
495 fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
496 &mut self,
497 core_ctx: &mut CC,
498 bindings_ctx: &mut BC,
499 timers: &mut TimerHeap<I, BC>,
500 neighbor: SpecifiedAddr<I::Addr>,
501 ) -> bool
502 where
503 I: Ip,
504 D: LinkDevice,
505 DeviceId: StrongDeviceIdentifier,
506 BC: NudBindingsContext<I, D, DeviceId>,
507 CC: NudConfigContext<I>,
508 {
509 let Self { transmit_counter, pending_frames: _, notifiers: _, _marker } = self;
510 schedule_timer_if_should_retransmit(
511 core_ctx,
512 bindings_ctx,
513 timers,
514 neighbor,
515 NudEvent::RetransmitMulticastProbe,
516 transmit_counter,
517 )
518 }
519
520 fn queue_packet<B, S>(
521 &mut self,
522 body: S,
523 meta: M,
524 ) -> Result<(), ErrorAndSerializer<SerializeError<Never>, S>>
525 where
526 B: BufferMut,
527 S: Serializer<Buffer = B>,
528 {
529 let Self { pending_frames, transmit_counter: _, notifiers: _, _marker } = self;
530
531 if pending_frames.len() < MAX_PENDING_FRAMES {
538 pending_frames.push_back((
539 body.serialize_vec_outer()
540 .map_err(|(error, serializer)| ErrorAndSerializer { error, serializer })?
541 .map_a(|b| Buf::new(b.as_ref().to_vec(), ..))
542 .into_inner(),
543 meta,
544 ));
545 }
546 Ok(())
547 }
548
549 fn complete_resolution<I, CC, BC>(
552 &mut self,
553 core_ctx: &mut CC,
554 bindings_ctx: &mut BC,
555 link_address: D::Address,
556 ) where
557 I: Ip,
558 D: LinkDevice,
559 BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata = M>,
560 CC: NudSenderContext<I, D, BC>,
561 {
562 let Self { pending_frames, notifiers, transmit_counter: _, _marker } = self;
563
564 for (body, meta) in pending_frames.drain(..) {
572 core_ctx
576 .send_ip_packet_to_neighbor_link_addr(bindings_ctx, link_address, body, meta)
577 .unwrap_or_else(|err| {
578 error!("failed to send pending IP packet to neighbor {link_address:?} {err:?}")
579 })
580 }
581 for notifier in notifiers.drain(..) {
582 notifier.notify(Ok(link_address));
583 }
584 }
585}
586
587#[derive(Debug, Derivative)]
589#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
590pub struct Reachable<D: LinkDevice, I: Instant> {
591 pub link_address: D::Address,
593 pub last_confirmed_at: I,
595}
596
597#[derive(Debug, Derivative)]
599#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
600pub struct Stale<D: LinkDevice> {
601 pub link_address: D::Address,
603}
604
605impl<D: LinkDevice> Stale<D> {
606 fn enter_delay<I, BC, DeviceId: Clone>(
607 &mut self,
608 bindings_ctx: &mut BC,
609 timers: &mut TimerHeap<I, BC>,
610 neighbor: SpecifiedAddr<I::Addr>,
611 ) -> Delay<D>
612 where
613 I: Ip,
614 BC: NudBindingsContext<I, D, DeviceId>,
615 {
616 let Self { link_address } = *self;
617
618 timers.schedule_neighbor(
621 bindings_ctx,
622 DELAY_FIRST_PROBE_TIME,
623 neighbor,
624 NudEvent::DelayFirstProbe,
625 );
626
627 Delay { link_address }
628 }
629}
630
631#[derive(Debug, Derivative)]
633#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
634pub struct Delay<D: LinkDevice> {
635 pub link_address: D::Address,
637}
638
639impl<D: LinkDevice> Delay<D> {
640 fn enter_probe<I, DeviceId, CC, BC>(
641 &mut self,
642 core_ctx: &mut CC,
643 bindings_ctx: &mut BC,
644 timers: &mut TimerHeap<I, BC>,
645 neighbor: SpecifiedAddr<I::Addr>,
646 ) -> Probe<D>
647 where
648 I: Ip,
649 DeviceId: StrongDeviceIdentifier,
650 BC: NudBindingsContext<I, D, DeviceId>,
651 CC: NudConfigContext<I>,
652 {
653 let Self { link_address } = *self;
654
655 let retransmit_timeout = core_ctx.retransmit_timeout();
659 timers.schedule_neighbor(
660 bindings_ctx,
661 retransmit_timeout,
662 neighbor,
663 NudEvent::RetransmitUnicastProbe,
664 );
665
666 Probe {
667 link_address,
668 transmit_counter: NonZeroU16::new(core_ctx.max_unicast_solicit().get() - 1),
669 }
670 }
671}
672
673#[derive(Debug, Derivative)]
674#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
675pub struct Probe<D: LinkDevice> {
676 link_address: D::Address,
677 transmit_counter: Option<NonZeroU16>,
678}
679
680impl<D: LinkDevice> Probe<D> {
681 fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
682 &mut self,
683 core_ctx: &mut CC,
684 bindings_ctx: &mut BC,
685 timers: &mut TimerHeap<I, BC>,
686 neighbor: SpecifiedAddr<I::Addr>,
687 ) -> bool
688 where
689 I: Ip,
690 DeviceId: StrongDeviceIdentifier,
691 BC: NudBindingsContext<I, D, DeviceId>,
692 CC: NudConfigContext<I>,
693 {
694 let Self { link_address: _, transmit_counter } = self;
695 schedule_timer_if_should_retransmit(
696 core_ctx,
697 bindings_ctx,
698 timers,
699 neighbor,
700 NudEvent::RetransmitUnicastProbe,
701 transmit_counter,
702 )
703 }
704
705 fn enter_unreachable<I, BC, DeviceId>(
706 &mut self,
707 bindings_ctx: &mut BC,
708 timers: &mut TimerHeap<I, BC>,
709 num_entries: usize,
710 last_gc: &mut Option<BC::Instant>,
711 ) -> Unreachable<D>
712 where
713 I: Ip,
714 BC: NudBindingsContext<I, D, DeviceId>,
715 DeviceId: Clone,
716 {
717 timers.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
721
722 let Self { link_address, transmit_counter: _ } = self;
723 Unreachable { link_address: *link_address, mode: UnreachableMode::WaitingForPacketSend }
724 }
725}
726
727#[derive(Debug, Derivative)]
728#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
729pub struct Unreachable<D: LinkDevice> {
730 link_address: D::Address,
731 mode: UnreachableMode,
732}
733
734#[derive(Debug, Clone, Copy, Derivative)]
746#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq, Eq))]
747pub(crate) enum UnreachableMode {
748 WaitingForPacketSend,
749 Backoff { probes_sent: NonZeroU32, packet_sent: bool },
750}
751
752impl UnreachableMode {
753 fn next_backoff_retransmit_timeout<I, CC>(&self, core_ctx: &mut CC) -> NonZeroDuration
759 where
760 I: Ip,
761 CC: NudConfigContext<I>,
762 {
763 let probes_sent = match self {
764 UnreachableMode::Backoff { probes_sent, packet_sent: _ } => probes_sent,
765 UnreachableMode::WaitingForPacketSend => {
766 panic!("cannot calculate exponential backoff in state {self:?}")
767 }
768 };
769 (core_ctx.retransmit_timeout() * BACKOFF_MULTIPLE.saturating_pow(probes_sent.get()))
773 .min(MAX_RETRANS_TIMER)
774 }
775}
776
777impl<D: LinkDevice> Unreachable<D> {
778 fn handle_timer<I, DeviceId, CC, BC>(
779 &mut self,
780 core_ctx: &mut CC,
781 bindings_ctx: &mut BC,
782 timers: &mut TimerHeap<I, BC>,
783 device_id: &DeviceId,
784 neighbor: SpecifiedAddr<I::Addr>,
785 ) -> Option<TransmitProbe<D::Address>>
786 where
787 I: Ip,
788 DeviceId: StrongDeviceIdentifier,
789 BC: NudBindingsContext<I, D, DeviceId>,
790 CC: NudConfigContext<I>,
791 {
792 let Self { link_address: _, mode } = self;
793 match mode {
794 UnreachableMode::WaitingForPacketSend => {
795 panic!(
796 "timer should not have fired in UNREACHABLE while waiting for packet send; got \
797 a retransmit multicast probe event for {neighbor} on {device_id:?}",
798 );
799 }
800 UnreachableMode::Backoff { probes_sent, packet_sent } => {
801 if *packet_sent {
802 *probes_sent = probes_sent.saturating_add(1);
809 *packet_sent = false;
810
811 let duration = mode.next_backoff_retransmit_timeout(core_ctx);
812 timers.schedule_neighbor(
813 bindings_ctx,
814 duration,
815 neighbor,
816 NudEvent::RetransmitMulticastProbe,
817 );
818
819 Some(TransmitProbe::Multicast)
820 } else {
821 *mode = UnreachableMode::WaitingForPacketSend;
822
823 None
824 }
825 }
826 }
827 }
828
829 fn handle_packet_queued_to_send<I, DeviceId, CC, BC>(
834 &mut self,
835 core_ctx: &mut CC,
836 bindings_ctx: &mut BC,
837 timers: &mut TimerHeap<I, BC>,
838 neighbor: SpecifiedAddr<I::Addr>,
839 ) -> bool
840 where
841 I: Ip,
842 DeviceId: StrongDeviceIdentifier,
843 BC: NudBindingsContext<I, D, DeviceId>,
844 CC: NudConfigContext<I>,
845 {
846 let Self { link_address: _, mode } = self;
847 match mode {
848 UnreachableMode::WaitingForPacketSend => {
849 let probes_sent = NonZeroU32::new(1).unwrap();
864 *mode = UnreachableMode::Backoff { probes_sent, packet_sent: false };
865
866 let duration = mode.next_backoff_retransmit_timeout(core_ctx);
867 timers.schedule_neighbor(
868 bindings_ctx,
869 duration,
870 neighbor,
871 NudEvent::RetransmitMulticastProbe,
872 );
873
874 true
876 }
877 UnreachableMode::Backoff { probes_sent: _, packet_sent } => {
878 *packet_sent = true;
882
883 false
884 }
885 }
886 }
887}
888
889#[derive(Debug, PartialEq, Eq, Error)]
890pub(crate) enum EnterProbeError {
891 #[error("link address is unknown")]
892 LinkAddressUnknown,
893}
894
895impl<D: LinkDevice, BT: NudBindingsTypes<D>> NeighborState<D, BT> {
896 fn to_event_state(&self) -> EventState<D::Address> {
897 match self {
898 NeighborState::Dynamic(dynamic_state) => {
899 EventState::Dynamic(dynamic_state.to_event_dynamic_state())
900 }
901 NeighborState::Static(addr) => EventState::Static(*addr),
902 }
903 }
904
905 pub(crate) fn enter_probe<I, DeviceId, CC>(
915 &mut self,
916 core_ctx: &mut CC,
917 bindings_ctx: &mut BT,
918 timers: &mut TimerHeap<I, BT>,
919 neighbor: SpecifiedAddr<I::Addr>,
920 device_id: &DeviceId,
921 ) -> Result<Option<D::Address>, EnterProbeError>
922 where
923 I: Ip,
924 DeviceId: StrongDeviceIdentifier,
925 BT: NudBindingsContext<I, D, DeviceId>,
926 CC: NudConfigContext<I>,
927 {
928 let link_addr = match self {
929 NeighborState::Dynamic(dynamic_state) => {
930 let link_addr = match dynamic_state {
931 DynamicNeighborState::Reachable(Reachable { link_address, .. }) => {
932 *link_address
933 }
934 DynamicNeighborState::Stale(Stale { link_address }) => *link_address,
935 DynamicNeighborState::Delay(Delay { link_address }) => *link_address,
936 DynamicNeighborState::Unreachable(Unreachable { link_address, .. }) => {
937 *link_address
938 }
939 DynamicNeighborState::Incomplete(_) => {
942 return Err(EnterProbeError::LinkAddressUnknown);
943 }
944 DynamicNeighborState::Probe(_) => return Ok(None),
947 };
948
949 dynamic_state.cancel_timer(bindings_ctx, timers, neighbor);
952 link_addr
953 }
954 NeighborState::Static(link_address) => *link_address,
955 };
956
957 let retransmit_timeout = core_ctx.retransmit_timeout();
959 timers.schedule_neighbor(
960 bindings_ctx,
961 retransmit_timeout,
962 neighbor,
963 NudEvent::RetransmitUnicastProbe,
964 );
965
966 *self = NeighborState::Dynamic(DynamicNeighborState::Probe(Probe {
968 link_address: link_addr,
969 transmit_counter: NonZeroU16::new(core_ctx.max_unicast_solicit().get() - 1),
970 }));
971 let event_state = self.to_event_state();
972 bindings_ctx.on_event(Event::changed(
973 &device_id,
974 event_state,
975 neighbor,
976 bindings_ctx.now(),
977 ));
978 Ok(Some(link_addr))
979 }
980}
981
982impl<D: LinkDevice, BC: NudBindingsTypes<D>> DynamicNeighborState<D, BC> {
983 fn cancel_timer<I, DeviceId>(
984 &mut self,
985 bindings_ctx: &mut BC,
986 timers: &mut TimerHeap<I, BC>,
987 neighbor: SpecifiedAddr<I::Addr>,
988 ) where
989 I: Ip,
990 DeviceId: StrongDeviceIdentifier,
991 BC: NudBindingsContext<I, D, DeviceId>,
992 {
993 let expected_event = match self {
994 DynamicNeighborState::Incomplete(Incomplete {
995 transmit_counter: _,
996 pending_frames: _,
997 notifiers: _,
998 _marker,
999 }) => Some(NudEvent::RetransmitMulticastProbe),
1000 DynamicNeighborState::Reachable(Reachable {
1001 link_address: _,
1002 last_confirmed_at: _,
1003 }) => Some(NudEvent::ReachableTime),
1004 DynamicNeighborState::Stale(Stale { link_address: _ }) => None,
1005 DynamicNeighborState::Delay(Delay { link_address: _ }) => {
1006 Some(NudEvent::DelayFirstProbe)
1007 }
1008 DynamicNeighborState::Probe(Probe { link_address: _, transmit_counter: _ }) => {
1009 Some(NudEvent::RetransmitUnicastProbe)
1010 }
1011 DynamicNeighborState::Unreachable(Unreachable { link_address: _, mode }) => {
1012 match mode {
1015 UnreachableMode::WaitingForPacketSend => None,
1016 UnreachableMode::Backoff { probes_sent: _, packet_sent: _ } => {
1017 Some(NudEvent::RetransmitMulticastProbe)
1018 }
1019 }
1020 }
1021 };
1022 assert_eq!(
1023 timers.cancel_neighbor(bindings_ctx, neighbor),
1024 expected_event,
1025 "neighbor {neighbor} ({self:?}) had unexpected timer installed"
1026 );
1027 }
1028
1029 fn cancel_timer_and_complete_resolution<I, CC>(
1030 mut self,
1031 core_ctx: &mut CC,
1032 bindings_ctx: &mut BC,
1033 timers: &mut TimerHeap<I, BC>,
1034 neighbor: SpecifiedAddr<I::Addr>,
1035 link_address: D::Address,
1036 ) where
1037 I: Ip,
1038 BC: NudBindingsContext<I, D, CC::DeviceId>,
1039 CC: NudSenderContext<I, D, BC>,
1040 {
1041 self.cancel_timer(bindings_ctx, timers, neighbor);
1042
1043 match self {
1044 DynamicNeighborState::Incomplete(mut incomplete) => {
1045 incomplete.complete_resolution(core_ctx, bindings_ctx, link_address);
1046 }
1047 DynamicNeighborState::Reachable(_)
1048 | DynamicNeighborState::Stale(_)
1049 | DynamicNeighborState::Delay(_)
1050 | DynamicNeighborState::Probe(_)
1051 | DynamicNeighborState::Unreachable(_) => {}
1052 }
1053 }
1054
1055 fn to_event_dynamic_state(&self) -> EventDynamicState<D::Address> {
1056 match self {
1057 Self::Incomplete(_) => EventDynamicState::Incomplete,
1058 Self::Reachable(Reachable { link_address, last_confirmed_at: _ }) => {
1059 EventDynamicState::Reachable(*link_address)
1060 }
1061 Self::Stale(Stale { link_address }) => EventDynamicState::Stale(*link_address),
1062 Self::Delay(Delay { link_address }) => EventDynamicState::Delay(*link_address),
1063 Self::Probe(Probe { link_address, transmit_counter: _ }) => {
1064 EventDynamicState::Probe(*link_address)
1065 }
1066 Self::Unreachable(Unreachable { link_address, mode: _ }) => {
1067 EventDynamicState::Unreachable(*link_address)
1068 }
1069 }
1070 }
1071
1072 fn enter_reachable<I, CC>(
1074 &mut self,
1075 core_ctx: &mut CC,
1076 bindings_ctx: &mut BC,
1077 timers: &mut TimerHeap<I, BC>,
1078 device_id: &CC::DeviceId,
1079 neighbor: SpecifiedAddr<I::Addr>,
1080 link_address: D::Address,
1081 ) where
1082 I: Ip,
1083 BC: NudBindingsContext<I, D, CC::DeviceId>,
1084 CC: NudSenderContext<I, D, BC>,
1085 {
1086 let now = bindings_ctx.now();
1089 match self {
1090 DynamicNeighborState::Reachable(Reachable {
1095 link_address: current,
1096 last_confirmed_at,
1097 }) if *current == link_address => {
1098 *last_confirmed_at = now;
1099 return;
1100 }
1101 DynamicNeighborState::Incomplete(_)
1102 | DynamicNeighborState::Reachable(_)
1103 | DynamicNeighborState::Stale(_)
1104 | DynamicNeighborState::Delay(_)
1105 | DynamicNeighborState::Probe(_)
1106 | DynamicNeighborState::Unreachable(_) => {}
1107 }
1108 let previous = core::mem::replace(
1109 self,
1110 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: now }),
1111 );
1112 let event_dynamic_state = self.to_event_dynamic_state();
1113 debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1114 let event_state = EventState::Dynamic(event_dynamic_state);
1115 bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1116 previous.cancel_timer_and_complete_resolution(
1117 core_ctx,
1118 bindings_ctx,
1119 timers,
1120 neighbor,
1121 link_address,
1122 );
1123 timers.schedule_neighbor(
1124 bindings_ctx,
1125 core_ctx.base_reachable_time(),
1126 neighbor,
1127 NudEvent::ReachableTime,
1128 );
1129 }
1130
1131 fn enter_stale<I, CC>(
1139 &mut self,
1140 core_ctx: &mut CC,
1141 bindings_ctx: &mut BC,
1142 timers: &mut TimerHeap<I, BC>,
1143 device_id: &CC::DeviceId,
1144 neighbor: SpecifiedAddr<I::Addr>,
1145 link_address: D::Address,
1146 num_entries: usize,
1147 last_gc: &mut Option<BC::Instant>,
1148 ) where
1149 I: Ip,
1150 BC: NudBindingsContext<I, D, CC::DeviceId>,
1151 CC: NudSenderContext<I, D, BC>,
1152 {
1153 let previous =
1156 core::mem::replace(self, DynamicNeighborState::Stale(Stale { link_address }));
1157 let event_dynamic_state = self.to_event_dynamic_state();
1158 debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1159 let event_state = EventState::Dynamic(event_dynamic_state);
1160 bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1161 previous.cancel_timer_and_complete_resolution(
1162 core_ctx,
1163 bindings_ctx,
1164 timers,
1165 neighbor,
1166 link_address,
1167 );
1168
1169 timers.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
1173
1174 }
1177
1178 fn resolve_link_addr<I, DeviceId, CC>(
1184 &mut self,
1185 core_ctx: &mut CC,
1186 bindings_ctx: &mut BC,
1187 timers: &mut TimerHeap<I, BC>,
1188 device_id: &DeviceId,
1189 neighbor: SpecifiedAddr<I::Addr>,
1190 ) -> (
1191 LinkResolutionResult<
1192 D::Address,
1193 <<BC as LinkResolutionContext<D>>::Notifier as LinkResolutionNotifier<D>>::Observer,
1194 >,
1195 bool,
1196 )
1197 where
1198 I: Ip,
1199 DeviceId: StrongDeviceIdentifier,
1200 BC: NudBindingsContext<I, D, DeviceId>,
1201 CC: NudConfigContext<I>,
1202 {
1203 match self {
1204 DynamicNeighborState::Incomplete(Incomplete {
1205 notifiers,
1206 transmit_counter: _,
1207 pending_frames: _,
1208 _marker,
1209 }) => {
1210 let (notifier, observer) = BC::Notifier::new();
1211 notifiers.push(notifier);
1212
1213 (LinkResolutionResult::Pending(observer), false)
1214 }
1215 DynamicNeighborState::Stale(entry) => {
1216 let delay @ Delay { link_address } =
1225 entry.enter_delay(bindings_ctx, timers, neighbor);
1226 *self = DynamicNeighborState::Delay(delay);
1227 let event_state = EventState::Dynamic(self.to_event_dynamic_state());
1228 bindings_ctx.on_event(Event::changed(
1229 device_id,
1230 event_state,
1231 neighbor,
1232 bindings_ctx.now(),
1233 ));
1234
1235 (LinkResolutionResult::Resolved(link_address), false)
1236 }
1237 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1238 | DynamicNeighborState::Delay(Delay { link_address })
1239 | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1240 (LinkResolutionResult::Resolved(*link_address), false)
1241 }
1242 DynamicNeighborState::Unreachable(unreachable) => {
1243 let Unreachable { link_address, mode: _ } = unreachable;
1244 let link_address = *link_address;
1245
1246 let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1248 core_ctx,
1249 bindings_ctx,
1250 timers,
1251 neighbor,
1252 );
1253 (LinkResolutionResult::Resolved(link_address), do_multicast_solicit)
1254 }
1255 }
1256 }
1257
1258 fn handle_packet_queued_to_send<I, CC, S>(
1264 &mut self,
1265 core_ctx: &mut CC,
1266 bindings_ctx: &mut BC,
1267 timers: &mut TimerHeap<I, BC>,
1268 device_id: &CC::DeviceId,
1269 neighbor: SpecifiedAddr<I::Addr>,
1270 body: S,
1271 meta: BC::TxMetadata,
1272 ) -> Result<bool, SendFrameError<S>>
1273 where
1274 I: Ip,
1275 BC: NudBindingsContext<I, D, CC::DeviceId>,
1276 CC: NudSenderContext<I, D, BC>,
1277 S: Serializer,
1278 S::Buffer: BufferMut,
1279 {
1280 match self {
1281 DynamicNeighborState::Incomplete(incomplete) => {
1282 incomplete.queue_packet(body, meta).map(|()| false).map_err(|e| e.err_into())
1283 }
1284 DynamicNeighborState::Stale(entry) => {
1291 let delay @ Delay { link_address } =
1299 entry.enter_delay(bindings_ctx, timers, neighbor);
1300 *self = DynamicNeighborState::Delay(delay);
1301 let event_state = EventState::Dynamic(self.to_event_dynamic_state());
1302 bindings_ctx.on_event(Event::changed(
1303 device_id,
1304 event_state,
1305 neighbor,
1306 bindings_ctx.now(),
1307 ));
1308
1309 core_ctx.send_ip_packet_to_neighbor_link_addr(
1310 bindings_ctx,
1311 link_address,
1312 body,
1313 meta,
1314 )?;
1315
1316 Ok(false)
1317 }
1318 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1319 | DynamicNeighborState::Delay(Delay { link_address })
1320 | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1321 core_ctx.send_ip_packet_to_neighbor_link_addr(
1322 bindings_ctx,
1323 *link_address,
1324 body,
1325 meta,
1326 )?;
1327
1328 Ok(false)
1329 }
1330 DynamicNeighborState::Unreachable(unreachable) => {
1331 let Unreachable { link_address, mode: _ } = unreachable;
1332 core_ctx.send_ip_packet_to_neighbor_link_addr(
1333 bindings_ctx,
1334 *link_address,
1335 body,
1336 meta,
1337 )?;
1338
1339 let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1340 core_ctx,
1341 bindings_ctx,
1342 timers,
1343 neighbor,
1344 );
1345 Ok(do_multicast_solicit)
1346 }
1347 }
1348 }
1349
1350 fn handle_probe<I, CC>(
1351 &mut self,
1352 core_ctx: &mut CC,
1353 bindings_ctx: &mut BC,
1354 timers: &mut TimerHeap<I, BC>,
1355 device_id: &CC::DeviceId,
1356 neighbor: SpecifiedAddr<I::Addr>,
1357 link_address: D::Address,
1358 num_entries: usize,
1359 last_gc: &mut Option<BC::Instant>,
1360 ) where
1361 I: Ip,
1362 BC: NudBindingsContext<I, D, CC::DeviceId>,
1363 CC: NudSenderContext<I, D, BC>,
1364 {
1365 let transition_to_stale = match self {
1374 DynamicNeighborState::Incomplete(_) => true,
1375 DynamicNeighborState::Reachable(Reachable {
1376 link_address: current,
1377 last_confirmed_at: _,
1378 })
1379 | DynamicNeighborState::Stale(Stale { link_address: current })
1380 | DynamicNeighborState::Delay(Delay { link_address: current })
1381 | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1382 | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1383 current != &link_address
1384 }
1385 };
1386 if transition_to_stale {
1387 self.enter_stale(
1388 core_ctx,
1389 bindings_ctx,
1390 timers,
1391 device_id,
1392 neighbor,
1393 link_address,
1394 num_entries,
1395 last_gc,
1396 );
1397 }
1398 }
1399
1400 fn handle_confirmation<I, CC>(
1401 &mut self,
1402 core_ctx: &mut CC,
1403 bindings_ctx: &mut BC,
1404 timers: &mut TimerHeap<I, BC>,
1405 device_id: &CC::DeviceId,
1406 neighbor: SpecifiedAddr<I::Addr>,
1407 link_address: Option<D::Address>,
1408 flags: ConfirmationFlags,
1409 num_entries: usize,
1410 last_gc: &mut Option<BC::Instant>,
1411 ) where
1412 I: Ip,
1413 BC: NudBindingsContext<I, D, CC::DeviceId>,
1414 CC: NudSenderContext<I, D, BC>,
1415 {
1416 let ConfirmationFlags { solicited_flag, override_flag } = flags;
1417 enum NewState<A> {
1418 Reachable { link_address: A },
1419 Stale { link_address: A },
1420 }
1421
1422 let new_state = match self {
1423 DynamicNeighborState::Incomplete(Incomplete {
1424 transmit_counter: _,
1425 pending_frames: _,
1426 notifiers: _,
1427 _marker,
1428 }) => {
1429 link_address.map(|link_address| {
1441 if solicited_flag {
1442 NewState::Reachable { link_address }
1443 } else {
1444 NewState::Stale { link_address }
1445 }
1446 })
1447 }
1448
1449 DynamicNeighborState::Reachable(Reachable {
1453 link_address: current,
1454 last_confirmed_at,
1455 }) if !core_ctx.override_lock_time().is_zero()
1456 && solicited_flag
1457 && bindings_ctx.now().saturating_duration_since(*last_confirmed_at)
1458 < core_ctx.override_lock_time() =>
1459 {
1460 log::warn!(
1461 "Ignoring duplicate neighbor confirmation for {:?}. Current address {:?}.
1462 New address {:?}",
1463 neighbor,
1464 current,
1465 link_address
1466 );
1467 None
1468 }
1469
1470 DynamicNeighborState::Reachable(Reachable {
1471 link_address: current,
1472 last_confirmed_at: _,
1473 })
1474 | DynamicNeighborState::Stale(Stale { link_address: current })
1475 | DynamicNeighborState::Delay(Delay { link_address: current })
1476 | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1477 | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1478 let updated_link_address = link_address
1497 .and_then(|link_address| (current != &link_address).then_some(link_address));
1498
1499 match (solicited_flag, updated_link_address, override_flag) {
1500 (true, _, true) | (true, None, _) => {
1506 Some(NewState::Reachable { link_address: link_address.unwrap_or(*current) })
1507 }
1508 (_, Some(_), false) => match self {
1518 DynamicNeighborState::Reachable(Reachable {
1520 link_address,
1521 last_confirmed_at: _,
1522 }) => Some(NewState::Stale { link_address: *link_address }),
1523 DynamicNeighborState::Stale(_)
1525 | DynamicNeighborState::Delay(_)
1526 | DynamicNeighborState::Probe(_)
1527 | DynamicNeighborState::Unreachable(_) => None,
1528 DynamicNeighborState::Incomplete(_) => unreachable!(),
1530 },
1531 (false, Some(link_address), true) => Some(NewState::Stale { link_address }),
1537 (false, None, _) => None,
1542 }
1543 }
1544 };
1545 match new_state {
1546 Some(NewState::Reachable { link_address }) => self.enter_reachable(
1547 core_ctx,
1548 bindings_ctx,
1549 timers,
1550 device_id,
1551 neighbor,
1552 link_address,
1553 ),
1554 Some(NewState::Stale { link_address }) => self.enter_stale(
1555 core_ctx,
1556 bindings_ctx,
1557 timers,
1558 device_id,
1559 neighbor,
1560 link_address,
1561 num_entries,
1562 last_gc,
1563 ),
1564 None => {}
1565 }
1566 }
1567}
1568
1569#[cfg(any(test, feature = "testutils"))]
1570pub(crate) mod testutil {
1571 use super::*;
1572
1573 use alloc::sync::Arc;
1574
1575 use netstack3_base::sync::Mutex;
1576 use netstack3_base::testutil::{FakeBindingsCtx, FakeCoreCtx};
1577
1578 pub fn assert_dynamic_neighbor_with_addr<
1580 I: Ip,
1581 D: LinkDevice,
1582 BC: NudBindingsContext<I, D, CC::DeviceId>,
1583 CC: NudContext<I, D, BC>,
1584 >(
1585 core_ctx: &mut CC,
1586 device_id: CC::DeviceId,
1587 neighbor: SpecifiedAddr<I::Addr>,
1588 expected_link_addr: D::Address,
1589 ) {
1590 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1591 assert_matches!(
1592 neighbors.get(&neighbor),
1593 Some(NeighborState::Dynamic(
1594 DynamicNeighborState::Reachable(Reachable{ link_address, last_confirmed_at: _ })
1595 | DynamicNeighborState::Stale(Stale{ link_address })
1596 )) => {
1597 assert_eq!(link_address, &expected_link_addr)
1598 }
1599 )
1600 })
1601 }
1602
1603 pub fn assert_dynamic_neighbor_state<I, D, BC, CC>(
1605 core_ctx: &mut CC,
1606 device_id: CC::DeviceId,
1607 neighbor: SpecifiedAddr<I::Addr>,
1608 expected_state: DynamicNeighborState<D, BC>,
1609 ) where
1610 I: Ip,
1611 D: LinkDevice + PartialEq,
1612 BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata: PartialEq>,
1613 CC: NudContext<I, D, BC>,
1614 {
1615 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1616 assert_matches!(
1617 neighbors.get(&neighbor),
1618 Some(NeighborState::Dynamic(state)) => {
1619 assert_eq!(state, &expected_state)
1620 }
1621 )
1622 })
1623 }
1624
1625 pub fn assert_neighbor_unknown<
1627 I: Ip,
1628 D: LinkDevice,
1629 BC: NudBindingsContext<I, D, CC::DeviceId>,
1630 CC: NudContext<I, D, BC>,
1631 >(
1632 core_ctx: &mut CC,
1633 device_id: CC::DeviceId,
1634 neighbor: SpecifiedAddr<I::Addr>,
1635 ) {
1636 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1637 assert_matches!(neighbors.get(&neighbor), None)
1638 })
1639 }
1640
1641 impl<D: LinkDevice, Id, Event: Debug, State, FrameMeta> LinkResolutionContext<D>
1642 for FakeBindingsCtx<Id, Event, State, FrameMeta>
1643 {
1644 type Notifier = FakeLinkResolutionNotifier<D>;
1645 }
1646
1647 #[derive(Debug)]
1649 pub struct FakeLinkResolutionNotifier<D: LinkDevice>(
1650 Arc<Mutex<Option<Result<D::Address, AddressResolutionFailed>>>>,
1651 );
1652
1653 impl<D: LinkDevice> LinkResolutionNotifier<D> for FakeLinkResolutionNotifier<D> {
1654 type Observer = Arc<Mutex<Option<Result<D::Address, AddressResolutionFailed>>>>;
1655
1656 fn new() -> (Self, Self::Observer) {
1657 let inner = Arc::new(Mutex::new(None));
1658 (Self(inner.clone()), inner)
1659 }
1660
1661 fn notify(self, result: Result<D::Address, AddressResolutionFailed>) {
1662 let Self(inner) = self;
1663 let mut inner = inner.lock();
1664 assert_eq!(*inner, None, "resolved link address was set more than once");
1665 *inner = Some(result);
1666 }
1667 }
1668
1669 impl<S, Meta, DeviceId> UseDelegateNudContext for FakeCoreCtx<S, Meta, DeviceId> where
1670 S: UseDelegateNudContext
1671 {
1672 }
1673 impl<I: Ip, S, Meta, DeviceId> DelegateNudContext<I> for FakeCoreCtx<S, Meta, DeviceId>
1674 where
1675 S: DelegateNudContext<I>,
1676 {
1677 type Delegate<T> = S::Delegate<T>;
1678 }
1679}
1680
1681#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1682enum NudEvent {
1683 RetransmitMulticastProbe,
1684 ReachableTime,
1685 DelayFirstProbe,
1686 RetransmitUnicastProbe,
1687}
1688
1689#[derive(GenericOverIp, Copy, Clone, Debug, Eq, PartialEq, Hash)]
1691#[generic_over_ip(I, Ip)]
1692pub struct NudTimerId<I: Ip, L: LinkDevice, D: WeakDeviceIdentifier> {
1693 device_id: D,
1694 timer_type: NudTimerType,
1695 _marker: PhantomData<(I, L)>,
1696}
1697
1698#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1699enum NudTimerType {
1700 Neighbor,
1701 GarbageCollection,
1702}
1703
1704#[derive(Debug)]
1706pub(crate) struct TimerHeap<I: Ip, BT: TimerBindingsTypes + InstantBindingsTypes> {
1707 gc: BT::Timer,
1708 neighbor: LocalTimerHeap<SpecifiedAddr<I::Addr>, NudEvent, BT>,
1709}
1710
1711impl<I: Ip, BC: TimerContext> TimerHeap<I, BC> {
1712 fn new<
1713 DeviceId: WeakDeviceIdentifier,
1714 L: LinkDevice,
1715 CC: CoreTimerContext<NudTimerId<I, L, DeviceId>, BC>,
1716 >(
1717 bindings_ctx: &mut BC,
1718 device_id: DeviceId,
1719 ) -> Self {
1720 Self {
1721 neighbor: LocalTimerHeap::new_with_context::<_, CC>(
1722 bindings_ctx,
1723 NudTimerId {
1724 device_id: device_id.clone(),
1725 timer_type: NudTimerType::Neighbor,
1726 _marker: PhantomData,
1727 },
1728 ),
1729 gc: CC::new_timer(
1730 bindings_ctx,
1731 NudTimerId {
1732 device_id,
1733 timer_type: NudTimerType::GarbageCollection,
1734 _marker: PhantomData,
1735 },
1736 ),
1737 }
1738 }
1739
1740 fn schedule_neighbor(
1741 &mut self,
1742 bindings_ctx: &mut BC,
1743 after: NonZeroDuration,
1744 neighbor: SpecifiedAddr<I::Addr>,
1745 event: NudEvent,
1746 ) {
1747 let Self { neighbor: heap, gc: _ } = self;
1748 assert_eq!(heap.schedule_after(bindings_ctx, neighbor, event, after.get()), None);
1749 }
1750
1751 fn schedule_neighbor_at(
1752 &mut self,
1753 bindings_ctx: &mut BC,
1754 at: BC::Instant,
1755 neighbor: SpecifiedAddr<I::Addr>,
1756 event: NudEvent,
1757 ) {
1758 let Self { neighbor: heap, gc: _ } = self;
1759 assert_eq!(heap.schedule_instant(bindings_ctx, neighbor, event, at), None);
1760 }
1761
1762 fn cancel_neighbor(
1764 &mut self,
1765 bindings_ctx: &mut BC,
1766 neighbor: SpecifiedAddr<I::Addr>,
1767 ) -> Option<NudEvent> {
1768 let Self { neighbor: heap, gc: _ } = self;
1769 heap.cancel(bindings_ctx, &neighbor).map(|(_instant, v)| v)
1770 }
1771
1772 fn pop_neighbor(
1773 &mut self,
1774 bindings_ctx: &mut BC,
1775 ) -> Option<(SpecifiedAddr<I::Addr>, NudEvent)> {
1776 let Self { neighbor: heap, gc: _ } = self;
1777 heap.pop(bindings_ctx)
1778 }
1779
1780 fn maybe_schedule_gc(
1783 &mut self,
1784 bindings_ctx: &mut BC,
1785 num_entries: usize,
1786 last_gc: &Option<BC::Instant>,
1787 ) {
1788 let Self { gc, neighbor: _ } = self;
1789 if num_entries > MAX_ENTRIES && bindings_ctx.scheduled_instant(gc).is_none() {
1790 let instant = if let Some(last_gc) = last_gc {
1791 last_gc.panicking_add(MIN_GARBAGE_COLLECTION_INTERVAL.get())
1792 } else {
1793 bindings_ctx.now()
1794 };
1795 assert_eq!(bindings_ctx.schedule_timer_instant(instant, gc), None);
1799 }
1800 }
1801}
1802
1803#[derive(Debug)]
1805pub struct NudState<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> {
1806 neighbors: HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>>,
1808 last_gc: Option<BT::Instant>,
1809 timer_heap: TimerHeap<I, BT>,
1810}
1811
1812impl<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> NudState<I, D, BT> {
1813 #[cfg(any(test, feature = "testutils"))]
1815 pub fn neighbors(&self) -> &HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>> {
1816 &self.neighbors
1817 }
1818
1819 fn entry_and_timer_heap(
1820 &mut self,
1821 addr: SpecifiedAddr<I::Addr>,
1822 ) -> (Entry<'_, SpecifiedAddr<I::Addr>, NeighborState<D, BT>>, &mut TimerHeap<I, BT>) {
1823 let Self { neighbors, timer_heap, .. } = self;
1824 (neighbors.entry(addr), timer_heap)
1825 }
1826}
1827
1828impl<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D> + TimerContext> NudState<I, D, BC> {
1829 pub fn new<
1831 DeviceId: WeakDeviceIdentifier,
1832 CC: CoreTimerContext<NudTimerId<I, D, DeviceId>, BC>,
1833 >(
1834 bindings_ctx: &mut BC,
1835 device_id: DeviceId,
1836 ) -> Self {
1837 Self {
1838 neighbors: Default::default(),
1839 last_gc: None,
1840 timer_heap: TimerHeap::new::<_, _, CC>(bindings_ctx, device_id),
1841 }
1842 }
1843}
1844
1845pub trait NudBindingsContext<I: Ip, D: LinkDevice, DeviceId>:
1847 TimerContext
1848 + LinkResolutionContext<D>
1849 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1850 + NudBindingsTypes<D>
1851{
1852}
1853
1854impl<
1855 I: Ip,
1856 D: LinkDevice,
1857 DeviceId,
1858 BC: TimerContext
1859 + LinkResolutionContext<D>
1860 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1861 + NudBindingsTypes<D>,
1862> NudBindingsContext<I, D, DeviceId> for BC
1863{
1864}
1865
1866pub trait NudBindingsTypes<D: LinkDevice>:
1868 LinkResolutionContext<D> + InstantBindingsTypes + TimerBindingsTypes + TxMetadataBindingsTypes
1869{
1870}
1871
1872impl<BT, D> NudBindingsTypes<D> for BT
1873where
1874 D: LinkDevice,
1875 BT: LinkResolutionContext<D>
1876 + InstantBindingsTypes
1877 + TimerBindingsTypes
1878 + TxMetadataBindingsTypes,
1879{
1880}
1881
1882pub trait LinkResolutionContext<D: LinkDevice> {
1884 type Notifier: LinkResolutionNotifier<D>;
1887}
1888
1889pub trait LinkResolutionNotifier<D: LinkDevice>: Debug + Sized + Send {
1892 type Observer;
1895
1896 fn new() -> (Self, Self::Observer);
1898
1899 fn notify(self, result: Result<D::Address, AddressResolutionFailed>);
1902}
1903
1904pub trait NudContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
1906 type ConfigCtx<'a>: NudConfigContext<I>;
1908 type SenderCtx<'a>: NudSenderContext<I, D, BC, DeviceId = Self::DeviceId>;
1910
1911 fn with_nud_state_mut_and_sender_ctx<
1914 O,
1915 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1916 >(
1917 &mut self,
1918 device_id: &Self::DeviceId,
1919 cb: F,
1920 ) -> O;
1921
1922 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1925 &mut self,
1926 device_id: &Self::DeviceId,
1927 cb: F,
1928 ) -> O;
1929
1930 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1932 &mut self,
1933 device_id: &Self::DeviceId,
1934 cb: F,
1935 ) -> O;
1936
1937 fn send_neighbor_solicitation(
1942 &mut self,
1943 bindings_ctx: &mut BC,
1944 device_id: &Self::DeviceId,
1945 lookup_addr: SpecifiedAddr<I::Addr>,
1946 remote_link_addr: Option<D::Address>,
1947 );
1948}
1949
1950pub trait UseDelegateNudContext {}
1953
1954pub trait DelegateNudContext<I: Ip>: UseDelegateNudContext + Sized {
1960 type Delegate<T>: ref_cast::RefCast<From = T>;
1962 fn wrap(&mut self) -> &mut Self::Delegate<Self> {
1964 <Self::Delegate<Self> as ref_cast::RefCast>::ref_cast_mut(self)
1965 }
1966}
1967
1968impl<I, D, BC, CC> NudContext<I, D, BC> for CC
1969where
1970 I: Ip,
1971 D: LinkDevice,
1972 BC: NudBindingsTypes<D>,
1973 CC: DelegateNudContext<I, Delegate<CC>: NudContext<I, D, BC, DeviceId = CC::DeviceId>>
1974 + UseDelegateNudContext
1977 + DeviceIdContext<D>,
1978{
1979 type ConfigCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::ConfigCtx<'a>;
1980 type SenderCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::SenderCtx<'a>;
1981 fn with_nud_state_mut_and_sender_ctx<
1982 O,
1983 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1984 >(
1985 &mut self,
1986 device_id: &Self::DeviceId,
1987 cb: F,
1988 ) -> O {
1989 self.wrap().with_nud_state_mut_and_sender_ctx(device_id, cb)
1990 }
1991
1992 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1993 &mut self,
1994 device_id: &Self::DeviceId,
1995 cb: F,
1996 ) -> O {
1997 self.wrap().with_nud_state_mut(device_id, cb)
1998 }
1999 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
2000 &mut self,
2001 device_id: &Self::DeviceId,
2002 cb: F,
2003 ) -> O {
2004 self.wrap().with_nud_state(device_id, cb)
2005 }
2006 fn send_neighbor_solicitation(
2007 &mut self,
2008 bindings_ctx: &mut BC,
2009 device_id: &Self::DeviceId,
2010 lookup_addr: SpecifiedAddr<I::Addr>,
2011 remote_link_addr: Option<D::Address>,
2012 ) {
2013 self.wrap().send_neighbor_solicitation(
2014 bindings_ctx,
2015 device_id,
2016 lookup_addr,
2017 remote_link_addr,
2018 )
2019 }
2020}
2021
2022pub trait NudIcmpIpExt: packet_formats::ip::IpExt {
2024 type Metadata;
2027
2028 fn extract_metadata<B: SplitByteSlice>(packet: &Self::Packet<B>) -> Self::Metadata;
2030}
2031
2032impl NudIcmpIpExt for Ipv4 {
2033 type Metadata = Ipv4FragmentType;
2034
2035 fn extract_metadata<B: SplitByteSlice>(packet: &Ipv4Packet<B>) -> Self::Metadata {
2036 packet.fragment_type()
2037 }
2038}
2039
2040impl NudIcmpIpExt for Ipv6 {
2041 type Metadata = ();
2042
2043 fn extract_metadata<B: SplitByteSlice>(_packet: &Ipv6Packet<B>) -> Self::Metadata {
2044 ()
2045 }
2046}
2047
2048pub trait NudIcmpContext<I: NudIcmpIpExt, D: LinkDevice, BC>: DeviceIdContext<D> {
2051 fn send_icmp_dest_unreachable(
2058 &mut self,
2059 bindings_ctx: &mut BC,
2060 frame: Buf<Vec<u8>>,
2061 device_id: Option<&Self::DeviceId>,
2062 original_src_ip: SocketIpAddr<I::Addr>,
2063 original_dst_ip: SocketIpAddr<I::Addr>,
2064 header_len: usize,
2065 proto: I::Proto,
2066 metadata: I::Metadata,
2067 );
2068}
2069
2070#[derive(Clone, Debug)]
2072pub struct NudUserConfig {
2073 pub max_unicast_solicitations: NonZeroU16,
2078 pub max_multicast_solicitations: NonZeroU16,
2083 pub base_reachable_time: NonZeroDuration,
2089 pub retrans_timer: NonZeroDuration,
2095}
2096
2097impl Default for NudUserConfig {
2098 fn default() -> Self {
2099 NudUserConfig {
2100 max_unicast_solicitations: DEFAULT_MAX_UNICAST_SOLICIT,
2101 max_multicast_solicitations: DEFAULT_MAX_MULTICAST_SOLICIT,
2102 base_reachable_time: DEFAULT_BASE_REACHABLE_TIME,
2103 retrans_timer: DEFAULT_RETRANS_TIMER,
2104 }
2105 }
2106}
2107
2108#[allow(missing_docs)]
2112#[derive(Clone, Debug, Eq, PartialEq, Default)]
2113pub struct NudUserConfigUpdate {
2114 pub max_unicast_solicitations: Option<NonZeroU16>,
2115 pub max_multicast_solicitations: Option<NonZeroU16>,
2116 pub base_reachable_time: Option<NonZeroDuration>,
2117 pub retrans_timer: Option<NonZeroDuration>,
2118}
2119
2120impl NudUserConfigUpdate {
2121 pub fn apply_and_take_previous(mut self, config: &mut NudUserConfig) -> Self {
2124 fn swap_if_set<T>(opt: &mut Option<T>, target: &mut T) {
2125 if let Some(opt) = opt.as_mut() {
2126 core::mem::swap(opt, target)
2127 }
2128 }
2129 let Self {
2130 max_unicast_solicitations,
2131 max_multicast_solicitations,
2132 base_reachable_time,
2133 retrans_timer,
2134 } = &mut self;
2135 swap_if_set(max_unicast_solicitations, &mut config.max_unicast_solicitations);
2136 swap_if_set(max_multicast_solicitations, &mut config.max_multicast_solicitations);
2137 swap_if_set(base_reachable_time, &mut config.base_reachable_time);
2138 swap_if_set(retrans_timer, &mut config.retrans_timer);
2139
2140 self
2141 }
2142}
2143
2144pub trait NudConfigContext<I: Ip> {
2147 fn retransmit_timeout(&mut self) -> NonZeroDuration;
2154
2155 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O;
2157
2158 fn max_unicast_solicit(&mut self) -> NonZeroU16 {
2160 self.with_nud_user_config(|NudUserConfig { max_unicast_solicitations, .. }| {
2161 *max_unicast_solicitations
2162 })
2163 }
2164
2165 fn max_multicast_solicit(&mut self) -> NonZeroU16 {
2167 self.with_nud_user_config(|NudUserConfig { max_multicast_solicitations, .. }| {
2168 *max_multicast_solicitations
2169 })
2170 }
2171
2172 fn base_reachable_time(&mut self) -> NonZeroDuration {
2175 self.with_nud_user_config(|NudUserConfig { base_reachable_time, .. }| *base_reachable_time)
2176 }
2177
2178 fn override_lock_time(&mut self) -> Duration;
2181}
2182
2183pub trait NudSenderContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>:
2186 NudConfigContext<I> + DeviceIdContext<D>
2187{
2188 fn send_ip_packet_to_neighbor_link_addr<S>(
2190 &mut self,
2191 bindings_ctx: &mut BC,
2192 neighbor_link_addr: D::Address,
2193 body: S,
2194 meta: BC::TxMetadata,
2195 ) -> Result<(), SendFrameError<S>>
2196 where
2197 S: Serializer,
2198 S::Buffer: BufferMut;
2199}
2200
2201pub trait NudIpHandler<I: Ip, BC>: DeviceIdContext<AnyDevice> {
2203 fn handle_neighbor_probe(
2208 &mut self,
2209 bindings_ctx: &mut BC,
2210 device_id: &Self::DeviceId,
2211 neighbor: SpecifiedAddr<I::Addr>,
2212 link_addr: &[u8],
2213 );
2214
2215 fn handle_neighbor_confirmation(
2219 &mut self,
2220 bindings_ctx: &mut BC,
2221 device_id: &Self::DeviceId,
2222 neighbor: SpecifiedAddr<I::Addr>,
2223 link_addr: Option<&[u8]>,
2224 flags: ConfirmationFlags,
2225 );
2226
2227 fn flush_neighbor_table(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2229}
2230
2231#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2233pub enum LinkResolutionResult<A: LinkAddress, Observer> {
2234 Resolved(A),
2236 Pending(Observer),
2238}
2239
2240pub trait NudHandler<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
2242 fn handle_neighbor_update(
2245 &mut self,
2246 bindings_ctx: &mut BC,
2247 device_id: &Self::DeviceId,
2248 neighbor: SpecifiedAddr<I::Addr>,
2253 source: DynamicNeighborUpdateSource<D::Address>,
2254 );
2255
2256 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2258
2259 fn send_ip_packet_to_neighbor<S>(
2264 &mut self,
2265 bindings_ctx: &mut BC,
2266 device_id: &Self::DeviceId,
2267 neighbor: SpecifiedAddr<I::Addr>,
2268 body: S,
2269 meta: BC::TxMetadata,
2270 ) -> Result<(), SendFrameError<S>>
2271 where
2272 S: Serializer,
2273 S::Buffer: BufferMut;
2274}
2275
2276enum TransmitProbe<A> {
2277 Multicast,
2278 Unicast(A),
2279}
2280
2281impl<
2282 I: NudIcmpIpExt,
2283 D: LinkDevice,
2284 BC: NudBindingsContext<I, D, CC::DeviceId>,
2285 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2286> HandleableTimer<CC, BC> for NudTimerId<I, D, CC::WeakDeviceId>
2287{
2288 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
2289 let Self { device_id, timer_type, _marker: PhantomData } = self;
2290 let Some(device_id) = device_id.upgrade() else {
2291 return;
2292 };
2293 match timer_type {
2294 NudTimerType::Neighbor => handle_neighbor_timer(core_ctx, bindings_ctx, device_id),
2295 NudTimerType::GarbageCollection => collect_garbage(core_ctx, bindings_ctx, device_id),
2296 }
2297 }
2298}
2299
2300fn handle_neighbor_timer<I, D, CC, BC>(
2301 core_ctx: &mut CC,
2302 bindings_ctx: &mut BC,
2303 device_id: CC::DeviceId,
2304) where
2305 I: NudIcmpIpExt,
2306 D: LinkDevice,
2307 BC: NudBindingsContext<I, D, CC::DeviceId>,
2308 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2309{
2310 enum Action<L, A, M> {
2311 TransmitProbe { probe: TransmitProbe<L>, to: A },
2312 SendIcmpDestUnreachable(VecDeque<(Buf<Vec<u8>>, M)>),
2313 }
2314 let action = core_ctx.with_nud_state_mut(
2315 &device_id,
2316 |NudState { neighbors, last_gc, timer_heap }, core_ctx| {
2317 let (lookup_addr, event) = timer_heap.pop_neighbor(bindings_ctx)?;
2318 let num_entries = neighbors.len();
2319 let mut entry = match neighbors.entry(lookup_addr) {
2320 Entry::Occupied(entry) => entry,
2321 Entry::Vacant(_) => panic!("timer fired for invalid entry"),
2322 };
2323
2324 match entry.get_mut() {
2325 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)) => {
2326 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2327
2328 if incomplete.schedule_timer_if_should_retransmit(
2329 core_ctx,
2330 bindings_ctx,
2331 timer_heap,
2332 lookup_addr,
2333 ) {
2334 Some(Action::TransmitProbe {
2335 probe: TransmitProbe::Multicast,
2336 to: lookup_addr,
2337 })
2338 } else {
2339 debug!("neighbor resolution failed for {lookup_addr}; removing entry");
2347 let Incomplete {
2348 transmit_counter: _,
2349 ref mut pending_frames,
2350 notifiers: _,
2351 _marker,
2352 } = assert_matches!(
2353 entry.remove(),
2354 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete))
2355 => incomplete
2356 );
2357 let pending_frames = core::mem::take(pending_frames);
2358 bindings_ctx.on_event(Event::removed(
2359 &device_id,
2360 lookup_addr,
2361 bindings_ctx.now(),
2362 ));
2363 Some(Action::SendIcmpDestUnreachable(pending_frames))
2364 }
2365 }
2366 NeighborState::Dynamic(DynamicNeighborState::Probe(probe)) => {
2367 assert_eq!(event, NudEvent::RetransmitUnicastProbe);
2368
2369 let Probe { link_address, transmit_counter: _ } = probe;
2370 let link_address = *link_address;
2371 if probe.schedule_timer_if_should_retransmit(
2372 core_ctx,
2373 bindings_ctx,
2374 timer_heap,
2375 lookup_addr,
2376 ) {
2377 Some(Action::TransmitProbe {
2378 probe: TransmitProbe::Unicast(link_address),
2379 to: lookup_addr,
2380 })
2381 } else {
2382 let unreachable =
2383 probe.enter_unreachable(bindings_ctx, timer_heap, num_entries, last_gc);
2384 *entry.get_mut() =
2385 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable));
2386 let event_state = entry.get_mut().to_event_state();
2387 let event = Event::changed(
2388 &device_id,
2389 event_state,
2390 lookup_addr,
2391 bindings_ctx.now(),
2392 );
2393 bindings_ctx.on_event(event);
2394 None
2395 }
2396 }
2397 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable)) => {
2398 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2399 unreachable
2400 .handle_timer(core_ctx, bindings_ctx, timer_heap, &device_id, lookup_addr)
2401 .map(|probe| Action::TransmitProbe { probe, to: lookup_addr })
2402 }
2403 NeighborState::Dynamic(DynamicNeighborState::Reachable(Reachable {
2404 link_address,
2405 last_confirmed_at,
2406 })) => {
2407 assert_eq!(event, NudEvent::ReachableTime);
2408 let link_address = *link_address;
2409
2410 let expiration =
2411 last_confirmed_at.saturating_add(core_ctx.base_reachable_time().get());
2412 if expiration > bindings_ctx.now() {
2413 timer_heap.schedule_neighbor_at(
2414 bindings_ctx,
2415 expiration,
2416 lookup_addr,
2417 NudEvent::ReachableTime,
2418 );
2419 } else {
2420 *entry.get_mut() =
2428 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2429 link_address,
2430 }));
2431 let event_state = entry.get_mut().to_event_state();
2432 let event = Event::changed(
2433 &device_id,
2434 event_state,
2435 lookup_addr,
2436 bindings_ctx.now(),
2437 );
2438 bindings_ctx.on_event(event);
2439
2440 timer_heap.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
2444 }
2445
2446 None
2447 }
2448 NeighborState::Dynamic(DynamicNeighborState::Delay(delay)) => {
2449 assert_eq!(event, NudEvent::DelayFirstProbe);
2450
2451 let probe @ Probe { link_address, transmit_counter: _ } =
2458 delay.enter_probe(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2459 *entry.get_mut() = NeighborState::Dynamic(DynamicNeighborState::Probe(probe));
2460 let event_state = entry.get_mut().to_event_state();
2461 bindings_ctx.on_event(Event::changed(
2462 &device_id,
2463 event_state,
2464 lookup_addr,
2465 bindings_ctx.now(),
2466 ));
2467
2468 Some(Action::TransmitProbe {
2469 probe: TransmitProbe::Unicast(link_address),
2470 to: lookup_addr,
2471 })
2472 }
2473 state @ (NeighborState::Static(_)
2474 | NeighborState::Dynamic(DynamicNeighborState::Stale(_))) => {
2475 panic!("timer unexpectedly fired in state {state:?}")
2476 }
2477 }
2478 },
2479 );
2480
2481 match action {
2482 Some(Action::SendIcmpDestUnreachable(mut pending_frames)) => {
2483 for (mut frame, meta) in pending_frames.drain(..) {
2484 core::mem::drop(meta);
2487
2488 let Some((packet, original_src_ip, original_dst_ip)) = frame
2492 .parse_mut::<I::Packet<_>>()
2493 .map_err(|e| {
2494 warn!("not sending ICMP dest unreachable due to parsing error: {:?}", e);
2495 })
2496 .ok()
2497 .and_then(|packet| {
2498 let original_src_ip = SocketIpAddr::new(packet.src_ip())?;
2499 let original_dst_ip = SocketIpAddr::new(packet.dst_ip())?;
2500 Some((packet, original_src_ip, original_dst_ip))
2501 })
2502 .or_else(|| {
2503 core_ctx.counters().icmp_dest_unreachable_dropped.increment();
2504 None
2505 })
2506 else {
2507 continue;
2508 };
2509 let header_metadata = I::extract_metadata(&packet);
2510 let header_len = packet.parse_metadata().header_len();
2511 let proto = packet.proto();
2512 let metadata = packet.parse_metadata();
2513 core::mem::drop(packet);
2514 frame.undo_parse(metadata);
2515 core_ctx.send_icmp_dest_unreachable(
2516 bindings_ctx,
2517 frame,
2518 original_src_ip.as_ref().must_have_zone().then_some(&device_id),
2530 original_src_ip,
2531 original_dst_ip,
2532 header_len,
2533 proto,
2534 header_metadata,
2535 );
2536 }
2537 }
2538 Some(Action::TransmitProbe { probe, to }) => {
2539 let remote_link_addr = match probe {
2540 TransmitProbe::Multicast => None,
2541 TransmitProbe::Unicast(link_addr) => Some(link_addr),
2542 };
2543 core_ctx.send_neighbor_solicitation(bindings_ctx, &device_id, to, remote_link_addr);
2544 }
2545 None => {}
2546 }
2547}
2548
2549impl<I: Ip, D: LinkDevice, BC: NudBindingsContext<I, D, CC::DeviceId>, CC: NudContext<I, D, BC>>
2550 NudHandler<I, D, BC> for CC
2551{
2552 fn handle_neighbor_update(
2553 &mut self,
2554 bindings_ctx: &mut BC,
2555 device_id: &CC::DeviceId,
2556 neighbor: SpecifiedAddr<I::Addr>,
2557 source: DynamicNeighborUpdateSource<D::Address>,
2558 ) {
2559 debug!("received neighbor {:?} from {}", source, neighbor);
2560 self.with_nud_state_mut_and_sender_ctx(
2561 device_id,
2562 |NudState { neighbors, last_gc, timer_heap }, core_ctx| {
2563 let num_entries = neighbors.len();
2564 match neighbors.entry(neighbor) {
2565 Entry::Vacant(e) => match source {
2566 DynamicNeighborUpdateSource::Probe { link_address } => {
2567 insert_new_entry(
2575 bindings_ctx,
2576 device_id,
2577 e,
2578 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2579 link_address,
2580 })),
2581 );
2582
2583 timer_heap.maybe_schedule_gc(bindings_ctx, neighbors.len(), last_gc);
2586 }
2587 DynamicNeighborUpdateSource::Confirmation { .. } => {}
2596 },
2597 Entry::Occupied(e) => match e.into_mut() {
2598 NeighborState::Dynamic(e) => match source {
2599 DynamicNeighborUpdateSource::Probe { link_address } => e.handle_probe(
2600 core_ctx,
2601 bindings_ctx,
2602 timer_heap,
2603 device_id,
2604 neighbor,
2605 link_address,
2606 num_entries,
2607 last_gc,
2608 ),
2609 DynamicNeighborUpdateSource::Confirmation { link_address, flags } => e
2610 .handle_confirmation(
2611 core_ctx,
2612 bindings_ctx,
2613 timer_heap,
2614 device_id,
2615 neighbor,
2616 link_address,
2617 flags,
2618 num_entries,
2619 last_gc,
2620 ),
2621 },
2622 NeighborState::Static(_) => {}
2623 },
2624 }
2625 },
2626 );
2627 }
2628
2629 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
2630 self.with_nud_state_mut(
2631 device_id,
2632 |NudState { neighbors, last_gc: _, timer_heap }, _config| {
2633 neighbors.drain().for_each(|(neighbor, state)| {
2634 match state {
2635 NeighborState::Dynamic(mut entry) => {
2636 entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
2637 }
2638 NeighborState::Static(_) => {}
2639 }
2640 bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
2641 });
2642 },
2643 );
2644 }
2645
2646 fn send_ip_packet_to_neighbor<S>(
2647 &mut self,
2648 bindings_ctx: &mut BC,
2649 device_id: &Self::DeviceId,
2650 lookup_addr: SpecifiedAddr<I::Addr>,
2651 body: S,
2652 meta: BC::TxMetadata,
2653 ) -> Result<(), SendFrameError<S>>
2654 where
2655 S: Serializer,
2656 S::Buffer: BufferMut,
2657 {
2658 let do_multicast_solicit = self.with_nud_state_mut_and_sender_ctx(
2659 device_id,
2660 |state, core_ctx| -> Result<_, SendFrameError<S>> {
2661 let (entry, timer_heap) = state.entry_and_timer_heap(lookup_addr);
2662 match entry {
2663 Entry::Vacant(e) => {
2664 let incomplete = Incomplete::new_with_packet(
2665 core_ctx,
2666 bindings_ctx,
2667 timer_heap,
2668 lookup_addr,
2669 body,
2670 meta,
2671 )
2672 .map_err(|e| e.err_into())?;
2673 insert_new_entry(
2674 bindings_ctx,
2675 device_id,
2676 e,
2677 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)),
2678 );
2679 Ok(true)
2680 }
2681 Entry::Occupied(e) => {
2682 match e.into_mut() {
2683 NeighborState::Static(link_address) => {
2684 core_ctx.send_ip_packet_to_neighbor_link_addr(
2692 bindings_ctx,
2693 *link_address,
2694 body,
2695 meta,
2696 )?;
2697
2698 Ok(false)
2699 }
2700 NeighborState::Dynamic(e) => {
2701 let do_multicast_solicit = e.handle_packet_queued_to_send(
2702 core_ctx,
2703 bindings_ctx,
2704 timer_heap,
2705 device_id,
2706 lookup_addr,
2707 body,
2708 meta,
2709 )?;
2710
2711 Ok(do_multicast_solicit)
2712 }
2713 }
2714 }
2715 }
2716 },
2717 )?;
2718
2719 if do_multicast_solicit {
2720 self.send_neighbor_solicitation(
2721 bindings_ctx,
2722 &device_id,
2723 lookup_addr,
2724 None,
2725 );
2726 }
2727
2728 Ok(())
2729 }
2730}
2731
2732fn insert_new_entry<
2733 I: Ip,
2734 D: LinkDevice,
2735 DeviceId: DeviceIdentifier,
2736 BC: NudBindingsContext<I, D, DeviceId>,
2737>(
2738 bindings_ctx: &mut BC,
2739 device_id: &DeviceId,
2740 vacant: hash_map::VacantEntry<'_, SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2741 entry: NeighborState<D, BC>,
2742) {
2743 let lookup_addr = *vacant.key();
2744 let state = vacant.insert(entry);
2745 let event = Event::added(device_id, state.to_event_state(), lookup_addr, bindings_ctx.now());
2746 bindings_ctx.on_event(event);
2747}
2748
2749pub fn confirm_reachable<I, D, CC, BC>(
2752 core_ctx: &mut CC,
2753 bindings_ctx: &mut BC,
2754 device_id: &CC::DeviceId,
2755 neighbor: SpecifiedAddr<I::Addr>,
2756) where
2757 I: Ip,
2758 D: LinkDevice,
2759 BC: NudBindingsContext<I, D, CC::DeviceId>,
2760 CC: NudContext<I, D, BC>,
2761{
2762 core_ctx.with_nud_state_mut_and_sender_ctx(
2763 device_id,
2764 |NudState { neighbors, last_gc: _, timer_heap }, core_ctx| {
2765 match neighbors.entry(neighbor) {
2766 Entry::Vacant(_) => {
2767 debug!(
2768 "got an upper-layer confirmation for non-existent neighbor entry {}",
2769 neighbor
2770 );
2771 }
2772 Entry::Occupied(e) => match e.into_mut() {
2773 NeighborState::Static(_) => {}
2774 NeighborState::Dynamic(e) => {
2775 let link_address = match e {
2785 DynamicNeighborState::Incomplete(_) => return,
2786 DynamicNeighborState::Reachable(Reachable {
2787 link_address,
2788 last_confirmed_at: _,
2789 })
2790 | DynamicNeighborState::Stale(Stale { link_address })
2791 | DynamicNeighborState::Delay(Delay { link_address })
2792 | DynamicNeighborState::Probe(Probe {
2793 link_address,
2794 transmit_counter: _,
2795 })
2796 | DynamicNeighborState::Unreachable(Unreachable {
2797 link_address,
2798 mode: _,
2799 }) => *link_address,
2800 };
2801 e.enter_reachable(
2802 core_ctx,
2803 bindings_ctx,
2804 timer_heap,
2805 device_id,
2806 neighbor,
2807 link_address,
2808 );
2809 }
2810 },
2811 }
2812 },
2813 );
2814}
2815
2816fn collect_garbage<I, D, CC, BC>(core_ctx: &mut CC, bindings_ctx: &mut BC, device_id: CC::DeviceId)
2827where
2828 I: Ip,
2829 D: LinkDevice,
2830 BC: NudBindingsContext<I, D, CC::DeviceId>,
2831 CC: NudContext<I, D, BC>,
2832{
2833 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, last_gc, timer_heap }, _| {
2834 let max_to_remove = neighbors.len().saturating_sub(MAX_ENTRIES);
2835 if max_to_remove == 0 {
2836 return;
2837 }
2838
2839 *last_gc = Some(bindings_ctx.now());
2840
2841 fn gc_priority<D: LinkDevice, BT: NudBindingsTypes<D>>(
2850 state: &DynamicNeighborState<D, BT>,
2851 ) -> usize {
2852 match state {
2853 DynamicNeighborState::Incomplete(_)
2854 | DynamicNeighborState::Reachable(_)
2855 | DynamicNeighborState::Delay(_)
2856 | DynamicNeighborState::Probe(_) => unreachable!(
2857 "the netstack should only ever discard STALE or UNREACHABLE entries; \
2858 found {:?}",
2859 state,
2860 ),
2861 DynamicNeighborState::Stale(_) => 0,
2862 DynamicNeighborState::Unreachable(Unreachable {
2863 link_address: _,
2864 mode: UnreachableMode::Backoff { probes_sent: _, packet_sent: _ },
2865 }) => 1,
2866 DynamicNeighborState::Unreachable(Unreachable {
2867 link_address: _,
2868 mode: UnreachableMode::WaitingForPacketSend,
2869 }) => 2,
2870 }
2871 }
2872
2873 struct SortEntry<'a, K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> {
2874 key: K,
2875 state: &'a mut DynamicNeighborState<D, BT>,
2876 }
2877
2878 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialEq for SortEntry<'_, K, D, BT> {
2879 fn eq(&self, other: &Self) -> bool {
2880 self.key == other.key && gc_priority(self.state) == gc_priority(other.state)
2881 }
2882 }
2883 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Eq for SortEntry<'_, K, D, BT> {}
2884 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Ord for SortEntry<'_, K, D, BT> {
2885 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2886 gc_priority(self.state).cmp(&gc_priority(other.state)).reverse()
2890 }
2891 }
2892 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialOrd for SortEntry<'_, K, D, BT> {
2893 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
2894 Some(self.cmp(&other))
2895 }
2896 }
2897
2898 let mut entries_to_remove = BinaryHeap::with_capacity(max_to_remove);
2899 for (ip, neighbor) in neighbors.iter_mut() {
2900 match neighbor {
2901 NeighborState::Static(_) => {
2902 continue;
2904 }
2905 NeighborState::Dynamic(state) => {
2906 match state {
2907 DynamicNeighborState::Incomplete(_)
2908 | DynamicNeighborState::Reachable(_)
2909 | DynamicNeighborState::Delay(_)
2910 | DynamicNeighborState::Probe(_) => {
2911 continue;
2913 }
2914 DynamicNeighborState::Stale(_) | DynamicNeighborState::Unreachable(_) => {
2915 if entries_to_remove.len() < max_to_remove {
2917 entries_to_remove.push(SortEntry { key: ip, state });
2918 continue;
2919 }
2920 let minimum = entries_to_remove
2924 .peek()
2925 .expect("heap should have at least 1 entry");
2926 let candidate = SortEntry { key: ip, state };
2927 if &candidate > minimum {
2928 let _: SortEntry<'_, _, _, _> = entries_to_remove.pop().unwrap();
2929 entries_to_remove.push(candidate);
2930 }
2931 }
2932 }
2933 }
2934 }
2935 }
2936
2937 let entries_to_remove = entries_to_remove
2938 .into_iter()
2939 .map(|SortEntry { key: neighbor, state }| {
2940 state.cancel_timer(bindings_ctx, timer_heap, *neighbor);
2941 *neighbor
2942 })
2943 .collect::<Vec<_>>();
2944
2945 for neighbor in entries_to_remove {
2946 assert_matches!(neighbors.remove(&neighbor), Some(_));
2947 bindings_ctx.on_event(Event::removed(&device_id, neighbor, bindings_ctx.now()));
2948 }
2949 })
2950}
2951
2952#[cfg(test)]
2953mod tests {
2954 use alloc::vec;
2955
2956 use ip_test_macro::ip_test;
2957 use net_declare::{net_ip_v4, net_ip_v6};
2958 use net_types::ip::{Ipv4Addr, Ipv6Addr};
2959 use netstack3_base::testutil::{
2960 FakeBindingsCtx, FakeCoreCtx, FakeInstant, FakeLinkAddress, FakeLinkDevice,
2961 FakeLinkDeviceId, FakeTimerCtxExt as _, FakeTxMetadata, FakeWeakDeviceId,
2962 };
2963 use netstack3_base::{
2964 CtxPair, InstantContext, IntoCoreTimerCtx, SendFrameContext as _, SendFrameErrorReason,
2965 };
2966 use netstack3_hashmap::HashSet;
2967 use test_case::test_case;
2968
2969 use super::*;
2970 use crate::internal::device::nud::api::NeighborApi;
2971
2972 struct FakeNudContext<I: Ip, D: LinkDevice> {
2973 state: NudState<I, D, FakeBindingsCtxImpl<I>>,
2974 counters: NudCounters<I>,
2975 }
2976
2977 struct FakeConfigContext {
2978 retrans_timer: NonZeroDuration,
2979 nud_config: NudUserConfig,
2980 }
2981
2982 struct FakeCoreCtxImpl<I: Ip> {
2983 nud: FakeNudContext<I, FakeLinkDevice>,
2984 inner: FakeInnerCtxImpl<I>,
2985 }
2986
2987 type FakeInnerCtxImpl<I> =
2988 FakeCoreCtx<FakeConfigContext, FakeNudMessageMeta<I>, FakeLinkDeviceId>;
2989
2990 #[derive(Debug, PartialEq, Eq)]
2991 enum FakeNudMessageMeta<I: Ip> {
2992 NeighborSolicitation {
2993 lookup_addr: SpecifiedAddr<I::Addr>,
2994 remote_link_addr: Option<FakeLinkAddress>,
2995 },
2996 IpFrame {
2997 dst_link_address: FakeLinkAddress,
2998 },
2999 IcmpDestUnreachable,
3000 }
3001
3002 type FakeBindingsCtxImpl<I> = FakeBindingsCtx<
3003 NudTimerId<I, FakeLinkDevice, FakeWeakDeviceId<FakeLinkDeviceId>>,
3004 Event<FakeLinkAddress, FakeLinkDeviceId, I, FakeInstant>,
3005 (),
3006 (),
3007 >;
3008
3009 impl<I: Ip> FakeCoreCtxImpl<I> {
3010 fn new(bindings_ctx: &mut FakeBindingsCtxImpl<I>) -> Self {
3011 Self {
3012 nud: {
3013 FakeNudContext {
3014 state: NudState::new::<_, IntoCoreTimerCtx>(
3015 bindings_ctx,
3016 FakeWeakDeviceId(FakeLinkDeviceId),
3017 ),
3018 counters: Default::default(),
3019 }
3020 },
3021 inner: FakeInnerCtxImpl::with_state(FakeConfigContext {
3022 retrans_timer: ONE_SECOND,
3023 nud_config: NudUserConfig {
3027 max_unicast_solicitations: NonZeroU16::new(4).unwrap(),
3028 max_multicast_solicitations: NonZeroU16::new(5).unwrap(),
3029 base_reachable_time: NonZeroDuration::from_secs(23).unwrap(),
3030 retrans_timer: NonZeroDuration::from_secs(3).unwrap(),
3031 },
3032 }),
3033 }
3034 }
3035 }
3036
3037 fn new_context<I: Ip>() -> CtxPair<FakeCoreCtxImpl<I>, FakeBindingsCtxImpl<I>> {
3038 CtxPair::with_default_bindings_ctx(|bindings_ctx| FakeCoreCtxImpl::<I>::new(bindings_ctx))
3039 }
3040
3041 impl<I: Ip> DeviceIdContext<FakeLinkDevice> for FakeCoreCtxImpl<I> {
3042 type DeviceId = FakeLinkDeviceId;
3043 type WeakDeviceId = FakeWeakDeviceId<FakeLinkDeviceId>;
3044 }
3045
3046 impl<I: Ip> NudContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeCoreCtxImpl<I> {
3047 type ConfigCtx<'a> = FakeConfigContext;
3048
3049 type SenderCtx<'a> = FakeInnerCtxImpl<I>;
3050
3051 fn with_nud_state_mut_and_sender_ctx<
3052 O,
3053 F: FnOnce(
3054 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3055 &mut Self::SenderCtx<'_>,
3056 ) -> O,
3057 >(
3058 &mut self,
3059 _device_id: &Self::DeviceId,
3060 cb: F,
3061 ) -> O {
3062 cb(&mut self.nud.state, &mut self.inner)
3063 }
3064
3065 fn with_nud_state_mut<
3066 O,
3067 F: FnOnce(
3068 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3069 &mut Self::ConfigCtx<'_>,
3070 ) -> O,
3071 >(
3072 &mut self,
3073 &FakeLinkDeviceId: &FakeLinkDeviceId,
3074 cb: F,
3075 ) -> O {
3076 cb(&mut self.nud.state, &mut self.inner.state)
3077 }
3078
3079 fn with_nud_state<
3080 O,
3081 F: FnOnce(&NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>) -> O,
3082 >(
3083 &mut self,
3084 &FakeLinkDeviceId: &FakeLinkDeviceId,
3085 cb: F,
3086 ) -> O {
3087 cb(&self.nud.state)
3088 }
3089
3090 fn send_neighbor_solicitation(
3091 &mut self,
3092 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3093 &FakeLinkDeviceId: &FakeLinkDeviceId,
3094 lookup_addr: SpecifiedAddr<I::Addr>,
3095 remote_link_addr: Option<FakeLinkAddress>,
3096 ) {
3097 self.inner
3098 .send_frame(
3099 bindings_ctx,
3100 FakeNudMessageMeta::NeighborSolicitation { lookup_addr, remote_link_addr },
3101 Buf::new(Vec::new(), ..),
3102 )
3103 .unwrap()
3104 }
3105 }
3106
3107 impl<I: NudIcmpIpExt> NudIcmpContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>
3108 for FakeCoreCtxImpl<I>
3109 {
3110 fn send_icmp_dest_unreachable(
3111 &mut self,
3112 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3113 frame: Buf<Vec<u8>>,
3114 _device_id: Option<&Self::DeviceId>,
3115 _original_src_ip: SocketIpAddr<I::Addr>,
3116 _original_dst_ip: SocketIpAddr<I::Addr>,
3117 _header_len: usize,
3118 _proto: I::Proto,
3119 _metadata: I::Metadata,
3120 ) {
3121 self.inner
3122 .send_frame(bindings_ctx, FakeNudMessageMeta::IcmpDestUnreachable, frame)
3123 .unwrap()
3124 }
3125 }
3126
3127 impl<I: Ip> CounterContext<NudCounters<I>> for FakeCoreCtxImpl<I> {
3128 fn counters(&self) -> &NudCounters<I> {
3129 &self.nud.counters
3130 }
3131 }
3132
3133 impl<I: Ip> NudConfigContext<I> for FakeConfigContext {
3134 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3135 self.retrans_timer
3136 }
3137
3138 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3139 cb(&self.nud_config)
3140 }
3141
3142 fn override_lock_time(&mut self) -> Duration {
3143 Duration::ZERO
3144 }
3145 }
3146
3147 impl<I: Ip> NudSenderContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeInnerCtxImpl<I> {
3148 fn send_ip_packet_to_neighbor_link_addr<S>(
3149 &mut self,
3150 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3151 dst_link_address: FakeLinkAddress,
3152 body: S,
3153 _tx_meta: FakeTxMetadata,
3154 ) -> Result<(), SendFrameError<S>>
3155 where
3156 S: Serializer,
3157 S::Buffer: BufferMut,
3158 {
3159 self.send_frame(bindings_ctx, FakeNudMessageMeta::IpFrame { dst_link_address }, body)
3160 }
3161 }
3162
3163 impl<I: Ip> NudConfigContext<I> for FakeInnerCtxImpl<I> {
3164 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3165 <FakeConfigContext as NudConfigContext<I>>::retransmit_timeout(&mut self.state)
3166 }
3167
3168 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3169 <FakeConfigContext as NudConfigContext<I>>::with_nud_user_config(&mut self.state, cb)
3170 }
3171
3172 fn override_lock_time(&mut self) -> Duration {
3173 <FakeConfigContext as NudConfigContext<I>>::override_lock_time(&mut self.state)
3174 }
3175 }
3176
3177 const ONE_SECOND: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
3178
3179 #[track_caller]
3180 fn check_lookup_has<I: Ip>(
3181 core_ctx: &mut FakeCoreCtxImpl<I>,
3182 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3183 lookup_addr: SpecifiedAddr<I::Addr>,
3184 expected_link_addr: FakeLinkAddress,
3185 ) {
3186 let entry = assert_matches!(
3187 core_ctx.nud.state.neighbors.get(&lookup_addr),
3188 Some(entry @ (
3189 NeighborState::Dynamic(
3190 DynamicNeighborState::Reachable (Reachable { link_address, last_confirmed_at: _ })
3191 | DynamicNeighborState::Stale (Stale { link_address })
3192 | DynamicNeighborState::Delay (Delay { link_address })
3193 | DynamicNeighborState::Probe (Probe { link_address, transmit_counter: _ })
3194 | DynamicNeighborState::Unreachable (Unreachable { link_address, mode: _ })
3195 )
3196 | NeighborState::Static(link_address)
3197 )) => {
3198 assert_eq!(link_address, &expected_link_addr);
3199 entry
3200 }
3201 );
3202 match entry {
3203 NeighborState::Dynamic(DynamicNeighborState::Incomplete { .. }) => {
3204 unreachable!("entry must be static, REACHABLE, or STALE")
3205 }
3206 NeighborState::Dynamic(DynamicNeighborState::Reachable { .. }) => {
3207 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3208 bindings_ctx,
3209 [(
3210 lookup_addr,
3211 NudEvent::ReachableTime,
3212 core_ctx.inner.base_reachable_time().get(),
3213 )],
3214 );
3215 }
3216 NeighborState::Dynamic(DynamicNeighborState::Delay { .. }) => {
3217 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3218 bindings_ctx,
3219 [(lookup_addr, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
3220 );
3221 }
3222 NeighborState::Dynamic(DynamicNeighborState::Probe { .. }) => {
3223 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3224 bindings_ctx,
3225 [(
3226 lookup_addr,
3227 NudEvent::RetransmitUnicastProbe,
3228 core_ctx.inner.state.retrans_timer.get(),
3229 )],
3230 );
3231 }
3232 NeighborState::Dynamic(DynamicNeighborState::Unreachable(Unreachable {
3233 link_address: _,
3234 mode,
3235 })) => {
3236 let instant = match mode {
3237 UnreachableMode::WaitingForPacketSend => None,
3238 mode @ UnreachableMode::Backoff { .. } => {
3239 let duration =
3240 mode.next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state);
3241 Some(bindings_ctx.now() + duration.get())
3242 }
3243 };
3244 if let Some(instant) = instant {
3245 core_ctx.nud.state.timer_heap.neighbor.assert_timers([(
3246 lookup_addr,
3247 NudEvent::RetransmitUnicastProbe,
3248 instant,
3249 )]);
3250 }
3251 }
3252 NeighborState::Dynamic(DynamicNeighborState::Stale { .. })
3253 | NeighborState::Static(_) => bindings_ctx.timers.assert_no_timers_installed(),
3254 }
3255 }
3256
3257 trait TestIpExt: NudIcmpIpExt {
3258 const LOOKUP_ADDR1: SpecifiedAddr<Self::Addr>;
3259 const LOOKUP_ADDR2: SpecifiedAddr<Self::Addr>;
3260 const LOOKUP_ADDR3: SpecifiedAddr<Self::Addr>;
3261 }
3262
3263 impl TestIpExt for Ipv4 {
3264 const LOOKUP_ADDR1: SpecifiedAddr<Ipv4Addr> =
3266 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.1")) };
3267 const LOOKUP_ADDR2: SpecifiedAddr<Ipv4Addr> =
3268 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.2")) };
3269 const LOOKUP_ADDR3: SpecifiedAddr<Ipv4Addr> =
3270 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.3")) };
3271 }
3272
3273 impl TestIpExt for Ipv6 {
3274 const LOOKUP_ADDR1: SpecifiedAddr<Ipv6Addr> =
3276 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::1")) };
3277 const LOOKUP_ADDR2: SpecifiedAddr<Ipv6Addr> =
3278 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::2")) };
3279 const LOOKUP_ADDR3: SpecifiedAddr<Ipv6Addr> =
3280 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::3")) };
3281 }
3282
3283 const LINK_ADDR1: FakeLinkAddress = FakeLinkAddress([1]);
3284 const LINK_ADDR2: FakeLinkAddress = FakeLinkAddress([2]);
3285 const LINK_ADDR3: FakeLinkAddress = FakeLinkAddress([3]);
3286
3287 impl<I: Ip, L: LinkDevice> NudTimerId<I, L, FakeWeakDeviceId<FakeLinkDeviceId>> {
3288 fn neighbor() -> Self {
3289 Self {
3290 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3291 timer_type: NudTimerType::Neighbor,
3292 _marker: PhantomData,
3293 }
3294 }
3295
3296 fn garbage_collection() -> Self {
3297 Self {
3298 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3299 timer_type: NudTimerType::GarbageCollection,
3300 _marker: PhantomData,
3301 }
3302 }
3303 }
3304
3305 fn queue_ip_packet_to_unresolved_neighbor<I: TestIpExt>(
3306 core_ctx: &mut FakeCoreCtxImpl<I>,
3307 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3308 neighbor: SpecifiedAddr<I::Addr>,
3309 pending_frames: &mut VecDeque<Buf<Vec<u8>>>,
3310 body: u8,
3311 expect_event: bool,
3312 ) {
3313 let body = [body];
3314 assert_eq!(
3315 NudHandler::send_ip_packet_to_neighbor(
3316 core_ctx,
3317 bindings_ctx,
3318 &FakeLinkDeviceId,
3319 neighbor,
3320 Buf::new(body, ..),
3321 FakeTxMetadata::default(),
3322 ),
3323 Ok(())
3324 );
3325
3326 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
3327
3328 pending_frames.push_back(Buf::new(body.to_vec(), ..));
3329
3330 assert_neighbor_state_with_ip(
3331 core_ctx,
3332 bindings_ctx,
3333 neighbor,
3334 DynamicNeighborState::Incomplete(Incomplete {
3335 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
3336 pending_frames: pending_frames
3337 .iter()
3338 .cloned()
3339 .map(|buf| (buf, FakeTxMetadata::default()))
3340 .collect(),
3341 notifiers: Vec::new(),
3342 _marker: PhantomData,
3343 }),
3344 expect_event.then_some(ExpectedEvent::Added),
3345 );
3346
3347 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3348 bindings_ctx,
3349 [(neighbor, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
3350 );
3351 }
3352
3353 fn init_incomplete_neighbor_with_ip<I: TestIpExt>(
3354 core_ctx: &mut FakeCoreCtxImpl<I>,
3355 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3356 ip_address: SpecifiedAddr<I::Addr>,
3357 take_probe: bool,
3358 ) -> VecDeque<Buf<Vec<u8>>> {
3359 let mut pending_frames = VecDeque::new();
3360 queue_ip_packet_to_unresolved_neighbor(
3361 core_ctx,
3362 bindings_ctx,
3363 ip_address,
3364 &mut pending_frames,
3365 1,
3366 true, );
3368 if take_probe {
3369 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, None);
3370 }
3371 pending_frames
3372 }
3373
3374 fn init_incomplete_neighbor<I: TestIpExt>(
3375 core_ctx: &mut FakeCoreCtxImpl<I>,
3376 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3377 take_probe: bool,
3378 ) -> VecDeque<Buf<Vec<u8>>> {
3379 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, take_probe)
3380 }
3381
3382 fn init_stale_neighbor_with_ip<I: TestIpExt>(
3383 core_ctx: &mut FakeCoreCtxImpl<I>,
3384 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3385 ip_address: SpecifiedAddr<I::Addr>,
3386 link_address: FakeLinkAddress,
3387 ) {
3388 NudHandler::handle_neighbor_update(
3389 core_ctx,
3390 bindings_ctx,
3391 &FakeLinkDeviceId,
3392 ip_address,
3393 DynamicNeighborUpdateSource::Probe { link_address },
3394 );
3395 assert_neighbor_state_with_ip(
3396 core_ctx,
3397 bindings_ctx,
3398 ip_address,
3399 DynamicNeighborState::Stale(Stale { link_address }),
3400 Some(ExpectedEvent::Added),
3401 );
3402 }
3403
3404 fn init_stale_neighbor<I: TestIpExt>(
3405 core_ctx: &mut FakeCoreCtxImpl<I>,
3406 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3407 link_address: FakeLinkAddress,
3408 ) {
3409 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3410 }
3411
3412 fn init_reachable_neighbor_with_ip<I: TestIpExt>(
3413 core_ctx: &mut FakeCoreCtxImpl<I>,
3414 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3415 ip_address: SpecifiedAddr<I::Addr>,
3416 link_address: FakeLinkAddress,
3417 ) {
3418 let queued_frame =
3419 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, true);
3420 NudHandler::handle_neighbor_update(
3421 core_ctx,
3422 bindings_ctx,
3423 &FakeLinkDeviceId,
3424 ip_address,
3425 DynamicNeighborUpdateSource::Confirmation {
3426 link_address: Some(link_address),
3427 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
3428 },
3429 );
3430 assert_neighbor_state_with_ip(
3431 core_ctx,
3432 bindings_ctx,
3433 ip_address,
3434 DynamicNeighborState::Reachable(Reachable {
3435 link_address,
3436 last_confirmed_at: bindings_ctx.now(),
3437 }),
3438 Some(ExpectedEvent::Changed),
3439 );
3440 assert_pending_frame_sent(core_ctx, queued_frame, link_address);
3441 }
3442
3443 fn init_reachable_neighbor<I: TestIpExt>(
3444 core_ctx: &mut FakeCoreCtxImpl<I>,
3445 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3446 link_address: FakeLinkAddress,
3447 ) {
3448 init_reachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3449 }
3450
3451 fn init_delay_neighbor_with_ip<I: TestIpExt>(
3452 core_ctx: &mut FakeCoreCtxImpl<I>,
3453 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3454 ip_address: SpecifiedAddr<I::Addr>,
3455 link_address: FakeLinkAddress,
3456 ) {
3457 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3458 assert_eq!(
3459 NudHandler::send_ip_packet_to_neighbor(
3460 core_ctx,
3461 bindings_ctx,
3462 &FakeLinkDeviceId,
3463 ip_address,
3464 Buf::new([1], ..),
3465 FakeTxMetadata::default(),
3466 ),
3467 Ok(())
3468 );
3469 assert_neighbor_state_with_ip(
3470 core_ctx,
3471 bindings_ctx,
3472 ip_address,
3473 DynamicNeighborState::Delay(Delay { link_address }),
3474 Some(ExpectedEvent::Changed),
3475 );
3476 assert_eq!(
3477 core_ctx.inner.take_frames(),
3478 vec![(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![1])],
3479 );
3480 }
3481
3482 fn init_delay_neighbor<I: TestIpExt>(
3483 core_ctx: &mut FakeCoreCtxImpl<I>,
3484 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3485 link_address: FakeLinkAddress,
3486 ) {
3487 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3488 }
3489
3490 fn init_probe_neighbor_with_ip<I: TestIpExt>(
3491 core_ctx: &mut FakeCoreCtxImpl<I>,
3492 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3493 ip_address: SpecifiedAddr<I::Addr>,
3494 link_address: FakeLinkAddress,
3495 take_probe: bool,
3496 ) {
3497 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3498 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3499 core_ctx.nud.state.timer_heap.neighbor.assert_top(&ip_address, &NudEvent::DelayFirstProbe);
3500 assert_eq!(
3501 bindings_ctx.trigger_timers_for(DELAY_FIRST_PROBE_TIME.into(), core_ctx),
3502 [NudTimerId::neighbor()]
3503 );
3504 assert_neighbor_state_with_ip(
3505 core_ctx,
3506 bindings_ctx,
3507 ip_address,
3508 DynamicNeighborState::Probe(Probe {
3509 link_address,
3510 transmit_counter: NonZeroU16::new(max_unicast_solicit - 1),
3511 }),
3512 Some(ExpectedEvent::Changed),
3513 );
3514 if take_probe {
3515 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3516 }
3517 }
3518
3519 fn init_probe_neighbor<I: TestIpExt>(
3520 core_ctx: &mut FakeCoreCtxImpl<I>,
3521 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3522 link_address: FakeLinkAddress,
3523 take_probe: bool,
3524 ) {
3525 init_probe_neighbor_with_ip(
3526 core_ctx,
3527 bindings_ctx,
3528 I::LOOKUP_ADDR1,
3529 link_address,
3530 take_probe,
3531 );
3532 }
3533
3534 fn init_unreachable_neighbor_with_ip<I: TestIpExt>(
3535 core_ctx: &mut FakeCoreCtxImpl<I>,
3536 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3537 ip_address: SpecifiedAddr<I::Addr>,
3538 link_address: FakeLinkAddress,
3539 ) {
3540 init_probe_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address, false);
3541 let retransmit_timeout = core_ctx.inner.retransmit_timeout();
3542 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3543 for _ in 0..max_unicast_solicit {
3544 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3545 assert_eq!(
3546 bindings_ctx.trigger_timers_for(retransmit_timeout.into(), core_ctx),
3547 [NudTimerId::neighbor()]
3548 );
3549 }
3550 assert_neighbor_state_with_ip(
3551 core_ctx,
3552 bindings_ctx,
3553 ip_address,
3554 DynamicNeighborState::Unreachable(Unreachable {
3555 link_address,
3556 mode: UnreachableMode::WaitingForPacketSend,
3557 }),
3558 Some(ExpectedEvent::Changed),
3559 );
3560 }
3561
3562 fn init_unreachable_neighbor<I: TestIpExt>(
3563 core_ctx: &mut FakeCoreCtxImpl<I>,
3564 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3565 link_address: FakeLinkAddress,
3566 ) {
3567 init_unreachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3568 }
3569
3570 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
3571 enum InitialState {
3572 Incomplete,
3573 Stale,
3574 Reachable,
3575 Delay,
3576 Probe,
3577 Unreachable,
3578 }
3579
3580 fn init_neighbor_in_state<I: TestIpExt>(
3581 core_ctx: &mut FakeCoreCtxImpl<I>,
3582 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3583 state: InitialState,
3584 ) -> DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>> {
3585 match state {
3586 InitialState::Incomplete => {
3587 let _: VecDeque<Buf<Vec<u8>>> =
3588 init_incomplete_neighbor(core_ctx, bindings_ctx, true);
3589 }
3590 InitialState::Reachable => {
3591 init_reachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3592 }
3593 InitialState::Stale => {
3594 init_stale_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3595 }
3596 InitialState::Delay => {
3597 init_delay_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3598 }
3599 InitialState::Probe => {
3600 init_probe_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, true);
3601 }
3602 InitialState::Unreachable => {
3603 init_unreachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3604 }
3605 }
3606 assert_matches!(core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
3607 Some(NeighborState::Dynamic(state)) => state.clone()
3608 )
3609 }
3610
3611 #[track_caller]
3612 fn init_static_neighbor_with_ip<I: TestIpExt>(
3613 core_ctx: &mut FakeCoreCtxImpl<I>,
3614 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3615 ip_address: SpecifiedAddr<I::Addr>,
3616 link_address: FakeLinkAddress,
3617 expected_event: ExpectedEvent,
3618 ) {
3619 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3620 NeighborApi::new(&mut ctx)
3621 .insert_static_entry(&FakeLinkDeviceId, *ip_address, link_address)
3622 .unwrap();
3623 assert_eq!(
3624 ctx.bindings_ctx.take_events(),
3625 [Event {
3626 device: FakeLinkDeviceId,
3627 addr: ip_address,
3628 kind: match expected_event {
3629 ExpectedEvent::Added => EventKind::Added(EventState::Static(link_address)),
3630 ExpectedEvent::Changed => EventKind::Changed(EventState::Static(link_address)),
3631 },
3632 at: ctx.bindings_ctx.now(),
3633 }],
3634 );
3635 }
3636
3637 #[track_caller]
3638 fn init_static_neighbor<I: TestIpExt>(
3639 core_ctx: &mut FakeCoreCtxImpl<I>,
3640 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3641 link_address: FakeLinkAddress,
3642 expected_event: ExpectedEvent,
3643 ) {
3644 init_static_neighbor_with_ip(
3645 core_ctx,
3646 bindings_ctx,
3647 I::LOOKUP_ADDR1,
3648 link_address,
3649 expected_event,
3650 );
3651 }
3652
3653 #[track_caller]
3654 fn delete_neighbor<I: TestIpExt>(
3655 core_ctx: &mut FakeCoreCtxImpl<I>,
3656 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3657 ) {
3658 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3659 NeighborApi::new(&mut ctx)
3660 .remove_entry(&FakeLinkDeviceId, *I::LOOKUP_ADDR1)
3661 .expect("neighbor entry should exist");
3662 assert_eq!(
3663 ctx.bindings_ctx.take_events(),
3664 [Event::removed(&FakeLinkDeviceId, I::LOOKUP_ADDR1, ctx.bindings_ctx.now())],
3665 );
3666 }
3667
3668 #[track_caller]
3669 fn assert_neighbor_state<I: TestIpExt>(
3670 core_ctx: &FakeCoreCtxImpl<I>,
3671 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3672 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3673 event_kind: Option<ExpectedEvent>,
3674 ) {
3675 assert_neighbor_state_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, state, event_kind);
3676 }
3677
3678 #[derive(Clone, Copy, Debug)]
3679 enum ExpectedEvent {
3680 Added,
3681 Changed,
3682 }
3683
3684 #[track_caller]
3685 fn assert_neighbor_state_with_ip<I: TestIpExt>(
3686 core_ctx: &FakeCoreCtxImpl<I>,
3687 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3688 neighbor: SpecifiedAddr<I::Addr>,
3689 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3690 expected_event: Option<ExpectedEvent>,
3691 ) {
3692 if let Some(expected_event) = expected_event {
3693 let event_state = EventState::Dynamic(state.to_event_dynamic_state());
3694 assert_eq!(
3695 bindings_ctx.take_events(),
3696 [Event {
3697 device: FakeLinkDeviceId,
3698 addr: neighbor,
3699 kind: match expected_event {
3700 ExpectedEvent::Added => EventKind::Added(event_state),
3701 ExpectedEvent::Changed => EventKind::Changed(event_state),
3702 },
3703 at: bindings_ctx.now(),
3704 }],
3705 );
3706 }
3707
3708 assert_eq!(
3709 core_ctx.nud.state.neighbors.get(&neighbor),
3710 Some(&NeighborState::Dynamic(state))
3711 );
3712 }
3713
3714 #[track_caller]
3715 fn assert_pending_frame_sent<I: TestIpExt>(
3716 core_ctx: &mut FakeCoreCtxImpl<I>,
3717 pending_frames: VecDeque<Buf<Vec<u8>>>,
3718 link_address: FakeLinkAddress,
3719 ) {
3720 assert_eq!(
3721 core_ctx.inner.take_frames(),
3722 pending_frames
3723 .into_iter()
3724 .map(|f| (
3725 FakeNudMessageMeta::IpFrame { dst_link_address: link_address },
3726 f.as_ref().to_vec(),
3727 ))
3728 .collect::<Vec<_>>()
3729 );
3730 }
3731
3732 #[track_caller]
3733 fn assert_neighbor_probe_sent_for_ip<I: TestIpExt>(
3734 core_ctx: &mut FakeCoreCtxImpl<I>,
3735 ip_address: SpecifiedAddr<I::Addr>,
3736 link_address: Option<FakeLinkAddress>,
3737 ) {
3738 assert_eq!(
3739 core_ctx.inner.take_frames(),
3740 [(
3741 FakeNudMessageMeta::NeighborSolicitation {
3742 lookup_addr: ip_address,
3743 remote_link_addr: link_address,
3744 },
3745 Vec::new()
3746 )]
3747 );
3748 }
3749
3750 #[track_caller]
3751 fn assert_neighbor_probe_sent<I: TestIpExt>(
3752 core_ctx: &mut FakeCoreCtxImpl<I>,
3753 link_address: Option<FakeLinkAddress>,
3754 ) {
3755 assert_neighbor_probe_sent_for_ip(core_ctx, I::LOOKUP_ADDR1, link_address);
3756 }
3757
3758 #[track_caller]
3759 fn assert_neighbor_removed_with_ip<I: TestIpExt>(
3760 core_ctx: &mut FakeCoreCtxImpl<I>,
3761 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3762 neighbor: SpecifiedAddr<I::Addr>,
3763 ) {
3764 super::testutil::assert_neighbor_unknown(core_ctx, FakeLinkDeviceId, neighbor);
3765 assert_eq!(
3766 bindings_ctx.take_events(),
3767 [Event::removed(&FakeLinkDeviceId, neighbor, bindings_ctx.now())],
3768 );
3769 }
3770
3771 #[ip_test(I)]
3772 fn serialization_failure_doesnt_schedule_timer<I: TestIpExt>() {
3773 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3774
3775 let packet = Buf::new([0; 2], ..).with_size_limit(1);
3778
3779 let err = assert_matches!(
3780 NudHandler::send_ip_packet_to_neighbor(
3781 &mut core_ctx,
3782 &mut bindings_ctx,
3783 &FakeLinkDeviceId,
3784 I::LOOKUP_ADDR1,
3785 packet,
3786 FakeTxMetadata::default(),
3787 ),
3788 Err(ErrorAndSerializer { error, serializer: _ }) => error
3789 );
3790 assert_eq!(err, SendFrameErrorReason::SizeConstraintsViolation);
3791
3792 super::testutil::assert_neighbor_unknown(&mut core_ctx, FakeLinkDeviceId, I::LOOKUP_ADDR1);
3795 assert_eq!(core_ctx.inner.take_frames(), []);
3796 bindings_ctx.timers.assert_no_timers_installed();
3797 }
3798
3799 #[ip_test(I)]
3800 fn incomplete_to_stale_on_probe<I: TestIpExt>() {
3801 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3802
3803 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3805
3806 NudHandler::handle_neighbor_update(
3808 &mut core_ctx,
3809 &mut bindings_ctx,
3810 &FakeLinkDeviceId,
3811 I::LOOKUP_ADDR1,
3812 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
3813 );
3814
3815 assert_neighbor_state(
3817 &core_ctx,
3818 &mut bindings_ctx,
3819 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3820 Some(ExpectedEvent::Changed),
3821 );
3822 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3823 }
3824
3825 #[ip_test(I)]
3826 #[test_case(true, true; "solicited override")]
3827 #[test_case(true, false; "solicited non-override")]
3828 #[test_case(false, true; "unsolicited override")]
3829 #[test_case(false, false; "unsolicited non-override")]
3830 fn incomplete_on_confirmation<I: TestIpExt>(solicited_flag: bool, override_flag: bool) {
3831 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3832
3833 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3835
3836 NudHandler::handle_neighbor_update(
3838 &mut core_ctx,
3839 &mut bindings_ctx,
3840 &FakeLinkDeviceId,
3841 I::LOOKUP_ADDR1,
3842 DynamicNeighborUpdateSource::Confirmation {
3843 link_address: Some(LINK_ADDR1),
3844 flags: ConfirmationFlags { solicited_flag, override_flag },
3845 },
3846 );
3847
3848 let expected_state = if solicited_flag {
3849 DynamicNeighborState::Reachable(Reachable {
3850 link_address: LINK_ADDR1,
3851 last_confirmed_at: bindings_ctx.now(),
3852 })
3853 } else {
3854 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 })
3855 };
3856 assert_neighbor_state(
3857 &core_ctx,
3858 &mut bindings_ctx,
3859 expected_state,
3860 Some(ExpectedEvent::Changed),
3861 );
3862 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3863 }
3864
3865 #[ip_test(I)]
3866 fn reachable_to_stale_on_timeout<I: TestIpExt>() {
3867 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3868
3869 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
3871
3872 assert_eq!(
3874 bindings_ctx
3875 .trigger_timers_for(core_ctx.inner.base_reachable_time().into(), &mut core_ctx,),
3876 [NudTimerId::neighbor()]
3877 );
3878 assert_neighbor_state(
3879 &core_ctx,
3880 &mut bindings_ctx,
3881 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3882 Some(ExpectedEvent::Changed),
3883 );
3884 }
3885
3886 #[ip_test(I)]
3887 #[test_case(InitialState::Reachable, true; "reachable with different address")]
3888 #[test_case(InitialState::Reachable, false; "reachable with same address")]
3889 #[test_case(InitialState::Stale, true; "stale with different address")]
3890 #[test_case(InitialState::Stale, false; "stale with same address")]
3891 #[test_case(InitialState::Delay, true; "delay with different address")]
3892 #[test_case(InitialState::Delay, false; "delay with same address")]
3893 #[test_case(InitialState::Probe, true; "probe with different address")]
3894 #[test_case(InitialState::Probe, false; "probe with same address")]
3895 #[test_case(InitialState::Unreachable, true; "unreachable with different address")]
3896 #[test_case(InitialState::Unreachable, false; "unreachable with same address")]
3897 fn transition_to_stale_on_probe_with_different_address<I: TestIpExt>(
3898 initial_state: InitialState,
3899 update_link_address: bool,
3900 ) {
3901 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3902
3903 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3905
3906 NudHandler::handle_neighbor_update(
3908 &mut core_ctx,
3909 &mut bindings_ctx,
3910 &FakeLinkDeviceId,
3911 I::LOOKUP_ADDR1,
3912 DynamicNeighborUpdateSource::Probe {
3913 link_address: if update_link_address { LINK_ADDR2 } else { LINK_ADDR1 },
3914 },
3915 );
3916
3917 let expected_state = if update_link_address {
3923 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 })
3924 } else {
3925 initial_state
3926 };
3927 assert_neighbor_state(
3928 &core_ctx,
3929 &mut bindings_ctx,
3930 expected_state,
3931 update_link_address.then_some(ExpectedEvent::Changed),
3932 );
3933 }
3934
3935 #[ip_test(I)]
3936 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
3937 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
3938 #[test_case(InitialState::Stale, true; "stale with override flag set")]
3939 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
3940 #[test_case(InitialState::Delay, true; "delay with override flag set")]
3941 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
3942 #[test_case(InitialState::Probe, true; "probe with override flag set")]
3943 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
3944 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
3945 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
3946 fn transition_to_reachable_on_solicited_confirmation_same_address<I: TestIpExt>(
3947 initial_state: InitialState,
3948 override_flag: bool,
3949 ) {
3950 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3951
3952 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3954
3955 NudHandler::handle_neighbor_update(
3957 &mut core_ctx,
3958 &mut bindings_ctx,
3959 &FakeLinkDeviceId,
3960 I::LOOKUP_ADDR1,
3961 DynamicNeighborUpdateSource::Confirmation {
3962 link_address: Some(LINK_ADDR1),
3963 flags: ConfirmationFlags { solicited_flag: true, override_flag },
3964 },
3965 );
3966
3967 let now = bindings_ctx.now();
3969 assert_neighbor_state(
3970 &core_ctx,
3971 &mut bindings_ctx,
3972 DynamicNeighborState::Reachable(Reachable {
3973 link_address: LINK_ADDR1,
3974 last_confirmed_at: now,
3975 }),
3976 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
3977 );
3978 }
3979
3980 #[ip_test(I)]
3981 #[test_case(InitialState::Reachable; "reachable")]
3982 #[test_case(InitialState::Stale; "stale")]
3983 #[test_case(InitialState::Delay; "delay")]
3984 #[test_case(InitialState::Probe; "probe")]
3985 #[test_case(InitialState::Unreachable; "unreachable")]
3986 fn transition_to_stale_on_unsolicited_override_confirmation_with_different_address<
3987 I: TestIpExt,
3988 >(
3989 initial_state: InitialState,
3990 ) {
3991 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3992
3993 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3995
3996 NudHandler::handle_neighbor_update(
3998 &mut core_ctx,
3999 &mut bindings_ctx,
4000 &FakeLinkDeviceId,
4001 I::LOOKUP_ADDR1,
4002 DynamicNeighborUpdateSource::Confirmation {
4003 link_address: Some(LINK_ADDR2),
4004 flags: ConfirmationFlags { solicited_flag: false, override_flag: true },
4005 },
4006 );
4007
4008 assert_neighbor_state(
4010 &core_ctx,
4011 &mut bindings_ctx,
4012 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4013 Some(ExpectedEvent::Changed),
4014 );
4015 }
4016
4017 #[ip_test(I)]
4018 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
4019 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
4020 #[test_case(InitialState::Stale, true; "stale with override flag set")]
4021 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
4022 #[test_case(InitialState::Delay, true; "delay with override flag set")]
4023 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
4024 #[test_case(InitialState::Probe, true; "probe with override flag set")]
4025 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
4026 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
4027 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
4028 fn noop_on_unsolicited_confirmation_with_same_address<I: TestIpExt>(
4029 initial_state: InitialState,
4030 override_flag: bool,
4031 ) {
4032 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4033
4034 let expected_state =
4036 init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4037
4038 NudHandler::handle_neighbor_update(
4040 &mut core_ctx,
4041 &mut bindings_ctx,
4042 &FakeLinkDeviceId,
4043 I::LOOKUP_ADDR1,
4044 DynamicNeighborUpdateSource::Confirmation {
4045 link_address: Some(LINK_ADDR1),
4046 flags: ConfirmationFlags { solicited_flag: false, override_flag },
4047 },
4048 );
4049
4050 assert_neighbor_state(&core_ctx, &mut bindings_ctx, expected_state, None);
4052 }
4053
4054 #[ip_test(I)]
4055 #[test_case(InitialState::Reachable; "reachable")]
4056 #[test_case(InitialState::Stale; "stale")]
4057 #[test_case(InitialState::Delay; "delay")]
4058 #[test_case(InitialState::Probe; "probe")]
4059 #[test_case(InitialState::Unreachable; "unreachable")]
4060 fn transition_to_reachable_on_solicited_override_confirmation_with_different_address<
4061 I: TestIpExt,
4062 >(
4063 initial_state: InitialState,
4064 ) {
4065 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4066
4067 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4069
4070 NudHandler::handle_neighbor_update(
4072 &mut core_ctx,
4073 &mut bindings_ctx,
4074 &FakeLinkDeviceId,
4075 I::LOOKUP_ADDR1,
4076 DynamicNeighborUpdateSource::Confirmation {
4077 link_address: Some(LINK_ADDR2),
4078 flags: ConfirmationFlags { solicited_flag: true, override_flag: true },
4079 },
4080 );
4081
4082 let now = bindings_ctx.now();
4084 assert_neighbor_state(
4085 &core_ctx,
4086 &mut bindings_ctx,
4087 DynamicNeighborState::Reachable(Reachable {
4088 link_address: LINK_ADDR2,
4089 last_confirmed_at: now,
4090 }),
4091 Some(ExpectedEvent::Changed),
4092 );
4093 }
4094
4095 #[ip_test(I)]
4096 fn reachable_to_reachable_on_probe_with_same_address<I: TestIpExt>() {
4097 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4098
4099 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4101
4102 NudHandler::handle_neighbor_update(
4104 &mut core_ctx,
4105 &mut bindings_ctx,
4106 &FakeLinkDeviceId,
4107 I::LOOKUP_ADDR1,
4108 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
4109 );
4110
4111 let now = bindings_ctx.now();
4113 assert_neighbor_state(
4114 &core_ctx,
4115 &mut bindings_ctx,
4116 DynamicNeighborState::Reachable(Reachable {
4117 link_address: LINK_ADDR1,
4118 last_confirmed_at: now,
4119 }),
4120 None,
4121 );
4122 }
4123
4124 #[ip_test(I)]
4125 #[test_case(true; "solicited")]
4126 #[test_case(false; "unsolicited")]
4127 fn reachable_to_stale_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4128 solicited_flag: bool,
4129 ) {
4130 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4131
4132 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4134
4135 NudHandler::handle_neighbor_update(
4137 &mut core_ctx,
4138 &mut bindings_ctx,
4139 &FakeLinkDeviceId,
4140 I::LOOKUP_ADDR1,
4141 DynamicNeighborUpdateSource::Confirmation {
4142 link_address: Some(LINK_ADDR2),
4143 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4144 },
4145 );
4146
4147 assert_neighbor_state(
4150 &core_ctx,
4151 &mut bindings_ctx,
4152 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4153 Some(ExpectedEvent::Changed),
4154 );
4155 }
4156
4157 #[ip_test(I)]
4158 #[test_case(InitialState::Stale, true; "stale solicited")]
4159 #[test_case(InitialState::Stale, false; "stale unsolicited")]
4160 #[test_case(InitialState::Delay, true; "delay solicited")]
4161 #[test_case(InitialState::Delay, false; "delay unsolicited")]
4162 #[test_case(InitialState::Probe, true; "probe solicited")]
4163 #[test_case(InitialState::Probe, false; "probe unsolicited")]
4164 #[test_case(InitialState::Unreachable, true; "unreachable solicited")]
4165 #[test_case(InitialState::Unreachable, false; "unreachable unsolicited")]
4166 fn noop_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4167 initial_state: InitialState,
4168 solicited_flag: bool,
4169 ) {
4170 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4171
4172 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4174
4175 NudHandler::handle_neighbor_update(
4177 &mut core_ctx,
4178 &mut bindings_ctx,
4179 &FakeLinkDeviceId,
4180 I::LOOKUP_ADDR1,
4181 DynamicNeighborUpdateSource::Confirmation {
4182 link_address: Some(LINK_ADDR2),
4183 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4184 },
4185 );
4186
4187 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial_state, None);
4190 }
4191
4192 #[ip_test(I)]
4193 fn stale_to_delay_on_packet_sent<I: TestIpExt>() {
4194 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4195
4196 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4198
4199 let body = 1;
4201 assert_eq!(
4202 NudHandler::send_ip_packet_to_neighbor(
4203 &mut core_ctx,
4204 &mut bindings_ctx,
4205 &FakeLinkDeviceId,
4206 I::LOOKUP_ADDR1,
4207 Buf::new([body], ..),
4208 FakeTxMetadata::default(),
4209 ),
4210 Ok(())
4211 );
4212
4213 assert_neighbor_state(
4215 &core_ctx,
4216 &mut bindings_ctx,
4217 DynamicNeighborState::Delay(Delay { link_address: LINK_ADDR1 }),
4218 Some(ExpectedEvent::Changed),
4219 );
4220 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4221 &mut bindings_ctx,
4222 [(I::LOOKUP_ADDR1, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
4223 );
4224 assert_pending_frame_sent(
4225 &mut core_ctx,
4226 VecDeque::from([Buf::new(vec![body], ..)]),
4227 LINK_ADDR1,
4228 );
4229 }
4230
4231 #[ip_test(I)]
4232 #[test_case(InitialState::Delay,
4233 NudEvent::DelayFirstProbe;
4234 "delay to probe")]
4235 #[test_case(InitialState::Probe,
4236 NudEvent::RetransmitUnicastProbe;
4237 "probe retransmit unicast probe")]
4238 fn delay_or_probe_to_probe_on_timeout<I: TestIpExt>(
4239 initial_state: InitialState,
4240 expected_initial_event: NudEvent,
4241 ) {
4242 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4243
4244 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4246
4247 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4248
4249 let (time, transmit_counter) = match initial_state {
4255 InitialState::Delay => {
4256 (DELAY_FIRST_PROBE_TIME, NonZeroU16::new(max_unicast_solicit - 1))
4257 }
4258 InitialState::Probe => {
4259 (core_ctx.inner.state.retrans_timer, NonZeroU16::new(max_unicast_solicit - 2))
4260 }
4261 other => unreachable!("test only covers DELAY and PROBE, got {:?}", other),
4262 };
4263 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4264 &mut bindings_ctx,
4265 [(I::LOOKUP_ADDR1, expected_initial_event, time.get())],
4266 );
4267 assert_eq!(
4268 bindings_ctx.trigger_timers_for(time.into(), &mut core_ctx,),
4269 [NudTimerId::neighbor()]
4270 );
4271 assert_neighbor_state(
4272 &core_ctx,
4273 &mut bindings_ctx,
4274 DynamicNeighborState::Probe(Probe { link_address: LINK_ADDR1, transmit_counter }),
4275 (initial_state != InitialState::Probe).then_some(ExpectedEvent::Changed),
4276 );
4277 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4278 &mut bindings_ctx,
4279 [(
4280 I::LOOKUP_ADDR1,
4281 NudEvent::RetransmitUnicastProbe,
4282 core_ctx.inner.state.retrans_timer.get(),
4283 )],
4284 );
4285 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4286 }
4287
4288 #[ip_test(I)]
4289 fn unreachable_probes_with_exponential_backoff_while_packets_sent<I: TestIpExt>() {
4290 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4291
4292 init_unreachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4293
4294 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4295 let timer_id = NudTimerId::neighbor();
4296
4297 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), []);
4299 assert_eq!(core_ctx.inner.take_frames(), []);
4300
4301 const BODY: u8 = 0x33;
4303 assert_eq!(
4304 NudHandler::send_ip_packet_to_neighbor(
4305 &mut core_ctx,
4306 &mut bindings_ctx,
4307 &FakeLinkDeviceId,
4308 I::LOOKUP_ADDR1,
4309 Buf::new([BODY], ..),
4310 FakeTxMetadata::default(),
4311 ),
4312 Ok(())
4313 );
4314 assert_eq!(
4315 core_ctx.inner.take_frames(),
4316 [
4317 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4318 (
4319 FakeNudMessageMeta::NeighborSolicitation {
4320 lookup_addr: I::LOOKUP_ADDR1,
4321 remote_link_addr: None,
4322 },
4323 Vec::new()
4324 )
4325 ]
4326 );
4327
4328 let next_backoff_timer = |core_ctx: &mut FakeCoreCtxImpl<I>, probes_sent| {
4329 UnreachableMode::Backoff {
4330 probes_sent: NonZeroU32::new(probes_sent).unwrap(),
4331 packet_sent: false,
4332 }
4333 .next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state)
4334 .get()
4335 };
4336
4337 const ITERATIONS: u8 = 2;
4338 for i in 1..ITERATIONS {
4339 let probes_sent = u32::from(i);
4340
4341 assert_eq!(
4344 NudHandler::send_ip_packet_to_neighbor(
4345 &mut core_ctx,
4346 &mut bindings_ctx,
4347 &FakeLinkDeviceId,
4348 I::LOOKUP_ADDR1,
4349 Buf::new([BODY + i], ..),
4350 FakeTxMetadata::default(),
4351 ),
4352 Ok(())
4353 );
4354 assert_eq!(
4355 core_ctx.inner.take_frames(),
4356 [(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY + i])]
4357 );
4358
4359 assert_eq!(
4364 bindings_ctx.trigger_timers_for(
4365 next_backoff_timer(&mut core_ctx, probes_sent),
4366 &mut core_ctx,
4367 ),
4368 [timer_id]
4369 );
4370 assert_neighbor_probe_sent(&mut core_ctx, None);
4371 bindings_ctx.timers.assert_timers_installed([(
4372 timer_id,
4373 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, probes_sent + 1),
4374 )]);
4375 }
4376
4377 let current_timer = next_backoff_timer(&mut core_ctx, u32::from(ITERATIONS));
4380 assert_eq!(bindings_ctx.trigger_timers_for(current_timer, &mut core_ctx,), [timer_id]);
4381 assert_eq!(core_ctx.inner.take_frames(), []);
4382 bindings_ctx.timers.assert_no_timers_installed();
4383
4384 assert_eq!(
4387 NudHandler::send_ip_packet_to_neighbor(
4388 &mut core_ctx,
4389 &mut bindings_ctx,
4390 &FakeLinkDeviceId,
4391 I::LOOKUP_ADDR1,
4392 Buf::new([BODY], ..),
4393 FakeTxMetadata::default(),
4394 ),
4395 Ok(())
4396 );
4397 assert_eq!(
4398 core_ctx.inner.take_frames(),
4399 [
4400 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4401 (
4402 FakeNudMessageMeta::NeighborSolicitation {
4403 lookup_addr: I::LOOKUP_ADDR1,
4404 remote_link_addr: None,
4405 },
4406 Vec::new()
4407 )
4408 ]
4409 );
4410 bindings_ctx.timers.assert_timers_installed([(
4411 timer_id,
4412 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, 1),
4413 )]);
4414 }
4415
4416 #[ip_test(I)]
4417 #[test_case(true; "solicited confirmation")]
4418 #[test_case(false; "unsolicited confirmation")]
4419 fn confirmation_should_not_create_entry<I: TestIpExt>(solicited_flag: bool) {
4420 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4421
4422 let link_address = Some(FakeLinkAddress([1]));
4423 NudHandler::handle_neighbor_update(
4424 &mut core_ctx,
4425 &mut bindings_ctx,
4426 &FakeLinkDeviceId,
4427 I::LOOKUP_ADDR1,
4428 DynamicNeighborUpdateSource::Confirmation {
4429 link_address,
4430 flags: ConfirmationFlags { solicited_flag, override_flag: false },
4431 },
4432 );
4433 assert_eq!(core_ctx.nud.state.neighbors, HashMap::new());
4434 }
4435
4436 #[ip_test(I)]
4437 #[test_case(true; "set_with_dynamic")]
4438 #[test_case(false; "set_with_static")]
4439 fn pending_frames<I: TestIpExt>(dynamic: bool) {
4440 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4441 assert_eq!(core_ctx.inner.take_frames(), []);
4442
4443 const MAX_PENDING_FRAMES_U8: u8 = MAX_PENDING_FRAMES as u8;
4447 let expected_pending_frames = (0..MAX_PENDING_FRAMES_U8)
4448 .map(|i| (Buf::new(vec![i], ..), FakeTxMetadata::default()))
4449 .collect::<VecDeque<_>>();
4450
4451 for (body, meta) in expected_pending_frames.iter() {
4452 assert_eq!(
4453 NudHandler::send_ip_packet_to_neighbor(
4454 &mut core_ctx,
4455 &mut bindings_ctx,
4456 &FakeLinkDeviceId,
4457 I::LOOKUP_ADDR1,
4458 body.clone(),
4459 meta.clone(),
4460 ),
4461 Ok(())
4462 );
4463 }
4464 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4465 assert_neighbor_probe_sent(&mut core_ctx, None);
4467 assert_neighbor_state(
4468 &core_ctx,
4469 &mut bindings_ctx,
4470 DynamicNeighborState::Incomplete(Incomplete {
4471 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4472 pending_frames: expected_pending_frames.clone(),
4473 notifiers: Vec::new(),
4474 _marker: PhantomData,
4475 }),
4476 Some(ExpectedEvent::Added),
4477 );
4478
4479 assert_eq!(
4481 NudHandler::send_ip_packet_to_neighbor(
4482 &mut core_ctx,
4483 &mut bindings_ctx,
4484 &FakeLinkDeviceId,
4485 I::LOOKUP_ADDR1,
4486 Buf::new([123], ..),
4487 FakeTxMetadata::default(),
4488 ),
4489 Ok(())
4490 );
4491 assert_eq!(core_ctx.inner.take_frames(), []);
4492 assert_neighbor_state(
4493 &core_ctx,
4494 &mut bindings_ctx,
4495 DynamicNeighborState::Incomplete(Incomplete {
4496 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4497 pending_frames: expected_pending_frames.clone(),
4498 notifiers: Vec::new(),
4499 _marker: PhantomData,
4500 }),
4501 None,
4502 );
4503
4504 if dynamic {
4506 NudHandler::handle_neighbor_update(
4507 &mut core_ctx,
4508 &mut bindings_ctx,
4509 &FakeLinkDeviceId,
4510 I::LOOKUP_ADDR1,
4511 DynamicNeighborUpdateSource::Confirmation {
4512 link_address: Some(LINK_ADDR1),
4513 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4514 },
4515 );
4516 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4517 &mut bindings_ctx,
4518 [(
4519 I::LOOKUP_ADDR1,
4520 NudEvent::ReachableTime,
4521 core_ctx.inner.base_reachable_time().get(),
4522 )],
4523 );
4524 let last_confirmed_at = bindings_ctx.now();
4525 assert_neighbor_state(
4526 &core_ctx,
4527 &mut bindings_ctx,
4528 DynamicNeighborState::Reachable(Reachable {
4529 link_address: LINK_ADDR1,
4530 last_confirmed_at,
4531 }),
4532 Some(ExpectedEvent::Changed),
4533 );
4534 } else {
4535 init_static_neighbor(
4536 &mut core_ctx,
4537 &mut bindings_ctx,
4538 LINK_ADDR1,
4539 ExpectedEvent::Changed,
4540 );
4541 bindings_ctx.timers.assert_no_timers_installed();
4542 }
4543 assert_eq!(
4544 core_ctx.inner.take_frames(),
4545 expected_pending_frames
4546 .into_iter()
4547 .map(|(p, FakeTxMetadata)| (
4548 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4549 p.as_ref().to_vec()
4550 ))
4551 .collect::<Vec<_>>()
4552 );
4553 }
4554
4555 #[ip_test(I)]
4556 fn static_neighbor<I: TestIpExt>() {
4557 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4558
4559 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4560 bindings_ctx.timers.assert_no_timers_installed();
4561 assert_eq!(core_ctx.inner.take_frames(), []);
4562 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4563
4564 NudHandler::handle_neighbor_update(
4566 &mut core_ctx,
4567 &mut bindings_ctx,
4568 &FakeLinkDeviceId,
4569 I::LOOKUP_ADDR1,
4570 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4571 );
4572 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4573
4574 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4575
4576 let neighbors = &core_ctx.nud.state.neighbors;
4577 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4578 }
4579
4580 #[ip_test(I)]
4581 fn dynamic_neighbor<I: TestIpExt>() {
4582 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4583
4584 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4585 bindings_ctx.timers.assert_no_timers_installed();
4586 assert_eq!(core_ctx.inner.take_frames(), []);
4587 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4588
4589 NudHandler::handle_neighbor_update(
4591 &mut core_ctx,
4592 &mut bindings_ctx,
4593 &FakeLinkDeviceId,
4594 I::LOOKUP_ADDR1,
4595 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4596 );
4597 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR2);
4598 assert_eq!(core_ctx.inner.take_frames(), []);
4599 assert_neighbor_state(
4600 &core_ctx,
4601 &mut bindings_ctx,
4602 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4603 Some(ExpectedEvent::Changed),
4604 );
4605
4606 init_static_neighbor_with_ip(
4608 &mut core_ctx,
4609 &mut bindings_ctx,
4610 I::LOOKUP_ADDR1,
4611 LINK_ADDR3,
4612 ExpectedEvent::Changed,
4613 );
4614 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR3);
4615 assert_eq!(core_ctx.inner.take_frames(), []);
4616 }
4617
4618 #[ip_test(I)]
4619 fn send_solicitation_on_lookup<I: TestIpExt>() {
4620 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4621 bindings_ctx.timers.assert_no_timers_installed();
4622 assert_eq!(core_ctx.inner.take_frames(), []);
4623
4624 let mut pending_frames = VecDeque::new();
4625
4626 queue_ip_packet_to_unresolved_neighbor(
4627 &mut core_ctx,
4628 &mut bindings_ctx,
4629 I::LOOKUP_ADDR1,
4630 &mut pending_frames,
4631 1,
4632 true, );
4634 assert_neighbor_probe_sent(&mut core_ctx, None);
4635
4636 queue_ip_packet_to_unresolved_neighbor(
4637 &mut core_ctx,
4638 &mut bindings_ctx,
4639 I::LOOKUP_ADDR1,
4640 &mut pending_frames,
4641 2,
4642 false, );
4644 assert_eq!(core_ctx.inner.take_frames(), []);
4645
4646 NudHandler::handle_neighbor_update(
4648 &mut core_ctx,
4649 &mut bindings_ctx,
4650 &FakeLinkDeviceId,
4651 I::LOOKUP_ADDR1,
4652 DynamicNeighborUpdateSource::Confirmation {
4653 link_address: Some(LINK_ADDR1),
4654 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4655 },
4656 );
4657 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4658
4659 let now = bindings_ctx.now();
4660 assert_neighbor_state(
4661 &core_ctx,
4662 &mut bindings_ctx,
4663 DynamicNeighborState::Reachable(Reachable {
4664 link_address: LINK_ADDR1,
4665 last_confirmed_at: now,
4666 }),
4667 Some(ExpectedEvent::Changed),
4668 );
4669 assert_eq!(
4670 core_ctx.inner.take_frames(),
4671 pending_frames
4672 .into_iter()
4673 .map(|f| (
4674 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4675 f.as_ref().to_vec(),
4676 ))
4677 .collect::<Vec<_>>()
4678 );
4679 }
4680
4681 #[ip_test(I)]
4682 fn solicitation_failure_in_incomplete<I: TestIpExt>() {
4683 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4684 bindings_ctx.timers.assert_no_timers_installed();
4685 assert_eq!(core_ctx.inner.take_frames(), []);
4686
4687 let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
4688
4689 let timer_id = NudTimerId::neighbor();
4690
4691 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4692 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4693
4694 for i in 1..=max_multicast_solicit {
4695 assert_neighbor_state(
4696 &core_ctx,
4697 &mut bindings_ctx,
4698 DynamicNeighborState::Incomplete(Incomplete {
4699 transmit_counter: NonZeroU16::new(max_multicast_solicit - i),
4700 pending_frames: pending_frames
4701 .iter()
4702 .cloned()
4703 .map(|b| (b, FakeTxMetadata::default()))
4704 .collect(),
4705 notifiers: Vec::new(),
4706 _marker: PhantomData,
4707 }),
4708 None,
4709 );
4710
4711 bindings_ctx
4712 .timers
4713 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4714 assert_neighbor_probe_sent(&mut core_ctx, None);
4715
4716 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4717 }
4718
4719 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1);
4721 bindings_ctx.timers.assert_no_timers_installed();
4722
4723 assert_eq!(core_ctx.inner.take_frames(), []);
4727 assert_eq!(core_ctx.counters().as_ref().icmp_dest_unreachable_dropped.get(), 1);
4728 }
4729
4730 #[ip_test(I)]
4731 fn solicitation_failure_in_probe<I: TestIpExt>() {
4732 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4733 bindings_ctx.timers.assert_no_timers_installed();
4734 assert_eq!(core_ctx.inner.take_frames(), []);
4735
4736 init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
4737
4738 let timer_id = NudTimerId::neighbor();
4739 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4740 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4741 for i in 1..=max_unicast_solicit {
4742 assert_neighbor_state(
4743 &core_ctx,
4744 &mut bindings_ctx,
4745 DynamicNeighborState::Probe(Probe {
4746 transmit_counter: NonZeroU16::new(max_unicast_solicit - i),
4747 link_address: LINK_ADDR1,
4748 }),
4749 None,
4750 );
4751
4752 bindings_ctx
4753 .timers
4754 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4755 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4756
4757 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4758 }
4759
4760 assert_neighbor_state(
4761 &core_ctx,
4762 &mut bindings_ctx,
4763 DynamicNeighborState::Unreachable(Unreachable {
4764 link_address: LINK_ADDR1,
4765 mode: UnreachableMode::WaitingForPacketSend,
4766 }),
4767 Some(ExpectedEvent::Changed),
4768 );
4769 bindings_ctx.timers.assert_no_timers_installed();
4770 assert_eq!(core_ctx.inner.take_frames(), []);
4771 }
4772
4773 #[ip_test(I)]
4774 fn flush_entries<I: TestIpExt>() {
4775 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4776 bindings_ctx.timers.assert_no_timers_installed();
4777 assert_eq!(core_ctx.inner.take_frames(), []);
4778
4779 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4780 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR2, LINK_ADDR2);
4781 let pending_frames = init_incomplete_neighbor_with_ip(
4782 &mut core_ctx,
4783 &mut bindings_ctx,
4784 I::LOOKUP_ADDR3,
4785 true,
4786 );
4787 let pending_frames =
4788 pending_frames.into_iter().map(|b| (b, FakeTxMetadata::default())).collect();
4789
4790 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4791 assert_eq!(
4792 core_ctx.nud.state.neighbors,
4793 HashMap::from([
4794 (I::LOOKUP_ADDR1, NeighborState::Static(LINK_ADDR1)),
4795 (
4796 I::LOOKUP_ADDR2,
4797 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
4798 link_address: LINK_ADDR2,
4799 })),
4800 ),
4801 (
4802 I::LOOKUP_ADDR3,
4803 NeighborState::Dynamic(DynamicNeighborState::Incomplete(Incomplete {
4804 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4805 pending_frames,
4806 notifiers: Vec::new(),
4807 _marker: PhantomData,
4808 })),
4809 ),
4810 ]),
4811 );
4812 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4813 &mut bindings_ctx,
4814 [(I::LOOKUP_ADDR3, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
4815 );
4816
4817 NudHandler::flush(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId);
4819 let neighbors = &core_ctx.nud.state.neighbors;
4820 assert!(neighbors.is_empty(), "neighbor table should be empty: {:?}", neighbors);
4821 assert_eq!(
4822 bindings_ctx.take_events().into_iter().collect::<HashSet<_>>(),
4823 [I::LOOKUP_ADDR1, I::LOOKUP_ADDR2, I::LOOKUP_ADDR3]
4824 .into_iter()
4825 .map(|addr| { Event::removed(&FakeLinkDeviceId, addr, bindings_ctx.now()) })
4826 .collect(),
4827 );
4828 bindings_ctx.timers.assert_no_timers_installed();
4829 }
4830
4831 #[ip_test(I)]
4832 fn delete_dynamic_entry<I: TestIpExt>() {
4833 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4834 bindings_ctx.timers.assert_no_timers_installed();
4835 assert_eq!(core_ctx.inner.take_frames(), []);
4836
4837 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4838 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4839
4840 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4841
4842 let neighbors = &core_ctx.nud.state.neighbors;
4844 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4845 bindings_ctx.timers.assert_no_timers_installed();
4846 }
4847
4848 #[ip_test(I)]
4849 #[test_case(InitialState::Reachable; "reachable neighbor")]
4850 #[test_case(InitialState::Stale; "stale neighbor")]
4851 #[test_case(InitialState::Delay; "delay neighbor")]
4852 #[test_case(InitialState::Probe; "probe neighbor")]
4853 #[test_case(InitialState::Unreachable; "unreachable neighbor")]
4854 fn resolve_cached_linked_addr<I: TestIpExt>(initial_state: InitialState) {
4855 let mut ctx = new_context::<I>();
4856 ctx.bindings_ctx.timers.assert_no_timers_installed();
4857 assert_eq!(ctx.core_ctx.inner.take_frames(), []);
4858
4859 let _ = init_neighbor_in_state(&mut ctx.core_ctx, &mut ctx.bindings_ctx, initial_state);
4860
4861 let link_addr = assert_matches!(
4862 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4863 &FakeLinkDeviceId,
4864 &I::LOOKUP_ADDR1,
4865 ),
4866 LinkResolutionResult::Resolved(addr) => addr
4867 );
4868 assert_eq!(link_addr, LINK_ADDR1);
4869 if initial_state == InitialState::Stale {
4870 assert_eq!(
4871 ctx.bindings_ctx.take_events(),
4872 [Event::changed(
4873 &FakeLinkDeviceId,
4874 EventState::Dynamic(EventDynamicState::Delay(LINK_ADDR1)),
4875 I::LOOKUP_ADDR1,
4876 ctx.bindings_ctx.now(),
4877 )],
4878 );
4879 }
4880 }
4881
4882 enum ResolutionSuccess {
4883 Confirmation,
4884 StaticEntryAdded,
4885 }
4886
4887 #[ip_test(I)]
4888 #[test_case(ResolutionSuccess::Confirmation; "incomplete entry timed out")]
4889 #[test_case(ResolutionSuccess::StaticEntryAdded; "incomplete entry removed from table")]
4890 fn dynamic_neighbor_resolution_success<I: TestIpExt>(reason: ResolutionSuccess) {
4891 let mut ctx = new_context::<I>();
4892
4893 let observers = (0..10)
4894 .map(|_| {
4895 let observer = assert_matches!(
4896 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4897 &FakeLinkDeviceId,
4898 &I::LOOKUP_ADDR1,
4899 ),
4900 LinkResolutionResult::Pending(observer) => observer
4901 );
4902 assert_eq!(*observer.lock(), None);
4903 observer
4904 })
4905 .collect::<Vec<_>>();
4906 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
4907 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4908
4909 assert_neighbor_state(
4912 core_ctx,
4913 bindings_ctx,
4914 DynamicNeighborState::Incomplete(Incomplete {
4915 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4916 pending_frames: VecDeque::new(),
4917 notifiers: Vec::new(),
4919 _marker: PhantomData,
4920 }),
4921 Some(ExpectedEvent::Added),
4922 );
4923 assert_neighbor_probe_sent(core_ctx, None);
4924
4925 match reason {
4926 ResolutionSuccess::Confirmation => {
4927 NudHandler::handle_neighbor_update(
4929 core_ctx,
4930 bindings_ctx,
4931 &FakeLinkDeviceId,
4932 I::LOOKUP_ADDR1,
4933 DynamicNeighborUpdateSource::Confirmation {
4934 link_address: Some(LINK_ADDR1),
4935 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4936 },
4937 );
4938 let now = bindings_ctx.now();
4939 assert_neighbor_state(
4940 core_ctx,
4941 bindings_ctx,
4942 DynamicNeighborState::Reachable(Reachable {
4943 link_address: LINK_ADDR1,
4944 last_confirmed_at: now,
4945 }),
4946 Some(ExpectedEvent::Changed),
4947 );
4948 }
4949 ResolutionSuccess::StaticEntryAdded => {
4950 init_static_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, ExpectedEvent::Changed);
4951 assert_eq!(
4952 core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
4953 Some(&NeighborState::Static(LINK_ADDR1))
4954 );
4955 }
4956 }
4957
4958 for observer in observers {
4960 assert_eq!(*observer.lock(), Some(Ok(LINK_ADDR1)));
4961 }
4962 }
4963
4964 enum ResolutionFailure {
4965 Timeout,
4966 Removed,
4967 }
4968
4969 #[ip_test(I)]
4970 #[test_case(ResolutionFailure::Timeout; "incomplete entry timed out")]
4971 #[test_case(ResolutionFailure::Removed; "incomplete entry removed from table")]
4972 fn dynamic_neighbor_resolution_failure<I: TestIpExt>(reason: ResolutionFailure) {
4973 let mut ctx = new_context::<I>();
4974
4975 let observers = (0..10)
4976 .map(|_| {
4977 let observer = assert_matches!(
4978 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4979 &FakeLinkDeviceId,
4980 &I::LOOKUP_ADDR1,
4981 ),
4982 LinkResolutionResult::Pending(observer) => observer
4983 );
4984 assert_eq!(*observer.lock(), None);
4985 observer
4986 })
4987 .collect::<Vec<_>>();
4988
4989 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
4990 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4991
4992 assert_neighbor_state(
4995 core_ctx,
4996 bindings_ctx,
4997 DynamicNeighborState::Incomplete(Incomplete {
4998 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4999 pending_frames: VecDeque::new(),
5000 notifiers: Vec::new(),
5002 _marker: PhantomData,
5003 }),
5004 Some(ExpectedEvent::Added),
5005 );
5006 assert_neighbor_probe_sent(core_ctx, None);
5007
5008 match reason {
5009 ResolutionFailure::Timeout => {
5010 for _ in 1..=max_multicast_solicit {
5013 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
5014 assert_eq!(
5015 bindings_ctx.trigger_timers_for(retrans_timer, core_ctx),
5016 [NudTimerId::neighbor()]
5017 );
5018 }
5019 }
5020 ResolutionFailure::Removed => {
5021 NudHandler::flush(core_ctx, bindings_ctx, &FakeLinkDeviceId);
5023 }
5024 }
5025
5026 assert_neighbor_removed_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1);
5027 for observer in observers {
5029 assert_eq!(*observer.lock(), Some(Err(AddressResolutionFailed)));
5030 }
5031 }
5032
5033 #[ip_test(I)]
5034 #[test_case(InitialState::Incomplete, false; "incomplete neighbor")]
5035 #[test_case(InitialState::Reachable, true; "reachable neighbor")]
5036 #[test_case(InitialState::Stale, true; "stale neighbor")]
5037 #[test_case(InitialState::Delay, true; "delay neighbor")]
5038 #[test_case(InitialState::Probe, true; "probe neighbor")]
5039 #[test_case(InitialState::Unreachable, true; "unreachable neighbor")]
5040 fn upper_layer_confirmation<I: TestIpExt>(
5041 initial_state: InitialState,
5042 should_transition_to_reachable: bool,
5043 ) {
5044 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5045 let base_reachable_time = core_ctx.inner.base_reachable_time().get();
5046
5047 let initial = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
5048
5049 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5050
5051 if !should_transition_to_reachable {
5052 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial, None);
5053 return;
5054 }
5055
5056 let now = bindings_ctx.now();
5058 assert_neighbor_state(
5059 &core_ctx,
5060 &mut bindings_ctx,
5061 DynamicNeighborState::Reachable(Reachable {
5062 link_address: LINK_ADDR1,
5063 last_confirmed_at: now,
5064 }),
5065 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
5066 );
5067 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5068 &mut bindings_ctx,
5069 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time)],
5070 );
5071
5072 bindings_ctx.timers.instant.sleep(base_reachable_time / 2);
5076 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
5077 let now = bindings_ctx.now();
5078 assert_neighbor_state(
5079 &core_ctx,
5080 &mut bindings_ctx,
5081 DynamicNeighborState::Reachable(Reachable {
5082 link_address: LINK_ADDR1,
5083 last_confirmed_at: now,
5084 }),
5085 None,
5086 );
5087 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5088 &mut bindings_ctx,
5089 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5090 );
5091
5092 assert_eq!(
5095 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5096 [NudTimerId::neighbor()]
5097 );
5098 let now = bindings_ctx.now();
5099 assert_neighbor_state(
5100 &core_ctx,
5101 &mut bindings_ctx,
5102 DynamicNeighborState::Reachable(Reachable {
5103 link_address: LINK_ADDR1,
5104 last_confirmed_at: now - base_reachable_time / 2,
5105 }),
5106 None,
5107 );
5108
5109 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
5110 &mut bindings_ctx,
5111 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
5112 );
5113
5114 assert_eq!(
5117 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
5118 [NudTimerId::neighbor()]
5119 );
5120 assert_neighbor_state(
5121 &core_ctx,
5122 &mut bindings_ctx,
5123 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
5124 Some(ExpectedEvent::Changed),
5125 );
5126 bindings_ctx.timers.assert_no_timers_installed();
5127 }
5128
5129 fn generate_ip_addr<I: Ip>(i: usize) -> SpecifiedAddr<I::Addr> {
5130 I::map_ip_out(
5131 i,
5132 |i| {
5133 let start = u32::from_be_bytes(net_ip_v4!("192.168.0.1").ipv4_bytes());
5134 let bytes = (start + u32::try_from(i).unwrap()).to_be_bytes();
5135 SpecifiedAddr::new(Ipv4Addr::new(bytes)).unwrap()
5136 },
5137 |i| {
5138 let start = u128::from_be_bytes(net_ip_v6!("fe80::1").ipv6_bytes());
5139 let bytes = (start + u128::try_from(i).unwrap()).to_be_bytes();
5140 SpecifiedAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap()
5141 },
5142 )
5143 }
5144
5145 #[ip_test(I)]
5146 fn garbage_collection_retains_static_entries<I: TestIpExt>() {
5147 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5148
5149 for i in 0..MAX_ENTRIES * 2 {
5153 if i % 2 == 0 {
5154 init_stale_neighbor_with_ip(
5155 &mut core_ctx,
5156 &mut bindings_ctx,
5157 generate_ip_addr::<I>(i),
5158 LINK_ADDR1,
5159 );
5160 } else {
5161 init_static_neighbor_with_ip(
5162 &mut core_ctx,
5163 &mut bindings_ctx,
5164 generate_ip_addr::<I>(i),
5165 LINK_ADDR1,
5166 ExpectedEvent::Added,
5167 );
5168 }
5169 }
5170 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES * 2);
5171
5172 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5174 for event in bindings_ctx.take_events() {
5175 assert_matches!(event, Event {
5176 device,
5177 addr: _,
5178 kind,
5179 at,
5180 } => {
5181 assert_eq!(kind, EventKind::Removed);
5182 assert_eq!(device, FakeLinkDeviceId);
5183 assert_eq!(at, bindings_ctx.now());
5184 });
5185 }
5186 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5187 for (_, neighbor) in core_ctx.nud.state.neighbors {
5188 assert_matches!(neighbor, NeighborState::Static(_));
5189 }
5190 }
5191
5192 #[ip_test(I)]
5193 fn garbage_collection_retains_in_use_entries<I: TestIpExt>() {
5194 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5195
5196 for i in 0..MAX_ENTRIES - 1 {
5198 init_static_neighbor_with_ip(
5199 &mut core_ctx,
5200 &mut bindings_ctx,
5201 generate_ip_addr::<I>(i),
5202 LINK_ADDR1,
5203 ExpectedEvent::Added,
5204 );
5205 }
5206
5207 let stale_entry = generate_ip_addr::<I>(MAX_ENTRIES - 1);
5209 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry, LINK_ADDR1);
5210 let reachable_entry = generate_ip_addr::<I>(MAX_ENTRIES);
5212 init_reachable_neighbor_with_ip(
5213 &mut core_ctx,
5214 &mut bindings_ctx,
5215 reachable_entry,
5216 LINK_ADDR1,
5217 );
5218
5219 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5221 super::testutil::assert_dynamic_neighbor_state(
5222 &mut core_ctx,
5223 FakeLinkDeviceId,
5224 reachable_entry,
5225 DynamicNeighborState::Reachable(Reachable {
5226 link_address: LINK_ADDR1,
5227 last_confirmed_at: bindings_ctx.now(),
5228 }),
5229 );
5230 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry);
5231 }
5232
5233 #[ip_test(I)]
5234 fn garbage_collection_triggered_on_new_stale_entry<I: TestIpExt>() {
5235 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5236 core_ctx.nud.state.last_gc = Some(bindings_ctx.now());
5238
5239 for i in 0..MAX_ENTRIES {
5241 init_static_neighbor_with_ip(
5242 &mut core_ctx,
5243 &mut bindings_ctx,
5244 generate_ip_addr::<I>(i),
5245 LINK_ADDR1,
5246 ExpectedEvent::Added,
5247 );
5248 }
5249
5250 init_stale_neighbor_with_ip(
5253 &mut core_ctx,
5254 &mut bindings_ctx,
5255 generate_ip_addr::<I>(MAX_ENTRIES + 1),
5256 LINK_ADDR1,
5257 );
5258 let expected_gc_time = bindings_ctx.now() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5259 bindings_ctx
5260 .timers
5261 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5262
5263 bindings_ctx.timers.instant.sleep(ONE_SECOND.get());
5267 init_stale_neighbor_with_ip(
5268 &mut core_ctx,
5269 &mut bindings_ctx,
5270 generate_ip_addr::<I>(MAX_ENTRIES + 2),
5271 LINK_ADDR1,
5272 );
5273 bindings_ctx
5274 .timers
5275 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5276 }
5277
5278 #[ip_test(I)]
5279 fn garbage_collection_triggered_on_transition_to_unreachable<I: TestIpExt>() {
5280 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5281 core_ctx.nud.state.last_gc = Some(bindings_ctx.now());
5283
5284 for i in 0..MAX_ENTRIES {
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 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5295
5296 init_unreachable_neighbor_with_ip(
5299 &mut core_ctx,
5300 &mut bindings_ctx,
5301 generate_ip_addr::<I>(MAX_ENTRIES),
5302 LINK_ADDR1,
5303 );
5304 let expected_gc_time =
5305 core_ctx.nud.state.last_gc.unwrap() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5306 bindings_ctx
5307 .timers
5308 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5309
5310 init_unreachable_neighbor_with_ip(
5313 &mut core_ctx,
5314 &mut bindings_ctx,
5315 generate_ip_addr::<I>(MAX_ENTRIES + 1),
5316 LINK_ADDR1,
5317 );
5318 bindings_ctx
5319 .timers
5320 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5321 }
5322
5323 #[ip_test(I)]
5324 fn garbage_collection_not_triggered_on_new_incomplete_entry<I: TestIpExt>() {
5325 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5326
5327 for i in 0..MAX_ENTRIES {
5329 init_static_neighbor_with_ip(
5330 &mut core_ctx,
5331 &mut bindings_ctx,
5332 generate_ip_addr::<I>(i),
5333 LINK_ADDR1,
5334 ExpectedEvent::Added,
5335 );
5336 }
5337 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5338
5339 let _: VecDeque<Buf<Vec<u8>>> = init_incomplete_neighbor_with_ip(
5340 &mut core_ctx,
5341 &mut bindings_ctx,
5342 generate_ip_addr::<I>(MAX_ENTRIES),
5343 true,
5344 );
5345 assert_eq!(
5346 bindings_ctx.timers.scheduled_instant(&mut core_ctx.nud.state.timer_heap.gc),
5347 None
5348 );
5349 }
5350
5351 #[ip_test(I)]
5352 fn confirmation_processed_even_if_no_target_link_layer_addr<I: TestIpExt>() {
5353 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5354
5355 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
5357
5358 NudHandler::handle_neighbor_update(
5362 &mut core_ctx,
5363 &mut bindings_ctx,
5364 &FakeLinkDeviceId,
5365 I::LOOKUP_ADDR1,
5366 DynamicNeighborUpdateSource::Confirmation {
5367 link_address: None,
5368 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
5369 },
5370 );
5371 let now = bindings_ctx.now();
5372 assert_neighbor_state(
5373 &core_ctx,
5374 &mut bindings_ctx,
5375 DynamicNeighborState::Reachable(Reachable {
5376 link_address: LINK_ADDR1,
5377 last_confirmed_at: now,
5378 }),
5379 Some(ExpectedEvent::Changed),
5380 );
5381 }
5382
5383 #[ip_test(I)]
5384 #[test_case(InitialState::Stale; "stale")]
5385 #[test_case(InitialState::Reachable; "reachable")]
5386 #[test_case(InitialState::Delay; "delay")]
5387 #[test_case(InitialState::Unreachable; "unreachable")]
5388 fn enter_probe_from_dynamic_state<I: TestIpExt>(initial: InitialState) {
5389 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5390
5391 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial);
5392
5393 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5394 let result = neighbor.enter_probe(
5395 &mut core_ctx.inner.state,
5396 &mut bindings_ctx,
5397 &mut core_ctx.nud.state.timer_heap,
5398 I::LOOKUP_ADDR1,
5399 &FakeLinkDeviceId,
5400 );
5401
5402 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5403 assert_matches!(result, Ok(Some(LINK_ADDR1)));
5404 assert_neighbor_state(
5405 &core_ctx,
5406 &mut bindings_ctx,
5407 DynamicNeighborState::Probe(Probe {
5408 link_address: LINK_ADDR1,
5409 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5410 }),
5411 Some(ExpectedEvent::Changed),
5412 );
5413 }
5414
5415 #[ip_test(I)]
5416 fn enter_probe_from_static_state<I: TestIpExt>() {
5417 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5418
5419 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
5420
5421 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5422 let result = neighbor.enter_probe(
5423 &mut core_ctx.inner.state,
5424 &mut bindings_ctx,
5425 &mut core_ctx.nud.state.timer_heap,
5426 I::LOOKUP_ADDR1,
5427 &FakeLinkDeviceId,
5428 );
5429
5430 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5431 assert_matches!(result, Ok(Some(LINK_ADDR1)));
5432 assert_neighbor_state(
5433 &core_ctx,
5434 &mut bindings_ctx,
5435 DynamicNeighborState::Probe(Probe {
5436 link_address: LINK_ADDR1,
5437 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5438 }),
5439 Some(ExpectedEvent::Changed),
5440 );
5441 }
5442
5443 #[ip_test(I)]
5444 fn enter_probe_from_probe_state<I: TestIpExt>() {
5445 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5446
5447 init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
5448
5449 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5450 let result = neighbor.enter_probe(
5451 &mut core_ctx.inner.state,
5452 &mut bindings_ctx,
5453 &mut core_ctx.nud.state.timer_heap,
5454 I::LOOKUP_ADDR1,
5455 &FakeLinkDeviceId,
5456 );
5457
5458 let max_unicast_probes = core_ctx.inner.max_unicast_solicit().get();
5459 assert_matches!(result, Ok(None)); assert_neighbor_state(
5461 &core_ctx,
5462 &mut bindings_ctx,
5463 DynamicNeighborState::Probe(Probe {
5464 link_address: LINK_ADDR1,
5465 transmit_counter: Some(NonZeroU16::new(max_unicast_probes - 1).unwrap()),
5466 }),
5467 None, );
5469 }
5470
5471 #[ip_test(I)]
5472 fn enter_probe_from_incomplete_state<I: TestIpExt>() {
5473 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5474
5475 let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
5476
5477 let neighbor = core_ctx.nud.state.neighbors.get_mut(&I::LOOKUP_ADDR1).unwrap();
5478 let result = neighbor.enter_probe(
5479 &mut core_ctx.inner.state,
5480 &mut bindings_ctx,
5481 &mut core_ctx.nud.state.timer_heap,
5482 I::LOOKUP_ADDR1,
5483 &FakeLinkDeviceId,
5484 );
5485
5486 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
5487 assert_matches!(result, Err(EnterProbeError::LinkAddressUnknown));
5488 assert_neighbor_state(
5489 &core_ctx,
5490 &mut bindings_ctx,
5491 DynamicNeighborState::Incomplete(Incomplete {
5492 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
5493 pending_frames: pending_frames
5494 .iter()
5495 .cloned()
5496 .map(|b| (b, FakeTxMetadata::default()))
5497 .collect(),
5498 notifiers: Vec::new(),
5499 _marker: PhantomData,
5500 }),
5501 None, );
5503 }
5504}