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};
14
15use assert_matches::assert_matches;
16use derivative::Derivative;
17use log::{debug, error, warn};
18use net_types::SpecifiedAddr;
19use net_types::ip::{GenericOverIp, Ip, IpMarked, Ipv4, Ipv6};
20use netstack3_base::socket::{SocketIpAddr, SocketIpAddrExt as _};
21use netstack3_base::{
22 AddressResolutionFailed, AnyDevice, CoreTimerContext, Counter, CounterContext, DeviceIdContext,
23 DeviceIdentifier, ErrorAndSerializer, EventContext, HandleableTimer, Instant,
24 InstantBindingsTypes, LinkAddress, LinkDevice, LinkUnicastAddress, LocalTimerHeap,
25 SendFrameError, StrongDeviceIdentifier, TimerBindingsTypes, TimerContext,
26 TxMetadataBindingsTypes, WeakDeviceIdentifier,
27};
28use netstack3_hashmap::hash_map::{self, Entry, HashMap};
29use packet::{
30 Buf, BufferMut, GrowBuffer as _, ParsablePacket as _, ParseBufferMut as _, SerializeError,
31 Serializer,
32};
33use packet_formats::ip::IpPacket as _;
34use packet_formats::ipv4::{Ipv4FragmentType, Ipv4Header as _, Ipv4Packet};
35use packet_formats::ipv6::Ipv6Packet;
36use packet_formats::utils::NonZeroDuration;
37use zerocopy::SplitByteSlice;
38
39pub(crate) mod api;
40
41pub(crate) const DEFAULT_MAX_MULTICAST_SOLICIT: NonZeroU16 = NonZeroU16::new(3).unwrap();
46
47const DEFAULT_MAX_UNICAST_SOLICIT: NonZeroU16 = NonZeroU16::new(3).unwrap();
52
53const MAX_RETRANS_TIMER: NonZeroDuration = NonZeroDuration::from_secs(60).unwrap();
58
59const BACKOFF_MULTIPLE: NonZeroU32 = NonZeroU32::new(3).unwrap();
64
65const MAX_PENDING_FRAMES: usize = 10;
66
67const DEFAULT_BASE_REACHABLE_TIME: NonZeroDuration = NonZeroDuration::from_secs(30).unwrap();
72
73const DELAY_FIRST_PROBE_TIME: NonZeroDuration = NonZeroDuration::from_secs(5).unwrap();
78
79pub const MAX_ENTRIES: usize = 512;
84
85const MIN_GARBAGE_COLLECTION_INTERVAL: NonZeroDuration = NonZeroDuration::from_secs(30).unwrap();
88
89#[derive(Default)]
91pub struct NudCountersInner {
92 pub icmp_dest_unreachable_dropped: Counter,
94}
95
96pub type NudCounters<I> = IpMarked<I, NudCountersInner>;
98
99#[derive(Debug, Copy, Clone)]
101pub struct ConfirmationFlags {
102 pub solicited_flag: bool,
104 pub override_flag: bool,
106}
107
108#[derive(Debug, Copy, Clone)]
110pub enum DynamicNeighborUpdateSource<A> {
111 Probe {
115 link_address: A,
118 },
119
120 Confirmation {
124 link_address: Option<A>,
127 flags: ConfirmationFlags,
129 },
130}
131
132#[derive(Derivative)]
134#[derivative(Debug(bound = ""))]
135#[cfg_attr(
136 any(test, feature = "testutils"),
137 derivative(
138 Clone(bound = "BT::TxMetadata: Clone"),
139 PartialEq(bound = "BT::TxMetadata: PartialEq"),
140 Eq(bound = "BT::TxMetadata: Eq")
141 )
142)]
143#[allow(missing_docs)]
144pub enum NeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
145 Dynamic(DynamicNeighborState<D, BT>),
146 Static(D::Address),
147}
148
149#[derive(Derivative)]
156#[derivative(Debug(bound = ""))]
157#[cfg_attr(
158 any(test, feature = "testutils"),
159 derivative(
160 Clone(bound = "BT::TxMetadata: Clone"),
161 PartialEq(bound = "BT::TxMetadata: PartialEq"),
162 Eq(bound = "BT::TxMetadata: Eq")
163 )
164)]
165pub enum DynamicNeighborState<D: LinkDevice, BT: NudBindingsTypes<D>> {
166 Incomplete(Incomplete<D, BT::Notifier, BT::TxMetadata>),
172
173 Reachable(Reachable<D, BT::Instant>),
178
179 Stale(Stale<D>),
190
191 Delay(Delay<D>),
203
204 Probe(Probe<D>),
208
209 Unreachable(Unreachable<D>),
216}
217
218#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
233pub enum EventDynamicState<L: LinkUnicastAddress> {
234 Incomplete,
237 Reachable(L),
240 Stale(L),
248 Delay(L),
256 Probe(L),
261 Unreachable(L),
265}
266
267#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
275pub enum EventState<L: LinkUnicastAddress> {
276 Dynamic(EventDynamicState<L>),
278 Static(L),
280}
281
282#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
284pub enum EventKind<L: LinkUnicastAddress> {
285 Added(EventState<L>),
287 Changed(EventState<L>),
289 Removed,
291}
292
293#[derive(Debug, Eq, Hash, PartialEq, GenericOverIp)]
295#[generic_over_ip(I, Ip)]
296pub struct Event<L: LinkUnicastAddress, DeviceId, I: Ip, Instant> {
297 pub device: DeviceId,
299 pub addr: SpecifiedAddr<I::Addr>,
301 pub kind: EventKind<L>,
303 pub at: Instant,
305}
306
307impl<L: LinkUnicastAddress, DeviceId, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
308 pub fn map_device<N, F: FnOnce(DeviceId) -> N>(self, map: F) -> Event<L, N, I, Instant> {
310 let Self { device, kind, addr, at } = self;
311 Event { device: map(device), kind, addr, at }
312 }
313}
314
315impl<L: LinkUnicastAddress, DeviceId: Clone, I: Ip, Instant> Event<L, DeviceId, I, Instant> {
316 fn changed(
317 device: &DeviceId,
318 event_state: EventState<L>,
319 addr: SpecifiedAddr<I::Addr>,
320 at: Instant,
321 ) -> Self {
322 Self { device: device.clone(), kind: EventKind::Changed(event_state), addr, at }
323 }
324
325 fn added(
326 device: &DeviceId,
327 event_state: EventState<L>,
328 addr: SpecifiedAddr<I::Addr>,
329 at: Instant,
330 ) -> Self {
331 Self { device: device.clone(), kind: EventKind::Added(event_state), addr, at }
332 }
333
334 fn removed(device: &DeviceId, addr: SpecifiedAddr<I::Addr>, at: Instant) -> Self {
335 Self { device: device.clone(), kind: EventKind::Removed, addr, at }
336 }
337}
338
339fn schedule_timer_if_should_retransmit<I, D, DeviceId, CC, BC>(
340 core_ctx: &mut CC,
341 bindings_ctx: &mut BC,
342 timers: &mut TimerHeap<I, BC>,
343 neighbor: SpecifiedAddr<I::Addr>,
344 event: NudEvent,
345 counter: &mut Option<NonZeroU16>,
346) -> bool
347where
348 I: Ip,
349 D: LinkDevice,
350 DeviceId: StrongDeviceIdentifier,
351 BC: NudBindingsContext<I, D, DeviceId>,
352 CC: NudConfigContext<I>,
353{
354 match counter {
355 Some(c) => {
356 *counter = NonZeroU16::new(c.get() - 1);
357 let retransmit_timeout = core_ctx.retransmit_timeout();
358 timers.schedule_neighbor(bindings_ctx, retransmit_timeout, neighbor, event);
359 true
360 }
361 None => false,
362 }
363}
364
365#[derive(Debug, Derivative)]
367#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq(bound = "M: PartialEq"), Eq))]
368pub struct Incomplete<D: LinkDevice, N: LinkResolutionNotifier<D>, M> {
369 transmit_counter: Option<NonZeroU16>,
370 pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
371 #[derivative(PartialEq = "ignore")]
372 notifiers: Vec<N>,
373 _marker: PhantomData<D>,
374}
375
376#[cfg(any(test, feature = "testutils"))]
377impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M: Clone> Clone for Incomplete<D, N, M> {
378 fn clone(&self) -> Self {
379 let Self { transmit_counter, pending_frames, notifiers: _, _marker } = self;
383 Self {
384 transmit_counter: transmit_counter.clone(),
385 pending_frames: pending_frames.clone(),
386 notifiers: Vec::new(),
387 _marker: PhantomData,
388 }
389 }
390}
391
392impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Drop for Incomplete<D, N, M> {
393 fn drop(&mut self) {
394 let Self { transmit_counter: _, pending_frames: _, notifiers, _marker } = self;
395 for notifier in notifiers.drain(..) {
396 notifier.notify(Err(AddressResolutionFailed));
397 }
398 }
399}
400
401impl<D: LinkDevice, N: LinkResolutionNotifier<D>, M> Incomplete<D, N, M> {
402 #[cfg(any(test, feature = "testutils"))]
405 pub fn new_with_pending_frames_and_transmit_counter(
406 pending_frames: VecDeque<(Buf<Vec<u8>>, M)>,
407 transmit_counter: Option<NonZeroU16>,
408 ) -> Self {
409 Self {
410 transmit_counter,
411 pending_frames,
412 notifiers: Default::default(),
413 _marker: PhantomData,
414 }
415 }
416
417 fn new_with_packet<I, CC, BC, DeviceId, B, S>(
418 core_ctx: &mut CC,
419 bindings_ctx: &mut BC,
420 timers: &mut TimerHeap<I, BC>,
421 neighbor: SpecifiedAddr<I::Addr>,
422 packet: S,
423 meta: M,
424 ) -> Result<Self, ErrorAndSerializer<SerializeError<Never>, S>>
425 where
426 I: Ip,
427 D: LinkDevice,
428 BC: NudBindingsContext<I, D, DeviceId>,
429 CC: NudConfigContext<I>,
430 DeviceId: StrongDeviceIdentifier,
431 B: BufferMut,
432 S: Serializer<Buffer = B>,
433 {
434 let packet = packet
438 .serialize_vec_outer()
439 .map_err(|(error, serializer)| ErrorAndSerializer { error, serializer })?
440 .map_a(|b| Buf::new(b.as_ref().to_vec(), ..))
441 .into_inner();
442
443 let mut this = Incomplete {
444 transmit_counter: Some(core_ctx.max_multicast_solicit()),
445 pending_frames: VecDeque::from([(packet, meta)]),
446 notifiers: Vec::new(),
447 _marker: PhantomData,
448 };
449 assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
453
454 Ok(this)
455 }
456
457 fn new_with_notifier<I, CC, BC, DeviceId>(
458 core_ctx: &mut CC,
459 bindings_ctx: &mut BC,
460 timers: &mut TimerHeap<I, BC>,
461 neighbor: SpecifiedAddr<I::Addr>,
462 notifier: BC::Notifier,
463 ) -> Self
464 where
465 I: Ip,
466 D: LinkDevice,
467 BC: NudBindingsContext<I, D, DeviceId, Notifier = N>,
468 CC: NudConfigContext<I>,
469 DeviceId: StrongDeviceIdentifier,
470 {
471 let mut this = Incomplete {
472 transmit_counter: Some(core_ctx.max_multicast_solicit()),
473 pending_frames: VecDeque::new(),
474 notifiers: [notifier].into(),
475 _marker: PhantomData,
476 };
477 assert!(this.schedule_timer_if_should_retransmit(core_ctx, bindings_ctx, timers, neighbor));
481
482 this
483 }
484
485 fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
486 &mut self,
487 core_ctx: &mut CC,
488 bindings_ctx: &mut BC,
489 timers: &mut TimerHeap<I, BC>,
490 neighbor: SpecifiedAddr<I::Addr>,
491 ) -> bool
492 where
493 I: Ip,
494 D: LinkDevice,
495 DeviceId: StrongDeviceIdentifier,
496 BC: NudBindingsContext<I, D, DeviceId>,
497 CC: NudConfigContext<I>,
498 {
499 let Self { transmit_counter, pending_frames: _, notifiers: _, _marker } = self;
500 schedule_timer_if_should_retransmit(
501 core_ctx,
502 bindings_ctx,
503 timers,
504 neighbor,
505 NudEvent::RetransmitMulticastProbe,
506 transmit_counter,
507 )
508 }
509
510 fn queue_packet<B, S>(
511 &mut self,
512 body: S,
513 meta: M,
514 ) -> Result<(), ErrorAndSerializer<SerializeError<Never>, S>>
515 where
516 B: BufferMut,
517 S: Serializer<Buffer = B>,
518 {
519 let Self { pending_frames, transmit_counter: _, notifiers: _, _marker } = self;
520
521 if pending_frames.len() < MAX_PENDING_FRAMES {
528 pending_frames.push_back((
529 body.serialize_vec_outer()
530 .map_err(|(error, serializer)| ErrorAndSerializer { error, serializer })?
531 .map_a(|b| Buf::new(b.as_ref().to_vec(), ..))
532 .into_inner(),
533 meta,
534 ));
535 }
536 Ok(())
537 }
538
539 fn complete_resolution<I, CC, BC>(
542 &mut self,
543 core_ctx: &mut CC,
544 bindings_ctx: &mut BC,
545 link_address: D::Address,
546 ) where
547 I: Ip,
548 D: LinkDevice,
549 BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata = M>,
550 CC: NudSenderContext<I, D, BC>,
551 {
552 let Self { pending_frames, notifiers, transmit_counter: _, _marker } = self;
553
554 for (body, meta) in pending_frames.drain(..) {
562 core_ctx
566 .send_ip_packet_to_neighbor_link_addr(bindings_ctx, link_address, body, meta)
567 .unwrap_or_else(|err| {
568 error!("failed to send pending IP packet to neighbor {link_address:?} {err:?}")
569 })
570 }
571 for notifier in notifiers.drain(..) {
572 notifier.notify(Ok(link_address));
573 }
574 }
575}
576
577#[derive(Debug, Derivative)]
579#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
580pub struct Reachable<D: LinkDevice, I: Instant> {
581 pub link_address: D::Address,
583 pub last_confirmed_at: I,
585}
586
587#[derive(Debug, Derivative)]
589#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
590pub struct Stale<D: LinkDevice> {
591 pub link_address: D::Address,
593}
594
595impl<D: LinkDevice> Stale<D> {
596 fn enter_delay<I, BC, DeviceId: Clone>(
597 &mut self,
598 bindings_ctx: &mut BC,
599 timers: &mut TimerHeap<I, BC>,
600 neighbor: SpecifiedAddr<I::Addr>,
601 ) -> Delay<D>
602 where
603 I: Ip,
604 BC: NudBindingsContext<I, D, DeviceId>,
605 {
606 let Self { link_address } = *self;
607
608 timers.schedule_neighbor(
611 bindings_ctx,
612 DELAY_FIRST_PROBE_TIME,
613 neighbor,
614 NudEvent::DelayFirstProbe,
615 );
616
617 Delay { link_address }
618 }
619}
620
621#[derive(Debug, Derivative)]
623#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
624pub struct Delay<D: LinkDevice> {
625 pub link_address: D::Address,
627}
628
629impl<D: LinkDevice> Delay<D> {
630 fn enter_probe<I, DeviceId, CC, BC>(
631 &mut self,
632 core_ctx: &mut CC,
633 bindings_ctx: &mut BC,
634 timers: &mut TimerHeap<I, BC>,
635 neighbor: SpecifiedAddr<I::Addr>,
636 ) -> Probe<D>
637 where
638 I: Ip,
639 DeviceId: StrongDeviceIdentifier,
640 BC: NudBindingsContext<I, D, DeviceId>,
641 CC: NudConfigContext<I>,
642 {
643 let Self { link_address } = *self;
644
645 let retransmit_timeout = core_ctx.retransmit_timeout();
649 timers.schedule_neighbor(
650 bindings_ctx,
651 retransmit_timeout,
652 neighbor,
653 NudEvent::RetransmitUnicastProbe,
654 );
655
656 Probe {
657 link_address,
658 transmit_counter: NonZeroU16::new(core_ctx.max_unicast_solicit().get() - 1),
659 }
660 }
661}
662
663#[derive(Debug, Derivative)]
664#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
665pub struct Probe<D: LinkDevice> {
666 link_address: D::Address,
667 transmit_counter: Option<NonZeroU16>,
668}
669
670impl<D: LinkDevice> Probe<D> {
671 fn schedule_timer_if_should_retransmit<I, DeviceId, CC, BC>(
672 &mut self,
673 core_ctx: &mut CC,
674 bindings_ctx: &mut BC,
675 timers: &mut TimerHeap<I, BC>,
676 neighbor: SpecifiedAddr<I::Addr>,
677 ) -> bool
678 where
679 I: Ip,
680 DeviceId: StrongDeviceIdentifier,
681 BC: NudBindingsContext<I, D, DeviceId>,
682 CC: NudConfigContext<I>,
683 {
684 let Self { link_address: _, transmit_counter } = self;
685 schedule_timer_if_should_retransmit(
686 core_ctx,
687 bindings_ctx,
688 timers,
689 neighbor,
690 NudEvent::RetransmitUnicastProbe,
691 transmit_counter,
692 )
693 }
694
695 fn enter_unreachable<I, BC, DeviceId>(
696 &mut self,
697 bindings_ctx: &mut BC,
698 timers: &mut TimerHeap<I, BC>,
699 num_entries: usize,
700 last_gc: &mut Option<BC::Instant>,
701 ) -> Unreachable<D>
702 where
703 I: Ip,
704 BC: NudBindingsContext<I, D, DeviceId>,
705 DeviceId: Clone,
706 {
707 timers.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
711
712 let Self { link_address, transmit_counter: _ } = self;
713 Unreachable { link_address: *link_address, mode: UnreachableMode::WaitingForPacketSend }
714 }
715}
716
717#[derive(Debug, Derivative)]
718#[cfg_attr(any(test, feature = "testutils"), derivative(Clone, PartialEq, Eq))]
719pub struct Unreachable<D: LinkDevice> {
720 link_address: D::Address,
721 mode: UnreachableMode,
722}
723
724#[derive(Debug, Clone, Copy, Derivative)]
736#[cfg_attr(any(test, feature = "testutils"), derivative(PartialEq, Eq))]
737pub(crate) enum UnreachableMode {
738 WaitingForPacketSend,
739 Backoff { probes_sent: NonZeroU32, packet_sent: bool },
740}
741
742impl UnreachableMode {
743 fn next_backoff_retransmit_timeout<I, CC>(&self, core_ctx: &mut CC) -> NonZeroDuration
749 where
750 I: Ip,
751 CC: NudConfigContext<I>,
752 {
753 let probes_sent = match self {
754 UnreachableMode::Backoff { probes_sent, packet_sent: _ } => probes_sent,
755 UnreachableMode::WaitingForPacketSend => {
756 panic!("cannot calculate exponential backoff in state {self:?}")
757 }
758 };
759 (core_ctx.retransmit_timeout() * BACKOFF_MULTIPLE.saturating_pow(probes_sent.get()))
763 .min(MAX_RETRANS_TIMER)
764 }
765}
766
767impl<D: LinkDevice> Unreachable<D> {
768 fn handle_timer<I, DeviceId, CC, BC>(
769 &mut self,
770 core_ctx: &mut CC,
771 bindings_ctx: &mut BC,
772 timers: &mut TimerHeap<I, BC>,
773 device_id: &DeviceId,
774 neighbor: SpecifiedAddr<I::Addr>,
775 ) -> Option<TransmitProbe<D::Address>>
776 where
777 I: Ip,
778 DeviceId: StrongDeviceIdentifier,
779 BC: NudBindingsContext<I, D, DeviceId>,
780 CC: NudConfigContext<I>,
781 {
782 let Self { link_address: _, mode } = self;
783 match mode {
784 UnreachableMode::WaitingForPacketSend => {
785 panic!(
786 "timer should not have fired in UNREACHABLE while waiting for packet send; got \
787 a retransmit multicast probe event for {neighbor} on {device_id:?}",
788 );
789 }
790 UnreachableMode::Backoff { probes_sent, packet_sent } => {
791 if *packet_sent {
792 *probes_sent = probes_sent.saturating_add(1);
799 *packet_sent = false;
800
801 let duration = mode.next_backoff_retransmit_timeout(core_ctx);
802 timers.schedule_neighbor(
803 bindings_ctx,
804 duration,
805 neighbor,
806 NudEvent::RetransmitMulticastProbe,
807 );
808
809 Some(TransmitProbe::Multicast)
810 } else {
811 *mode = UnreachableMode::WaitingForPacketSend;
812
813 None
814 }
815 }
816 }
817 }
818
819 fn handle_packet_queued_to_send<I, DeviceId, CC, BC>(
824 &mut self,
825 core_ctx: &mut CC,
826 bindings_ctx: &mut BC,
827 timers: &mut TimerHeap<I, BC>,
828 neighbor: SpecifiedAddr<I::Addr>,
829 ) -> bool
830 where
831 I: Ip,
832 DeviceId: StrongDeviceIdentifier,
833 BC: NudBindingsContext<I, D, DeviceId>,
834 CC: NudConfigContext<I>,
835 {
836 let Self { link_address: _, mode } = self;
837 match mode {
838 UnreachableMode::WaitingForPacketSend => {
839 let probes_sent = NonZeroU32::new(1).unwrap();
854 *mode = UnreachableMode::Backoff { probes_sent, packet_sent: false };
855
856 let duration = mode.next_backoff_retransmit_timeout(core_ctx);
857 timers.schedule_neighbor(
858 bindings_ctx,
859 duration,
860 neighbor,
861 NudEvent::RetransmitMulticastProbe,
862 );
863
864 true
866 }
867 UnreachableMode::Backoff { probes_sent: _, packet_sent } => {
868 *packet_sent = true;
872
873 false
874 }
875 }
876 }
877}
878
879impl<D: LinkDevice, BT: NudBindingsTypes<D>> NeighborState<D, BT> {
880 fn to_event_state(&self) -> EventState<D::Address> {
881 match self {
882 NeighborState::Dynamic(dynamic_state) => {
883 EventState::Dynamic(dynamic_state.to_event_dynamic_state())
884 }
885 NeighborState::Static(addr) => EventState::Static(*addr),
886 }
887 }
888}
889
890impl<D: LinkDevice, BC: NudBindingsTypes<D>> DynamicNeighborState<D, BC> {
891 fn cancel_timer<I, DeviceId>(
892 &mut self,
893 bindings_ctx: &mut BC,
894 timers: &mut TimerHeap<I, BC>,
895 neighbor: SpecifiedAddr<I::Addr>,
896 ) where
897 I: Ip,
898 DeviceId: StrongDeviceIdentifier,
899 BC: NudBindingsContext<I, D, DeviceId>,
900 {
901 let expected_event = match self {
902 DynamicNeighborState::Incomplete(Incomplete {
903 transmit_counter: _,
904 pending_frames: _,
905 notifiers: _,
906 _marker,
907 }) => Some(NudEvent::RetransmitMulticastProbe),
908 DynamicNeighborState::Reachable(Reachable {
909 link_address: _,
910 last_confirmed_at: _,
911 }) => Some(NudEvent::ReachableTime),
912 DynamicNeighborState::Stale(Stale { link_address: _ }) => None,
913 DynamicNeighborState::Delay(Delay { link_address: _ }) => {
914 Some(NudEvent::DelayFirstProbe)
915 }
916 DynamicNeighborState::Probe(Probe { link_address: _, transmit_counter: _ }) => {
917 Some(NudEvent::RetransmitUnicastProbe)
918 }
919 DynamicNeighborState::Unreachable(Unreachable { link_address: _, mode }) => {
920 match mode {
923 UnreachableMode::WaitingForPacketSend => None,
924 UnreachableMode::Backoff { probes_sent: _, packet_sent: _ } => {
925 Some(NudEvent::RetransmitMulticastProbe)
926 }
927 }
928 }
929 };
930 assert_eq!(
931 timers.cancel_neighbor(bindings_ctx, neighbor),
932 expected_event,
933 "neighbor {neighbor} ({self:?}) had unexpected timer installed"
934 );
935 }
936
937 fn cancel_timer_and_complete_resolution<I, CC>(
938 mut self,
939 core_ctx: &mut CC,
940 bindings_ctx: &mut BC,
941 timers: &mut TimerHeap<I, BC>,
942 neighbor: SpecifiedAddr<I::Addr>,
943 link_address: D::Address,
944 ) where
945 I: Ip,
946 BC: NudBindingsContext<I, D, CC::DeviceId>,
947 CC: NudSenderContext<I, D, BC>,
948 {
949 self.cancel_timer(bindings_ctx, timers, neighbor);
950
951 match self {
952 DynamicNeighborState::Incomplete(mut incomplete) => {
953 incomplete.complete_resolution(core_ctx, bindings_ctx, link_address);
954 }
955 DynamicNeighborState::Reachable(_)
956 | DynamicNeighborState::Stale(_)
957 | DynamicNeighborState::Delay(_)
958 | DynamicNeighborState::Probe(_)
959 | DynamicNeighborState::Unreachable(_) => {}
960 }
961 }
962
963 fn to_event_dynamic_state(&self) -> EventDynamicState<D::Address> {
964 match self {
965 Self::Incomplete(_) => EventDynamicState::Incomplete,
966 Self::Reachable(Reachable { link_address, last_confirmed_at: _ }) => {
967 EventDynamicState::Reachable(*link_address)
968 }
969 Self::Stale(Stale { link_address }) => EventDynamicState::Stale(*link_address),
970 Self::Delay(Delay { link_address }) => EventDynamicState::Delay(*link_address),
971 Self::Probe(Probe { link_address, transmit_counter: _ }) => {
972 EventDynamicState::Probe(*link_address)
973 }
974 Self::Unreachable(Unreachable { link_address, mode: _ }) => {
975 EventDynamicState::Unreachable(*link_address)
976 }
977 }
978 }
979
980 fn enter_reachable<I, CC>(
982 &mut self,
983 core_ctx: &mut CC,
984 bindings_ctx: &mut BC,
985 timers: &mut TimerHeap<I, BC>,
986 device_id: &CC::DeviceId,
987 neighbor: SpecifiedAddr<I::Addr>,
988 link_address: D::Address,
989 ) where
990 I: Ip,
991 BC: NudBindingsContext<I, D, CC::DeviceId>,
992 CC: NudSenderContext<I, D, BC>,
993 {
994 let now = bindings_ctx.now();
997 match self {
998 DynamicNeighborState::Reachable(Reachable {
1003 link_address: current,
1004 last_confirmed_at,
1005 }) if *current == link_address => {
1006 *last_confirmed_at = now;
1007 return;
1008 }
1009 DynamicNeighborState::Incomplete(_)
1010 | DynamicNeighborState::Reachable(_)
1011 | DynamicNeighborState::Stale(_)
1012 | DynamicNeighborState::Delay(_)
1013 | DynamicNeighborState::Probe(_)
1014 | DynamicNeighborState::Unreachable(_) => {}
1015 }
1016 let previous = core::mem::replace(
1017 self,
1018 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: now }),
1019 );
1020 let event_dynamic_state = self.to_event_dynamic_state();
1021 debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1022 let event_state = EventState::Dynamic(event_dynamic_state);
1023 bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1024 previous.cancel_timer_and_complete_resolution(
1025 core_ctx,
1026 bindings_ctx,
1027 timers,
1028 neighbor,
1029 link_address,
1030 );
1031 timers.schedule_neighbor(
1032 bindings_ctx,
1033 core_ctx.base_reachable_time(),
1034 neighbor,
1035 NudEvent::ReachableTime,
1036 );
1037 }
1038
1039 fn enter_stale<I, CC>(
1047 &mut self,
1048 core_ctx: &mut CC,
1049 bindings_ctx: &mut BC,
1050 timers: &mut TimerHeap<I, BC>,
1051 device_id: &CC::DeviceId,
1052 neighbor: SpecifiedAddr<I::Addr>,
1053 link_address: D::Address,
1054 num_entries: usize,
1055 last_gc: &mut Option<BC::Instant>,
1056 ) where
1057 I: Ip,
1058 BC: NudBindingsContext<I, D, CC::DeviceId>,
1059 CC: NudSenderContext<I, D, BC>,
1060 {
1061 let previous =
1064 core::mem::replace(self, DynamicNeighborState::Stale(Stale { link_address }));
1065 let event_dynamic_state = self.to_event_dynamic_state();
1066 debug_assert_ne!(previous.to_event_dynamic_state(), event_dynamic_state);
1067 let event_state = EventState::Dynamic(event_dynamic_state);
1068 bindings_ctx.on_event(Event::changed(device_id, event_state, neighbor, bindings_ctx.now()));
1069 previous.cancel_timer_and_complete_resolution(
1070 core_ctx,
1071 bindings_ctx,
1072 timers,
1073 neighbor,
1074 link_address,
1075 );
1076
1077 timers.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
1081
1082 }
1085
1086 fn resolve_link_addr<I, DeviceId, CC>(
1092 &mut self,
1093 core_ctx: &mut CC,
1094 bindings_ctx: &mut BC,
1095 timers: &mut TimerHeap<I, BC>,
1096 device_id: &DeviceId,
1097 neighbor: SpecifiedAddr<I::Addr>,
1098 ) -> (
1099 LinkResolutionResult<
1100 D::Address,
1101 <<BC as LinkResolutionContext<D>>::Notifier as LinkResolutionNotifier<D>>::Observer,
1102 >,
1103 bool,
1104 )
1105 where
1106 I: Ip,
1107 DeviceId: StrongDeviceIdentifier,
1108 BC: NudBindingsContext<I, D, DeviceId>,
1109 CC: NudConfigContext<I>,
1110 {
1111 match self {
1112 DynamicNeighborState::Incomplete(Incomplete {
1113 notifiers,
1114 transmit_counter: _,
1115 pending_frames: _,
1116 _marker,
1117 }) => {
1118 let (notifier, observer) = BC::Notifier::new();
1119 notifiers.push(notifier);
1120
1121 (LinkResolutionResult::Pending(observer), false)
1122 }
1123 DynamicNeighborState::Stale(entry) => {
1124 let delay @ Delay { link_address } =
1133 entry.enter_delay(bindings_ctx, timers, neighbor);
1134 *self = DynamicNeighborState::Delay(delay);
1135 let event_state = EventState::Dynamic(self.to_event_dynamic_state());
1136 bindings_ctx.on_event(Event::changed(
1137 device_id,
1138 event_state,
1139 neighbor,
1140 bindings_ctx.now(),
1141 ));
1142
1143 (LinkResolutionResult::Resolved(link_address), false)
1144 }
1145 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1146 | DynamicNeighborState::Delay(Delay { link_address })
1147 | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1148 (LinkResolutionResult::Resolved(*link_address), false)
1149 }
1150 DynamicNeighborState::Unreachable(unreachable) => {
1151 let Unreachable { link_address, mode: _ } = unreachable;
1152 let link_address = *link_address;
1153
1154 let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1156 core_ctx,
1157 bindings_ctx,
1158 timers,
1159 neighbor,
1160 );
1161 (LinkResolutionResult::Resolved(link_address), do_multicast_solicit)
1162 }
1163 }
1164 }
1165
1166 fn handle_packet_queued_to_send<I, CC, S>(
1172 &mut self,
1173 core_ctx: &mut CC,
1174 bindings_ctx: &mut BC,
1175 timers: &mut TimerHeap<I, BC>,
1176 device_id: &CC::DeviceId,
1177 neighbor: SpecifiedAddr<I::Addr>,
1178 body: S,
1179 meta: BC::TxMetadata,
1180 ) -> Result<bool, SendFrameError<S>>
1181 where
1182 I: Ip,
1183 BC: NudBindingsContext<I, D, CC::DeviceId>,
1184 CC: NudSenderContext<I, D, BC>,
1185 S: Serializer,
1186 S::Buffer: BufferMut,
1187 {
1188 match self {
1189 DynamicNeighborState::Incomplete(incomplete) => {
1190 incomplete.queue_packet(body, meta).map(|()| false).map_err(|e| e.err_into())
1191 }
1192 DynamicNeighborState::Stale(entry) => {
1199 let delay @ Delay { link_address } =
1207 entry.enter_delay(bindings_ctx, timers, neighbor);
1208 *self = DynamicNeighborState::Delay(delay);
1209 let event_state = EventState::Dynamic(self.to_event_dynamic_state());
1210 bindings_ctx.on_event(Event::changed(
1211 device_id,
1212 event_state,
1213 neighbor,
1214 bindings_ctx.now(),
1215 ));
1216
1217 core_ctx.send_ip_packet_to_neighbor_link_addr(
1218 bindings_ctx,
1219 link_address,
1220 body,
1221 meta,
1222 )?;
1223
1224 Ok(false)
1225 }
1226 DynamicNeighborState::Reachable(Reachable { link_address, last_confirmed_at: _ })
1227 | DynamicNeighborState::Delay(Delay { link_address })
1228 | DynamicNeighborState::Probe(Probe { link_address, transmit_counter: _ }) => {
1229 core_ctx.send_ip_packet_to_neighbor_link_addr(
1230 bindings_ctx,
1231 *link_address,
1232 body,
1233 meta,
1234 )?;
1235
1236 Ok(false)
1237 }
1238 DynamicNeighborState::Unreachable(unreachable) => {
1239 let Unreachable { link_address, mode: _ } = unreachable;
1240 core_ctx.send_ip_packet_to_neighbor_link_addr(
1241 bindings_ctx,
1242 *link_address,
1243 body,
1244 meta,
1245 )?;
1246
1247 let do_multicast_solicit = unreachable.handle_packet_queued_to_send(
1248 core_ctx,
1249 bindings_ctx,
1250 timers,
1251 neighbor,
1252 );
1253 Ok(do_multicast_solicit)
1254 }
1255 }
1256 }
1257
1258 fn handle_probe<I, CC>(
1259 &mut self,
1260 core_ctx: &mut CC,
1261 bindings_ctx: &mut BC,
1262 timers: &mut TimerHeap<I, BC>,
1263 device_id: &CC::DeviceId,
1264 neighbor: SpecifiedAddr<I::Addr>,
1265 link_address: D::Address,
1266 num_entries: usize,
1267 last_gc: &mut Option<BC::Instant>,
1268 ) where
1269 I: Ip,
1270 BC: NudBindingsContext<I, D, CC::DeviceId>,
1271 CC: NudSenderContext<I, D, BC>,
1272 {
1273 let transition_to_stale = match self {
1282 DynamicNeighborState::Incomplete(_) => true,
1283 DynamicNeighborState::Reachable(Reachable {
1284 link_address: current,
1285 last_confirmed_at: _,
1286 })
1287 | DynamicNeighborState::Stale(Stale { link_address: current })
1288 | DynamicNeighborState::Delay(Delay { link_address: current })
1289 | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1290 | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1291 current != &link_address
1292 }
1293 };
1294 if transition_to_stale {
1295 self.enter_stale(
1296 core_ctx,
1297 bindings_ctx,
1298 timers,
1299 device_id,
1300 neighbor,
1301 link_address,
1302 num_entries,
1303 last_gc,
1304 );
1305 }
1306 }
1307
1308 fn handle_confirmation<I, CC>(
1309 &mut self,
1310 core_ctx: &mut CC,
1311 bindings_ctx: &mut BC,
1312 timers: &mut TimerHeap<I, BC>,
1313 device_id: &CC::DeviceId,
1314 neighbor: SpecifiedAddr<I::Addr>,
1315 link_address: Option<D::Address>,
1316 flags: ConfirmationFlags,
1317 num_entries: usize,
1318 last_gc: &mut Option<BC::Instant>,
1319 ) where
1320 I: Ip,
1321 BC: NudBindingsContext<I, D, CC::DeviceId>,
1322 CC: NudSenderContext<I, D, BC>,
1323 {
1324 let ConfirmationFlags { solicited_flag, override_flag } = flags;
1325 enum NewState<A> {
1326 Reachable { link_address: A },
1327 Stale { link_address: A },
1328 }
1329
1330 let new_state = match self {
1331 DynamicNeighborState::Incomplete(Incomplete {
1332 transmit_counter: _,
1333 pending_frames: _,
1334 notifiers: _,
1335 _marker,
1336 }) => {
1337 link_address.map(|link_address| {
1349 if solicited_flag {
1350 NewState::Reachable { link_address }
1351 } else {
1352 NewState::Stale { link_address }
1353 }
1354 })
1355 }
1356 DynamicNeighborState::Reachable(Reachable {
1357 link_address: current,
1358 last_confirmed_at: _,
1359 })
1360 | DynamicNeighborState::Stale(Stale { link_address: current })
1361 | DynamicNeighborState::Delay(Delay { link_address: current })
1362 | DynamicNeighborState::Probe(Probe { link_address: current, transmit_counter: _ })
1363 | DynamicNeighborState::Unreachable(Unreachable { link_address: current, mode: _ }) => {
1364 let updated_link_address = link_address
1383 .and_then(|link_address| (current != &link_address).then_some(link_address));
1384
1385 match (solicited_flag, updated_link_address, override_flag) {
1386 (true, _, true) | (true, None, _) => {
1392 Some(NewState::Reachable { link_address: link_address.unwrap_or(*current) })
1393 }
1394 (_, Some(_), false) => match self {
1404 DynamicNeighborState::Reachable(Reachable {
1406 link_address,
1407 last_confirmed_at: _,
1408 }) => Some(NewState::Stale { link_address: *link_address }),
1409 DynamicNeighborState::Stale(_)
1411 | DynamicNeighborState::Delay(_)
1412 | DynamicNeighborState::Probe(_)
1413 | DynamicNeighborState::Unreachable(_) => None,
1414 DynamicNeighborState::Incomplete(_) => unreachable!(),
1416 },
1417 (false, Some(link_address), true) => Some(NewState::Stale { link_address }),
1423 (false, None, _) => None,
1428 }
1429 }
1430 };
1431 match new_state {
1432 Some(NewState::Reachable { link_address }) => self.enter_reachable(
1433 core_ctx,
1434 bindings_ctx,
1435 timers,
1436 device_id,
1437 neighbor,
1438 link_address,
1439 ),
1440 Some(NewState::Stale { link_address }) => self.enter_stale(
1441 core_ctx,
1442 bindings_ctx,
1443 timers,
1444 device_id,
1445 neighbor,
1446 link_address,
1447 num_entries,
1448 last_gc,
1449 ),
1450 None => {}
1451 }
1452 }
1453}
1454
1455#[cfg(any(test, feature = "testutils"))]
1456pub(crate) mod testutil {
1457 use super::*;
1458
1459 use alloc::sync::Arc;
1460
1461 use netstack3_base::sync::Mutex;
1462 use netstack3_base::testutil::{FakeBindingsCtx, FakeCoreCtx};
1463
1464 pub fn assert_dynamic_neighbor_with_addr<
1466 I: Ip,
1467 D: LinkDevice,
1468 BC: NudBindingsContext<I, D, CC::DeviceId>,
1469 CC: NudContext<I, D, BC>,
1470 >(
1471 core_ctx: &mut CC,
1472 device_id: CC::DeviceId,
1473 neighbor: SpecifiedAddr<I::Addr>,
1474 expected_link_addr: D::Address,
1475 ) {
1476 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1477 assert_matches!(
1478 neighbors.get(&neighbor),
1479 Some(NeighborState::Dynamic(
1480 DynamicNeighborState::Reachable(Reachable{ link_address, last_confirmed_at: _ })
1481 | DynamicNeighborState::Stale(Stale{ link_address })
1482 )) => {
1483 assert_eq!(link_address, &expected_link_addr)
1484 }
1485 )
1486 })
1487 }
1488
1489 pub fn assert_dynamic_neighbor_state<I, D, BC, CC>(
1491 core_ctx: &mut CC,
1492 device_id: CC::DeviceId,
1493 neighbor: SpecifiedAddr<I::Addr>,
1494 expected_state: DynamicNeighborState<D, BC>,
1495 ) where
1496 I: Ip,
1497 D: LinkDevice + PartialEq,
1498 BC: NudBindingsContext<I, D, CC::DeviceId, TxMetadata: PartialEq>,
1499 CC: NudContext<I, D, BC>,
1500 {
1501 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1502 assert_matches!(
1503 neighbors.get(&neighbor),
1504 Some(NeighborState::Dynamic(state)) => {
1505 assert_eq!(state, &expected_state)
1506 }
1507 )
1508 })
1509 }
1510
1511 pub fn assert_neighbor_unknown<
1513 I: Ip,
1514 D: LinkDevice,
1515 BC: NudBindingsContext<I, D, CC::DeviceId>,
1516 CC: NudContext<I, D, BC>,
1517 >(
1518 core_ctx: &mut CC,
1519 device_id: CC::DeviceId,
1520 neighbor: SpecifiedAddr<I::Addr>,
1521 ) {
1522 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, .. }, _config| {
1523 assert_matches!(neighbors.get(&neighbor), None)
1524 })
1525 }
1526
1527 impl<D: LinkDevice, Id, Event: Debug, State, FrameMeta> LinkResolutionContext<D>
1528 for FakeBindingsCtx<Id, Event, State, FrameMeta>
1529 {
1530 type Notifier = FakeLinkResolutionNotifier<D>;
1531 }
1532
1533 #[derive(Debug)]
1535 pub struct FakeLinkResolutionNotifier<D: LinkDevice>(
1536 Arc<Mutex<Option<Result<D::Address, AddressResolutionFailed>>>>,
1537 );
1538
1539 impl<D: LinkDevice> LinkResolutionNotifier<D> for FakeLinkResolutionNotifier<D> {
1540 type Observer = Arc<Mutex<Option<Result<D::Address, AddressResolutionFailed>>>>;
1541
1542 fn new() -> (Self, Self::Observer) {
1543 let inner = Arc::new(Mutex::new(None));
1544 (Self(inner.clone()), inner)
1545 }
1546
1547 fn notify(self, result: Result<D::Address, AddressResolutionFailed>) {
1548 let Self(inner) = self;
1549 let mut inner = inner.lock();
1550 assert_eq!(*inner, None, "resolved link address was set more than once");
1551 *inner = Some(result);
1552 }
1553 }
1554
1555 impl<S, Meta, DeviceId> UseDelegateNudContext for FakeCoreCtx<S, Meta, DeviceId> where
1556 S: UseDelegateNudContext
1557 {
1558 }
1559 impl<I: Ip, S, Meta, DeviceId> DelegateNudContext<I> for FakeCoreCtx<S, Meta, DeviceId>
1560 where
1561 S: DelegateNudContext<I>,
1562 {
1563 type Delegate<T> = S::Delegate<T>;
1564 }
1565}
1566
1567#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1568enum NudEvent {
1569 RetransmitMulticastProbe,
1570 ReachableTime,
1571 DelayFirstProbe,
1572 RetransmitUnicastProbe,
1573}
1574
1575#[derive(GenericOverIp, Copy, Clone, Debug, Eq, PartialEq, Hash)]
1577#[generic_over_ip(I, Ip)]
1578pub struct NudTimerId<I: Ip, L: LinkDevice, D: WeakDeviceIdentifier> {
1579 device_id: D,
1580 timer_type: NudTimerType,
1581 _marker: PhantomData<(I, L)>,
1582}
1583
1584#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
1585enum NudTimerType {
1586 Neighbor,
1587 GarbageCollection,
1588}
1589
1590#[derive(Debug)]
1592struct TimerHeap<I: Ip, BT: TimerBindingsTypes + InstantBindingsTypes> {
1593 gc: BT::Timer,
1594 neighbor: LocalTimerHeap<SpecifiedAddr<I::Addr>, NudEvent, BT>,
1595}
1596
1597impl<I: Ip, BC: TimerContext> TimerHeap<I, BC> {
1598 fn new<
1599 DeviceId: WeakDeviceIdentifier,
1600 L: LinkDevice,
1601 CC: CoreTimerContext<NudTimerId<I, L, DeviceId>, BC>,
1602 >(
1603 bindings_ctx: &mut BC,
1604 device_id: DeviceId,
1605 ) -> Self {
1606 Self {
1607 neighbor: LocalTimerHeap::new_with_context::<_, CC>(
1608 bindings_ctx,
1609 NudTimerId {
1610 device_id: device_id.clone(),
1611 timer_type: NudTimerType::Neighbor,
1612 _marker: PhantomData,
1613 },
1614 ),
1615 gc: CC::new_timer(
1616 bindings_ctx,
1617 NudTimerId {
1618 device_id,
1619 timer_type: NudTimerType::GarbageCollection,
1620 _marker: PhantomData,
1621 },
1622 ),
1623 }
1624 }
1625
1626 fn schedule_neighbor(
1627 &mut self,
1628 bindings_ctx: &mut BC,
1629 after: NonZeroDuration,
1630 neighbor: SpecifiedAddr<I::Addr>,
1631 event: NudEvent,
1632 ) {
1633 let Self { neighbor: heap, gc: _ } = self;
1634 assert_eq!(heap.schedule_after(bindings_ctx, neighbor, event, after.get()), None);
1635 }
1636
1637 fn schedule_neighbor_at(
1638 &mut self,
1639 bindings_ctx: &mut BC,
1640 at: BC::Instant,
1641 neighbor: SpecifiedAddr<I::Addr>,
1642 event: NudEvent,
1643 ) {
1644 let Self { neighbor: heap, gc: _ } = self;
1645 assert_eq!(heap.schedule_instant(bindings_ctx, neighbor, event, at), None);
1646 }
1647
1648 fn cancel_neighbor(
1650 &mut self,
1651 bindings_ctx: &mut BC,
1652 neighbor: SpecifiedAddr<I::Addr>,
1653 ) -> Option<NudEvent> {
1654 let Self { neighbor: heap, gc: _ } = self;
1655 heap.cancel(bindings_ctx, &neighbor).map(|(_instant, v)| v)
1656 }
1657
1658 fn pop_neighbor(
1659 &mut self,
1660 bindings_ctx: &mut BC,
1661 ) -> Option<(SpecifiedAddr<I::Addr>, NudEvent)> {
1662 let Self { neighbor: heap, gc: _ } = self;
1663 heap.pop(bindings_ctx)
1664 }
1665
1666 fn maybe_schedule_gc(
1669 &mut self,
1670 bindings_ctx: &mut BC,
1671 num_entries: usize,
1672 last_gc: &Option<BC::Instant>,
1673 ) {
1674 let Self { gc, neighbor: _ } = self;
1675 if num_entries > MAX_ENTRIES && bindings_ctx.scheduled_instant(gc).is_none() {
1676 let instant = if let Some(last_gc) = last_gc {
1677 last_gc.panicking_add(MIN_GARBAGE_COLLECTION_INTERVAL.get())
1678 } else {
1679 bindings_ctx.now()
1680 };
1681 assert_eq!(bindings_ctx.schedule_timer_instant(instant, gc), None);
1685 }
1686 }
1687}
1688
1689#[derive(Debug)]
1691pub struct NudState<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> {
1692 neighbors: HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>>,
1694 last_gc: Option<BT::Instant>,
1695 timer_heap: TimerHeap<I, BT>,
1696}
1697
1698impl<I: Ip, D: LinkDevice, BT: NudBindingsTypes<D>> NudState<I, D, BT> {
1699 #[cfg(any(test, feature = "testutils"))]
1701 pub fn neighbors(&self) -> &HashMap<SpecifiedAddr<I::Addr>, NeighborState<D, BT>> {
1702 &self.neighbors
1703 }
1704
1705 fn entry_and_timer_heap(
1706 &mut self,
1707 addr: SpecifiedAddr<I::Addr>,
1708 ) -> (Entry<'_, SpecifiedAddr<I::Addr>, NeighborState<D, BT>>, &mut TimerHeap<I, BT>) {
1709 let Self { neighbors, timer_heap, .. } = self;
1710 (neighbors.entry(addr), timer_heap)
1711 }
1712}
1713
1714impl<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D> + TimerContext> NudState<I, D, BC> {
1715 pub fn new<
1717 DeviceId: WeakDeviceIdentifier,
1718 CC: CoreTimerContext<NudTimerId<I, D, DeviceId>, BC>,
1719 >(
1720 bindings_ctx: &mut BC,
1721 device_id: DeviceId,
1722 ) -> Self {
1723 Self {
1724 neighbors: Default::default(),
1725 last_gc: None,
1726 timer_heap: TimerHeap::new::<_, _, CC>(bindings_ctx, device_id),
1727 }
1728 }
1729}
1730
1731pub trait NudBindingsContext<I: Ip, D: LinkDevice, DeviceId>:
1733 TimerContext
1734 + LinkResolutionContext<D>
1735 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1736 + NudBindingsTypes<D>
1737{
1738}
1739
1740impl<
1741 I: Ip,
1742 D: LinkDevice,
1743 DeviceId,
1744 BC: TimerContext
1745 + LinkResolutionContext<D>
1746 + EventContext<Event<D::Address, DeviceId, I, <Self as InstantBindingsTypes>::Instant>>
1747 + NudBindingsTypes<D>,
1748> NudBindingsContext<I, D, DeviceId> for BC
1749{
1750}
1751
1752pub trait NudBindingsTypes<D: LinkDevice>:
1754 LinkResolutionContext<D> + InstantBindingsTypes + TimerBindingsTypes + TxMetadataBindingsTypes
1755{
1756}
1757
1758impl<BT, D> NudBindingsTypes<D> for BT
1759where
1760 D: LinkDevice,
1761 BT: LinkResolutionContext<D>
1762 + InstantBindingsTypes
1763 + TimerBindingsTypes
1764 + TxMetadataBindingsTypes,
1765{
1766}
1767
1768pub trait LinkResolutionContext<D: LinkDevice> {
1770 type Notifier: LinkResolutionNotifier<D>;
1773}
1774
1775pub trait LinkResolutionNotifier<D: LinkDevice>: Debug + Sized + Send {
1778 type Observer;
1781
1782 fn new() -> (Self, Self::Observer);
1784
1785 fn notify(self, result: Result<D::Address, AddressResolutionFailed>);
1788}
1789
1790pub trait NudContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
1792 type ConfigCtx<'a>: NudConfigContext<I>;
1794 type SenderCtx<'a>: NudSenderContext<I, D, BC, DeviceId = Self::DeviceId>;
1796
1797 fn with_nud_state_mut_and_sender_ctx<
1800 O,
1801 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1802 >(
1803 &mut self,
1804 device_id: &Self::DeviceId,
1805 cb: F,
1806 ) -> O;
1807
1808 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1811 &mut self,
1812 device_id: &Self::DeviceId,
1813 cb: F,
1814 ) -> O;
1815
1816 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1818 &mut self,
1819 device_id: &Self::DeviceId,
1820 cb: F,
1821 ) -> O;
1822
1823 fn send_neighbor_solicitation(
1828 &mut self,
1829 bindings_ctx: &mut BC,
1830 device_id: &Self::DeviceId,
1831 lookup_addr: SpecifiedAddr<I::Addr>,
1832 remote_link_addr: Option<D::Address>,
1833 );
1834}
1835
1836pub trait UseDelegateNudContext {}
1839
1840pub trait DelegateNudContext<I: Ip>: UseDelegateNudContext + Sized {
1846 type Delegate<T>: ref_cast::RefCast<From = T>;
1848 fn wrap(&mut self) -> &mut Self::Delegate<Self> {
1850 <Self::Delegate<Self> as ref_cast::RefCast>::ref_cast_mut(self)
1851 }
1852}
1853
1854impl<I, D, BC, CC> NudContext<I, D, BC> for CC
1855where
1856 I: Ip,
1857 D: LinkDevice,
1858 BC: NudBindingsTypes<D>,
1859 CC: DelegateNudContext<I, Delegate<CC>: NudContext<I, D, BC, DeviceId = CC::DeviceId>>
1860 + UseDelegateNudContext
1863 + DeviceIdContext<D>,
1864{
1865 type ConfigCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::ConfigCtx<'a>;
1866 type SenderCtx<'a> = <CC::Delegate<CC> as NudContext<I, D, BC>>::SenderCtx<'a>;
1867 fn with_nud_state_mut_and_sender_ctx<
1868 O,
1869 F: FnOnce(&mut NudState<I, D, BC>, &mut Self::SenderCtx<'_>) -> O,
1870 >(
1871 &mut self,
1872 device_id: &Self::DeviceId,
1873 cb: F,
1874 ) -> O {
1875 self.wrap().with_nud_state_mut_and_sender_ctx(device_id, cb)
1876 }
1877
1878 fn with_nud_state_mut<O, F: FnOnce(&mut NudState<I, D, BC>, &mut Self::ConfigCtx<'_>) -> O>(
1879 &mut self,
1880 device_id: &Self::DeviceId,
1881 cb: F,
1882 ) -> O {
1883 self.wrap().with_nud_state_mut(device_id, cb)
1884 }
1885 fn with_nud_state<O, F: FnOnce(&NudState<I, D, BC>) -> O>(
1886 &mut self,
1887 device_id: &Self::DeviceId,
1888 cb: F,
1889 ) -> O {
1890 self.wrap().with_nud_state(device_id, cb)
1891 }
1892 fn send_neighbor_solicitation(
1893 &mut self,
1894 bindings_ctx: &mut BC,
1895 device_id: &Self::DeviceId,
1896 lookup_addr: SpecifiedAddr<I::Addr>,
1897 remote_link_addr: Option<D::Address>,
1898 ) {
1899 self.wrap().send_neighbor_solicitation(
1900 bindings_ctx,
1901 device_id,
1902 lookup_addr,
1903 remote_link_addr,
1904 )
1905 }
1906}
1907
1908pub trait NudIcmpIpExt: packet_formats::ip::IpExt {
1910 type Metadata;
1913
1914 fn extract_metadata<B: SplitByteSlice>(packet: &Self::Packet<B>) -> Self::Metadata;
1916}
1917
1918impl NudIcmpIpExt for Ipv4 {
1919 type Metadata = (usize, Ipv4FragmentType);
1920
1921 fn extract_metadata<B: SplitByteSlice>(packet: &Ipv4Packet<B>) -> Self::Metadata {
1922 (packet.header_len(), packet.fragment_type())
1923 }
1924}
1925
1926impl NudIcmpIpExt for Ipv6 {
1927 type Metadata = ();
1928
1929 fn extract_metadata<B: SplitByteSlice>(_: &Ipv6Packet<B>) -> () {}
1930}
1931
1932pub trait NudIcmpContext<I: NudIcmpIpExt, D: LinkDevice, BC>: DeviceIdContext<D> {
1935 fn send_icmp_dest_unreachable(
1942 &mut self,
1943 bindings_ctx: &mut BC,
1944 frame: Buf<Vec<u8>>,
1945 device_id: Option<&Self::DeviceId>,
1946 original_src_ip: SocketIpAddr<I::Addr>,
1947 original_dst_ip: SocketIpAddr<I::Addr>,
1948 metadata: I::Metadata,
1949 );
1950}
1951
1952#[derive(Clone, Debug)]
1954pub struct NudUserConfig {
1955 pub max_unicast_solicitations: NonZeroU16,
1960 pub max_multicast_solicitations: NonZeroU16,
1965 pub base_reachable_time: NonZeroDuration,
1971}
1972
1973impl Default for NudUserConfig {
1974 fn default() -> Self {
1975 NudUserConfig {
1976 max_unicast_solicitations: DEFAULT_MAX_UNICAST_SOLICIT,
1977 max_multicast_solicitations: DEFAULT_MAX_MULTICAST_SOLICIT,
1978 base_reachable_time: DEFAULT_BASE_REACHABLE_TIME,
1979 }
1980 }
1981}
1982
1983#[derive(Clone, Debug, Eq, PartialEq, Default)]
1987pub struct NudUserConfigUpdate {
1988 pub max_unicast_solicitations: Option<NonZeroU16>,
1991 pub max_multicast_solicitations: Option<NonZeroU16>,
1994 pub base_reachable_time: Option<NonZeroDuration>,
2000}
2001
2002impl NudUserConfigUpdate {
2003 pub fn apply_and_take_previous(mut self, config: &mut NudUserConfig) -> Self {
2006 fn swap_if_set<T>(opt: &mut Option<T>, target: &mut T) {
2007 if let Some(opt) = opt.as_mut() {
2008 core::mem::swap(opt, target)
2009 }
2010 }
2011 let Self { max_unicast_solicitations, max_multicast_solicitations, base_reachable_time } =
2012 &mut self;
2013 swap_if_set(max_unicast_solicitations, &mut config.max_unicast_solicitations);
2014 swap_if_set(max_multicast_solicitations, &mut config.max_multicast_solicitations);
2015 swap_if_set(base_reachable_time, &mut config.base_reachable_time);
2016
2017 self
2018 }
2019}
2020
2021pub trait NudConfigContext<I: Ip> {
2024 fn retransmit_timeout(&mut self) -> NonZeroDuration;
2031
2032 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O;
2034
2035 fn max_unicast_solicit(&mut self) -> NonZeroU16 {
2037 self.with_nud_user_config(|NudUserConfig { max_unicast_solicitations, .. }| {
2038 *max_unicast_solicitations
2039 })
2040 }
2041
2042 fn max_multicast_solicit(&mut self) -> NonZeroU16 {
2044 self.with_nud_user_config(|NudUserConfig { max_multicast_solicitations, .. }| {
2045 *max_multicast_solicitations
2046 })
2047 }
2048
2049 fn base_reachable_time(&mut self) -> NonZeroDuration {
2052 self.with_nud_user_config(|NudUserConfig { base_reachable_time, .. }| *base_reachable_time)
2053 }
2054}
2055
2056pub trait NudSenderContext<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>:
2059 NudConfigContext<I> + DeviceIdContext<D>
2060{
2061 fn send_ip_packet_to_neighbor_link_addr<S>(
2063 &mut self,
2064 bindings_ctx: &mut BC,
2065 neighbor_link_addr: D::Address,
2066 body: S,
2067 meta: BC::TxMetadata,
2068 ) -> Result<(), SendFrameError<S>>
2069 where
2070 S: Serializer,
2071 S::Buffer: BufferMut;
2072}
2073
2074pub trait NudIpHandler<I: Ip, BC>: DeviceIdContext<AnyDevice> {
2076 fn handle_neighbor_probe(
2081 &mut self,
2082 bindings_ctx: &mut BC,
2083 device_id: &Self::DeviceId,
2084 neighbor: SpecifiedAddr<I::Addr>,
2085 link_addr: &[u8],
2086 );
2087
2088 fn handle_neighbor_confirmation(
2092 &mut self,
2093 bindings_ctx: &mut BC,
2094 device_id: &Self::DeviceId,
2095 neighbor: SpecifiedAddr<I::Addr>,
2096 link_addr: Option<&[u8]>,
2097 flags: ConfirmationFlags,
2098 );
2099
2100 fn flush_neighbor_table(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2102}
2103
2104#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2106pub enum LinkResolutionResult<A: LinkAddress, Observer> {
2107 Resolved(A),
2109 Pending(Observer),
2111}
2112
2113pub trait NudHandler<I: Ip, D: LinkDevice, BC: NudBindingsTypes<D>>: DeviceIdContext<D> {
2115 fn handle_neighbor_update(
2118 &mut self,
2119 bindings_ctx: &mut BC,
2120 device_id: &Self::DeviceId,
2121 neighbor: SpecifiedAddr<I::Addr>,
2126 source: DynamicNeighborUpdateSource<D::Address>,
2127 );
2128
2129 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
2131
2132 fn send_ip_packet_to_neighbor<S>(
2137 &mut self,
2138 bindings_ctx: &mut BC,
2139 device_id: &Self::DeviceId,
2140 neighbor: SpecifiedAddr<I::Addr>,
2141 body: S,
2142 meta: BC::TxMetadata,
2143 ) -> Result<(), SendFrameError<S>>
2144 where
2145 S: Serializer,
2146 S::Buffer: BufferMut;
2147}
2148
2149enum TransmitProbe<A> {
2150 Multicast,
2151 Unicast(A),
2152}
2153
2154impl<
2155 I: NudIcmpIpExt,
2156 D: LinkDevice,
2157 BC: NudBindingsContext<I, D, CC::DeviceId>,
2158 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2159> HandleableTimer<CC, BC> for NudTimerId<I, D, CC::WeakDeviceId>
2160{
2161 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
2162 let Self { device_id, timer_type, _marker: PhantomData } = self;
2163 let Some(device_id) = device_id.upgrade() else {
2164 return;
2165 };
2166 match timer_type {
2167 NudTimerType::Neighbor => handle_neighbor_timer(core_ctx, bindings_ctx, device_id),
2168 NudTimerType::GarbageCollection => collect_garbage(core_ctx, bindings_ctx, device_id),
2169 }
2170 }
2171}
2172
2173fn handle_neighbor_timer<I, D, CC, BC>(
2174 core_ctx: &mut CC,
2175 bindings_ctx: &mut BC,
2176 device_id: CC::DeviceId,
2177) where
2178 I: NudIcmpIpExt,
2179 D: LinkDevice,
2180 BC: NudBindingsContext<I, D, CC::DeviceId>,
2181 CC: NudContext<I, D, BC> + NudIcmpContext<I, D, BC> + CounterContext<NudCounters<I>>,
2182{
2183 enum Action<L, A, M> {
2184 TransmitProbe { probe: TransmitProbe<L>, to: A },
2185 SendIcmpDestUnreachable(VecDeque<(Buf<Vec<u8>>, M)>),
2186 }
2187 let action = core_ctx.with_nud_state_mut(
2188 &device_id,
2189 |NudState { neighbors, last_gc, timer_heap }, core_ctx| {
2190 let (lookup_addr, event) = timer_heap.pop_neighbor(bindings_ctx)?;
2191 let num_entries = neighbors.len();
2192 let mut entry = match neighbors.entry(lookup_addr) {
2193 Entry::Occupied(entry) => entry,
2194 Entry::Vacant(_) => panic!("timer fired for invalid entry"),
2195 };
2196
2197 match entry.get_mut() {
2198 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)) => {
2199 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2200
2201 if incomplete.schedule_timer_if_should_retransmit(
2202 core_ctx,
2203 bindings_ctx,
2204 timer_heap,
2205 lookup_addr,
2206 ) {
2207 Some(Action::TransmitProbe {
2208 probe: TransmitProbe::Multicast,
2209 to: lookup_addr,
2210 })
2211 } else {
2212 debug!("neighbor resolution failed for {lookup_addr}; removing entry");
2220 let Incomplete {
2221 transmit_counter: _,
2222 ref mut pending_frames,
2223 notifiers: _,
2224 _marker,
2225 } = assert_matches!(
2226 entry.remove(),
2227 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete))
2228 => incomplete
2229 );
2230 let pending_frames = core::mem::take(pending_frames);
2231 bindings_ctx.on_event(Event::removed(
2232 &device_id,
2233 lookup_addr,
2234 bindings_ctx.now(),
2235 ));
2236 Some(Action::SendIcmpDestUnreachable(pending_frames))
2237 }
2238 }
2239 NeighborState::Dynamic(DynamicNeighborState::Probe(probe)) => {
2240 assert_eq!(event, NudEvent::RetransmitUnicastProbe);
2241
2242 let Probe { link_address, transmit_counter: _ } = probe;
2243 let link_address = *link_address;
2244 if probe.schedule_timer_if_should_retransmit(
2245 core_ctx,
2246 bindings_ctx,
2247 timer_heap,
2248 lookup_addr,
2249 ) {
2250 Some(Action::TransmitProbe {
2251 probe: TransmitProbe::Unicast(link_address),
2252 to: lookup_addr,
2253 })
2254 } else {
2255 let unreachable =
2256 probe.enter_unreachable(bindings_ctx, timer_heap, num_entries, last_gc);
2257 *entry.get_mut() =
2258 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable));
2259 let event_state = entry.get_mut().to_event_state();
2260 let event = Event::changed(
2261 &device_id,
2262 event_state,
2263 lookup_addr,
2264 bindings_ctx.now(),
2265 );
2266 bindings_ctx.on_event(event);
2267 None
2268 }
2269 }
2270 NeighborState::Dynamic(DynamicNeighborState::Unreachable(unreachable)) => {
2271 assert_eq!(event, NudEvent::RetransmitMulticastProbe);
2272 unreachable
2273 .handle_timer(core_ctx, bindings_ctx, timer_heap, &device_id, lookup_addr)
2274 .map(|probe| Action::TransmitProbe { probe, to: lookup_addr })
2275 }
2276 NeighborState::Dynamic(DynamicNeighborState::Reachable(Reachable {
2277 link_address,
2278 last_confirmed_at,
2279 })) => {
2280 assert_eq!(event, NudEvent::ReachableTime);
2281 let link_address = *link_address;
2282
2283 let expiration =
2284 last_confirmed_at.saturating_add(core_ctx.base_reachable_time().get());
2285 if expiration > bindings_ctx.now() {
2286 timer_heap.schedule_neighbor_at(
2287 bindings_ctx,
2288 expiration,
2289 lookup_addr,
2290 NudEvent::ReachableTime,
2291 );
2292 } else {
2293 *entry.get_mut() =
2301 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2302 link_address,
2303 }));
2304 let event_state = entry.get_mut().to_event_state();
2305 let event = Event::changed(
2306 &device_id,
2307 event_state,
2308 lookup_addr,
2309 bindings_ctx.now(),
2310 );
2311 bindings_ctx.on_event(event);
2312
2313 timer_heap.maybe_schedule_gc(bindings_ctx, num_entries, last_gc);
2317 }
2318
2319 None
2320 }
2321 NeighborState::Dynamic(DynamicNeighborState::Delay(delay)) => {
2322 assert_eq!(event, NudEvent::DelayFirstProbe);
2323
2324 let probe @ Probe { link_address, transmit_counter: _ } =
2331 delay.enter_probe(core_ctx, bindings_ctx, timer_heap, lookup_addr);
2332 *entry.get_mut() = NeighborState::Dynamic(DynamicNeighborState::Probe(probe));
2333 let event_state = entry.get_mut().to_event_state();
2334 bindings_ctx.on_event(Event::changed(
2335 &device_id,
2336 event_state,
2337 lookup_addr,
2338 bindings_ctx.now(),
2339 ));
2340
2341 Some(Action::TransmitProbe {
2342 probe: TransmitProbe::Unicast(link_address),
2343 to: lookup_addr,
2344 })
2345 }
2346 state @ (NeighborState::Static(_)
2347 | NeighborState::Dynamic(DynamicNeighborState::Stale(_))) => {
2348 panic!("timer unexpectedly fired in state {state:?}")
2349 }
2350 }
2351 },
2352 );
2353
2354 match action {
2355 Some(Action::SendIcmpDestUnreachable(mut pending_frames)) => {
2356 for (mut frame, meta) in pending_frames.drain(..) {
2357 core::mem::drop(meta);
2360
2361 let Some((packet, original_src_ip, original_dst_ip)) = frame
2365 .parse_mut::<I::Packet<_>>()
2366 .map_err(|e| {
2367 warn!("not sending ICMP dest unreachable due to parsing error: {:?}", e);
2368 })
2369 .ok()
2370 .and_then(|packet| {
2371 let original_src_ip = SocketIpAddr::new(packet.src_ip())?;
2372 let original_dst_ip = SocketIpAddr::new(packet.dst_ip())?;
2373 Some((packet, original_src_ip, original_dst_ip))
2374 })
2375 .or_else(|| {
2376 core_ctx.counters().icmp_dest_unreachable_dropped.increment();
2377 None
2378 })
2379 else {
2380 continue;
2381 };
2382 let header_metadata = I::extract_metadata(&packet);
2383 let metadata = packet.parse_metadata();
2384 core::mem::drop(packet);
2385 frame.undo_parse(metadata);
2386 core_ctx.send_icmp_dest_unreachable(
2387 bindings_ctx,
2388 frame,
2389 original_src_ip.as_ref().must_have_zone().then_some(&device_id),
2401 original_src_ip,
2402 original_dst_ip,
2403 header_metadata,
2404 );
2405 }
2406 }
2407 Some(Action::TransmitProbe { probe, to }) => {
2408 let remote_link_addr = match probe {
2409 TransmitProbe::Multicast => None,
2410 TransmitProbe::Unicast(link_addr) => Some(link_addr),
2411 };
2412 core_ctx.send_neighbor_solicitation(bindings_ctx, &device_id, to, remote_link_addr);
2413 }
2414 None => {}
2415 }
2416}
2417
2418impl<I: Ip, D: LinkDevice, BC: NudBindingsContext<I, D, CC::DeviceId>, CC: NudContext<I, D, BC>>
2419 NudHandler<I, D, BC> for CC
2420{
2421 fn handle_neighbor_update(
2422 &mut self,
2423 bindings_ctx: &mut BC,
2424 device_id: &CC::DeviceId,
2425 neighbor: SpecifiedAddr<I::Addr>,
2426 source: DynamicNeighborUpdateSource<D::Address>,
2427 ) {
2428 debug!("received neighbor {:?} from {}", source, neighbor);
2429 self.with_nud_state_mut_and_sender_ctx(
2430 device_id,
2431 |NudState { neighbors, last_gc, timer_heap }, core_ctx| {
2432 let num_entries = neighbors.len();
2433 match neighbors.entry(neighbor) {
2434 Entry::Vacant(e) => match source {
2435 DynamicNeighborUpdateSource::Probe { link_address } => {
2436 insert_new_entry(
2444 bindings_ctx,
2445 device_id,
2446 e,
2447 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
2448 link_address,
2449 })),
2450 );
2451
2452 timer_heap.maybe_schedule_gc(bindings_ctx, neighbors.len(), last_gc);
2455 }
2456 DynamicNeighborUpdateSource::Confirmation { .. } => {}
2465 },
2466 Entry::Occupied(e) => match e.into_mut() {
2467 NeighborState::Dynamic(e) => match source {
2468 DynamicNeighborUpdateSource::Probe { link_address } => e.handle_probe(
2469 core_ctx,
2470 bindings_ctx,
2471 timer_heap,
2472 device_id,
2473 neighbor,
2474 link_address,
2475 num_entries,
2476 last_gc,
2477 ),
2478 DynamicNeighborUpdateSource::Confirmation { link_address, flags } => e
2479 .handle_confirmation(
2480 core_ctx,
2481 bindings_ctx,
2482 timer_heap,
2483 device_id,
2484 neighbor,
2485 link_address,
2486 flags,
2487 num_entries,
2488 last_gc,
2489 ),
2490 },
2491 NeighborState::Static(_) => {}
2492 },
2493 }
2494 },
2495 );
2496 }
2497
2498 fn flush(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId) {
2499 self.with_nud_state_mut(
2500 device_id,
2501 |NudState { neighbors, last_gc: _, timer_heap }, _config| {
2502 neighbors.drain().for_each(|(neighbor, state)| {
2503 match state {
2504 NeighborState::Dynamic(mut entry) => {
2505 entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
2506 }
2507 NeighborState::Static(_) => {}
2508 }
2509 bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
2510 });
2511 },
2512 );
2513 }
2514
2515 fn send_ip_packet_to_neighbor<S>(
2516 &mut self,
2517 bindings_ctx: &mut BC,
2518 device_id: &Self::DeviceId,
2519 lookup_addr: SpecifiedAddr<I::Addr>,
2520 body: S,
2521 meta: BC::TxMetadata,
2522 ) -> Result<(), SendFrameError<S>>
2523 where
2524 S: Serializer,
2525 S::Buffer: BufferMut,
2526 {
2527 let do_multicast_solicit = self.with_nud_state_mut_and_sender_ctx(
2528 device_id,
2529 |state, core_ctx| -> Result<_, SendFrameError<S>> {
2530 let (entry, timer_heap) = state.entry_and_timer_heap(lookup_addr);
2531 match entry {
2532 Entry::Vacant(e) => {
2533 let incomplete = Incomplete::new_with_packet(
2534 core_ctx,
2535 bindings_ctx,
2536 timer_heap,
2537 lookup_addr,
2538 body,
2539 meta,
2540 )
2541 .map_err(|e| e.err_into())?;
2542 insert_new_entry(
2543 bindings_ctx,
2544 device_id,
2545 e,
2546 NeighborState::Dynamic(DynamicNeighborState::Incomplete(incomplete)),
2547 );
2548 Ok(true)
2549 }
2550 Entry::Occupied(e) => {
2551 match e.into_mut() {
2552 NeighborState::Static(link_address) => {
2553 core_ctx.send_ip_packet_to_neighbor_link_addr(
2561 bindings_ctx,
2562 *link_address,
2563 body,
2564 meta,
2565 )?;
2566
2567 Ok(false)
2568 }
2569 NeighborState::Dynamic(e) => {
2570 let do_multicast_solicit = e.handle_packet_queued_to_send(
2571 core_ctx,
2572 bindings_ctx,
2573 timer_heap,
2574 device_id,
2575 lookup_addr,
2576 body,
2577 meta,
2578 )?;
2579
2580 Ok(do_multicast_solicit)
2581 }
2582 }
2583 }
2584 }
2585 },
2586 )?;
2587
2588 if do_multicast_solicit {
2589 self.send_neighbor_solicitation(
2590 bindings_ctx,
2591 &device_id,
2592 lookup_addr,
2593 None,
2594 );
2595 }
2596
2597 Ok(())
2598 }
2599}
2600
2601fn insert_new_entry<
2602 I: Ip,
2603 D: LinkDevice,
2604 DeviceId: DeviceIdentifier,
2605 BC: NudBindingsContext<I, D, DeviceId>,
2606>(
2607 bindings_ctx: &mut BC,
2608 device_id: &DeviceId,
2609 vacant: hash_map::VacantEntry<'_, SpecifiedAddr<I::Addr>, NeighborState<D, BC>>,
2610 entry: NeighborState<D, BC>,
2611) {
2612 let lookup_addr = *vacant.key();
2613 let state = vacant.insert(entry);
2614 let event = Event::added(device_id, state.to_event_state(), lookup_addr, bindings_ctx.now());
2615 bindings_ctx.on_event(event);
2616}
2617
2618pub fn confirm_reachable<I, D, CC, BC>(
2621 core_ctx: &mut CC,
2622 bindings_ctx: &mut BC,
2623 device_id: &CC::DeviceId,
2624 neighbor: SpecifiedAddr<I::Addr>,
2625) where
2626 I: Ip,
2627 D: LinkDevice,
2628 BC: NudBindingsContext<I, D, CC::DeviceId>,
2629 CC: NudContext<I, D, BC>,
2630{
2631 core_ctx.with_nud_state_mut_and_sender_ctx(
2632 device_id,
2633 |NudState { neighbors, last_gc: _, timer_heap }, core_ctx| {
2634 match neighbors.entry(neighbor) {
2635 Entry::Vacant(_) => {
2636 debug!(
2637 "got an upper-layer confirmation for non-existent neighbor entry {}",
2638 neighbor
2639 );
2640 }
2641 Entry::Occupied(e) => match e.into_mut() {
2642 NeighborState::Static(_) => {}
2643 NeighborState::Dynamic(e) => {
2644 let link_address = match e {
2654 DynamicNeighborState::Incomplete(_) => return,
2655 DynamicNeighborState::Reachable(Reachable {
2656 link_address,
2657 last_confirmed_at: _,
2658 })
2659 | DynamicNeighborState::Stale(Stale { link_address })
2660 | DynamicNeighborState::Delay(Delay { link_address })
2661 | DynamicNeighborState::Probe(Probe {
2662 link_address,
2663 transmit_counter: _,
2664 })
2665 | DynamicNeighborState::Unreachable(Unreachable {
2666 link_address,
2667 mode: _,
2668 }) => *link_address,
2669 };
2670 e.enter_reachable(
2671 core_ctx,
2672 bindings_ctx,
2673 timer_heap,
2674 device_id,
2675 neighbor,
2676 link_address,
2677 );
2678 }
2679 },
2680 }
2681 },
2682 );
2683}
2684
2685fn collect_garbage<I, D, CC, BC>(core_ctx: &mut CC, bindings_ctx: &mut BC, device_id: CC::DeviceId)
2696where
2697 I: Ip,
2698 D: LinkDevice,
2699 BC: NudBindingsContext<I, D, CC::DeviceId>,
2700 CC: NudContext<I, D, BC>,
2701{
2702 core_ctx.with_nud_state_mut(&device_id, |NudState { neighbors, last_gc, timer_heap }, _| {
2703 let max_to_remove = neighbors.len().saturating_sub(MAX_ENTRIES);
2704 if max_to_remove == 0 {
2705 return;
2706 }
2707
2708 *last_gc = Some(bindings_ctx.now());
2709
2710 fn gc_priority<D: LinkDevice, BT: NudBindingsTypes<D>>(
2719 state: &DynamicNeighborState<D, BT>,
2720 ) -> usize {
2721 match state {
2722 DynamicNeighborState::Incomplete(_)
2723 | DynamicNeighborState::Reachable(_)
2724 | DynamicNeighborState::Delay(_)
2725 | DynamicNeighborState::Probe(_) => unreachable!(
2726 "the netstack should only ever discard STALE or UNREACHABLE entries; \
2727 found {:?}",
2728 state,
2729 ),
2730 DynamicNeighborState::Stale(_) => 0,
2731 DynamicNeighborState::Unreachable(Unreachable {
2732 link_address: _,
2733 mode: UnreachableMode::Backoff { probes_sent: _, packet_sent: _ },
2734 }) => 1,
2735 DynamicNeighborState::Unreachable(Unreachable {
2736 link_address: _,
2737 mode: UnreachableMode::WaitingForPacketSend,
2738 }) => 2,
2739 }
2740 }
2741
2742 struct SortEntry<'a, K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> {
2743 key: K,
2744 state: &'a mut DynamicNeighborState<D, BT>,
2745 }
2746
2747 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialEq for SortEntry<'_, K, D, BT> {
2748 fn eq(&self, other: &Self) -> bool {
2749 self.key == other.key && gc_priority(self.state) == gc_priority(other.state)
2750 }
2751 }
2752 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Eq for SortEntry<'_, K, D, BT> {}
2753 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> Ord for SortEntry<'_, K, D, BT> {
2754 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2755 gc_priority(self.state).cmp(&gc_priority(other.state)).reverse()
2759 }
2760 }
2761 impl<K: Eq, D: LinkDevice, BT: NudBindingsTypes<D>> PartialOrd for SortEntry<'_, K, D, BT> {
2762 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
2763 Some(self.cmp(&other))
2764 }
2765 }
2766
2767 let mut entries_to_remove = BinaryHeap::with_capacity(max_to_remove);
2768 for (ip, neighbor) in neighbors.iter_mut() {
2769 match neighbor {
2770 NeighborState::Static(_) => {
2771 continue;
2773 }
2774 NeighborState::Dynamic(state) => {
2775 match state {
2776 DynamicNeighborState::Incomplete(_)
2777 | DynamicNeighborState::Reachable(_)
2778 | DynamicNeighborState::Delay(_)
2779 | DynamicNeighborState::Probe(_) => {
2780 continue;
2782 }
2783 DynamicNeighborState::Stale(_) | DynamicNeighborState::Unreachable(_) => {
2784 if entries_to_remove.len() < max_to_remove {
2786 entries_to_remove.push(SortEntry { key: ip, state });
2787 continue;
2788 }
2789 let minimum = entries_to_remove
2793 .peek()
2794 .expect("heap should have at least 1 entry");
2795 let candidate = SortEntry { key: ip, state };
2796 if &candidate > minimum {
2797 let _: SortEntry<'_, _, _, _> = entries_to_remove.pop().unwrap();
2798 entries_to_remove.push(candidate);
2799 }
2800 }
2801 }
2802 }
2803 }
2804 }
2805
2806 let entries_to_remove = entries_to_remove
2807 .into_iter()
2808 .map(|SortEntry { key: neighbor, state }| {
2809 state.cancel_timer(bindings_ctx, timer_heap, *neighbor);
2810 *neighbor
2811 })
2812 .collect::<Vec<_>>();
2813
2814 for neighbor in entries_to_remove {
2815 assert_matches!(neighbors.remove(&neighbor), Some(_));
2816 bindings_ctx.on_event(Event::removed(&device_id, neighbor, bindings_ctx.now()));
2817 }
2818 })
2819}
2820
2821#[cfg(test)]
2822mod tests {
2823 use alloc::vec;
2824
2825 use ip_test_macro::ip_test;
2826 use net_declare::{net_ip_v4, net_ip_v6};
2827 use net_types::ip::{Ipv4Addr, Ipv6Addr};
2828 use netstack3_base::testutil::{
2829 FakeBindingsCtx, FakeCoreCtx, FakeInstant, FakeLinkAddress, FakeLinkDevice,
2830 FakeLinkDeviceId, FakeTimerCtxExt as _, FakeTxMetadata, FakeWeakDeviceId,
2831 };
2832 use netstack3_base::{
2833 CtxPair, InstantContext, IntoCoreTimerCtx, SendFrameContext as _, SendFrameErrorReason,
2834 };
2835 use netstack3_hashmap::HashSet;
2836 use test_case::test_case;
2837
2838 use super::*;
2839 use crate::internal::device::nud::api::NeighborApi;
2840
2841 struct FakeNudContext<I: Ip, D: LinkDevice> {
2842 state: NudState<I, D, FakeBindingsCtxImpl<I>>,
2843 counters: NudCounters<I>,
2844 }
2845
2846 struct FakeConfigContext {
2847 retrans_timer: NonZeroDuration,
2848 nud_config: NudUserConfig,
2849 }
2850
2851 struct FakeCoreCtxImpl<I: Ip> {
2852 nud: FakeNudContext<I, FakeLinkDevice>,
2853 inner: FakeInnerCtxImpl<I>,
2854 }
2855
2856 type FakeInnerCtxImpl<I> =
2857 FakeCoreCtx<FakeConfigContext, FakeNudMessageMeta<I>, FakeLinkDeviceId>;
2858
2859 #[derive(Debug, PartialEq, Eq)]
2860 enum FakeNudMessageMeta<I: Ip> {
2861 NeighborSolicitation {
2862 lookup_addr: SpecifiedAddr<I::Addr>,
2863 remote_link_addr: Option<FakeLinkAddress>,
2864 },
2865 IpFrame {
2866 dst_link_address: FakeLinkAddress,
2867 },
2868 IcmpDestUnreachable,
2869 }
2870
2871 type FakeBindingsCtxImpl<I> = FakeBindingsCtx<
2872 NudTimerId<I, FakeLinkDevice, FakeWeakDeviceId<FakeLinkDeviceId>>,
2873 Event<FakeLinkAddress, FakeLinkDeviceId, I, FakeInstant>,
2874 (),
2875 (),
2876 >;
2877
2878 impl<I: Ip> FakeCoreCtxImpl<I> {
2879 fn new(bindings_ctx: &mut FakeBindingsCtxImpl<I>) -> Self {
2880 Self {
2881 nud: {
2882 FakeNudContext {
2883 state: NudState::new::<_, IntoCoreTimerCtx>(
2884 bindings_ctx,
2885 FakeWeakDeviceId(FakeLinkDeviceId),
2886 ),
2887 counters: Default::default(),
2888 }
2889 },
2890 inner: FakeInnerCtxImpl::with_state(FakeConfigContext {
2891 retrans_timer: ONE_SECOND,
2892 nud_config: NudUserConfig {
2896 max_unicast_solicitations: NonZeroU16::new(4).unwrap(),
2897 max_multicast_solicitations: NonZeroU16::new(5).unwrap(),
2898 base_reachable_time: NonZeroDuration::from_secs(23).unwrap(),
2899 },
2900 }),
2901 }
2902 }
2903 }
2904
2905 fn new_context<I: Ip>() -> CtxPair<FakeCoreCtxImpl<I>, FakeBindingsCtxImpl<I>> {
2906 CtxPair::with_default_bindings_ctx(|bindings_ctx| FakeCoreCtxImpl::<I>::new(bindings_ctx))
2907 }
2908
2909 impl<I: Ip> DeviceIdContext<FakeLinkDevice> for FakeCoreCtxImpl<I> {
2910 type DeviceId = FakeLinkDeviceId;
2911 type WeakDeviceId = FakeWeakDeviceId<FakeLinkDeviceId>;
2912 }
2913
2914 impl<I: Ip> NudContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeCoreCtxImpl<I> {
2915 type ConfigCtx<'a> = FakeConfigContext;
2916
2917 type SenderCtx<'a> = FakeInnerCtxImpl<I>;
2918
2919 fn with_nud_state_mut_and_sender_ctx<
2920 O,
2921 F: FnOnce(
2922 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
2923 &mut Self::SenderCtx<'_>,
2924 ) -> O,
2925 >(
2926 &mut self,
2927 _device_id: &Self::DeviceId,
2928 cb: F,
2929 ) -> O {
2930 cb(&mut self.nud.state, &mut self.inner)
2931 }
2932
2933 fn with_nud_state_mut<
2934 O,
2935 F: FnOnce(
2936 &mut NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>,
2937 &mut Self::ConfigCtx<'_>,
2938 ) -> O,
2939 >(
2940 &mut self,
2941 &FakeLinkDeviceId: &FakeLinkDeviceId,
2942 cb: F,
2943 ) -> O {
2944 cb(&mut self.nud.state, &mut self.inner.state)
2945 }
2946
2947 fn with_nud_state<
2948 O,
2949 F: FnOnce(&NudState<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>) -> O,
2950 >(
2951 &mut self,
2952 &FakeLinkDeviceId: &FakeLinkDeviceId,
2953 cb: F,
2954 ) -> O {
2955 cb(&self.nud.state)
2956 }
2957
2958 fn send_neighbor_solicitation(
2959 &mut self,
2960 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
2961 &FakeLinkDeviceId: &FakeLinkDeviceId,
2962 lookup_addr: SpecifiedAddr<I::Addr>,
2963 remote_link_addr: Option<FakeLinkAddress>,
2964 ) {
2965 self.inner
2966 .send_frame(
2967 bindings_ctx,
2968 FakeNudMessageMeta::NeighborSolicitation { lookup_addr, remote_link_addr },
2969 Buf::new(Vec::new(), ..),
2970 )
2971 .unwrap()
2972 }
2973 }
2974
2975 impl<I: NudIcmpIpExt> NudIcmpContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>>
2976 for FakeCoreCtxImpl<I>
2977 {
2978 fn send_icmp_dest_unreachable(
2979 &mut self,
2980 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
2981 frame: Buf<Vec<u8>>,
2982 _device_id: Option<&Self::DeviceId>,
2983 _original_src_ip: SocketIpAddr<I::Addr>,
2984 _original_dst_ip: SocketIpAddr<I::Addr>,
2985 _header_len: I::Metadata,
2986 ) {
2987 self.inner
2988 .send_frame(bindings_ctx, FakeNudMessageMeta::IcmpDestUnreachable, frame)
2989 .unwrap()
2990 }
2991 }
2992
2993 impl<I: Ip> CounterContext<NudCounters<I>> for FakeCoreCtxImpl<I> {
2994 fn counters(&self) -> &NudCounters<I> {
2995 &self.nud.counters
2996 }
2997 }
2998
2999 impl<I: Ip> NudConfigContext<I> for FakeConfigContext {
3000 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3001 self.retrans_timer
3002 }
3003
3004 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3005 cb(&self.nud_config)
3006 }
3007 }
3008
3009 impl<I: Ip> NudSenderContext<I, FakeLinkDevice, FakeBindingsCtxImpl<I>> for FakeInnerCtxImpl<I> {
3010 fn send_ip_packet_to_neighbor_link_addr<S>(
3011 &mut self,
3012 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3013 dst_link_address: FakeLinkAddress,
3014 body: S,
3015 _tx_meta: FakeTxMetadata,
3016 ) -> Result<(), SendFrameError<S>>
3017 where
3018 S: Serializer,
3019 S::Buffer: BufferMut,
3020 {
3021 self.send_frame(bindings_ctx, FakeNudMessageMeta::IpFrame { dst_link_address }, body)
3022 }
3023 }
3024
3025 impl<I: Ip> NudConfigContext<I> for FakeInnerCtxImpl<I> {
3026 fn retransmit_timeout(&mut self) -> NonZeroDuration {
3027 <FakeConfigContext as NudConfigContext<I>>::retransmit_timeout(&mut self.state)
3028 }
3029
3030 fn with_nud_user_config<O, F: FnOnce(&NudUserConfig) -> O>(&mut self, cb: F) -> O {
3031 <FakeConfigContext as NudConfigContext<I>>::with_nud_user_config(&mut self.state, cb)
3032 }
3033 }
3034
3035 const ONE_SECOND: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
3036
3037 #[track_caller]
3038 fn check_lookup_has<I: Ip>(
3039 core_ctx: &mut FakeCoreCtxImpl<I>,
3040 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3041 lookup_addr: SpecifiedAddr<I::Addr>,
3042 expected_link_addr: FakeLinkAddress,
3043 ) {
3044 let entry = assert_matches!(
3045 core_ctx.nud.state.neighbors.get(&lookup_addr),
3046 Some(entry @ (
3047 NeighborState::Dynamic(
3048 DynamicNeighborState::Reachable (Reachable { link_address, last_confirmed_at: _ })
3049 | DynamicNeighborState::Stale (Stale { link_address })
3050 | DynamicNeighborState::Delay (Delay { link_address })
3051 | DynamicNeighborState::Probe (Probe { link_address, transmit_counter: _ })
3052 | DynamicNeighborState::Unreachable (Unreachable { link_address, mode: _ })
3053 )
3054 | NeighborState::Static(link_address)
3055 )) => {
3056 assert_eq!(link_address, &expected_link_addr);
3057 entry
3058 }
3059 );
3060 match entry {
3061 NeighborState::Dynamic(DynamicNeighborState::Incomplete { .. }) => {
3062 unreachable!("entry must be static, REACHABLE, or STALE")
3063 }
3064 NeighborState::Dynamic(DynamicNeighborState::Reachable { .. }) => {
3065 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3066 bindings_ctx,
3067 [(
3068 lookup_addr,
3069 NudEvent::ReachableTime,
3070 core_ctx.inner.base_reachable_time().get(),
3071 )],
3072 );
3073 }
3074 NeighborState::Dynamic(DynamicNeighborState::Delay { .. }) => {
3075 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3076 bindings_ctx,
3077 [(lookup_addr, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
3078 );
3079 }
3080 NeighborState::Dynamic(DynamicNeighborState::Probe { .. }) => {
3081 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3082 bindings_ctx,
3083 [(
3084 lookup_addr,
3085 NudEvent::RetransmitUnicastProbe,
3086 core_ctx.inner.state.retrans_timer.get(),
3087 )],
3088 );
3089 }
3090 NeighborState::Dynamic(DynamicNeighborState::Unreachable(Unreachable {
3091 link_address: _,
3092 mode,
3093 })) => {
3094 let instant = match mode {
3095 UnreachableMode::WaitingForPacketSend => None,
3096 mode @ UnreachableMode::Backoff { .. } => {
3097 let duration =
3098 mode.next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state);
3099 Some(bindings_ctx.now() + duration.get())
3100 }
3101 };
3102 if let Some(instant) = instant {
3103 core_ctx.nud.state.timer_heap.neighbor.assert_timers([(
3104 lookup_addr,
3105 NudEvent::RetransmitUnicastProbe,
3106 instant,
3107 )]);
3108 }
3109 }
3110 NeighborState::Dynamic(DynamicNeighborState::Stale { .. })
3111 | NeighborState::Static(_) => bindings_ctx.timers.assert_no_timers_installed(),
3112 }
3113 }
3114
3115 trait TestIpExt: NudIcmpIpExt {
3116 const LOOKUP_ADDR1: SpecifiedAddr<Self::Addr>;
3117 const LOOKUP_ADDR2: SpecifiedAddr<Self::Addr>;
3118 const LOOKUP_ADDR3: SpecifiedAddr<Self::Addr>;
3119 }
3120
3121 impl TestIpExt for Ipv4 {
3122 const LOOKUP_ADDR1: SpecifiedAddr<Ipv4Addr> =
3124 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.1")) };
3125 const LOOKUP_ADDR2: SpecifiedAddr<Ipv4Addr> =
3126 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.2")) };
3127 const LOOKUP_ADDR3: SpecifiedAddr<Ipv4Addr> =
3128 unsafe { SpecifiedAddr::new_unchecked(net_ip_v4!("192.168.0.3")) };
3129 }
3130
3131 impl TestIpExt for Ipv6 {
3132 const LOOKUP_ADDR1: SpecifiedAddr<Ipv6Addr> =
3134 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::1")) };
3135 const LOOKUP_ADDR2: SpecifiedAddr<Ipv6Addr> =
3136 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::2")) };
3137 const LOOKUP_ADDR3: SpecifiedAddr<Ipv6Addr> =
3138 unsafe { SpecifiedAddr::new_unchecked(net_ip_v6!("fe80::3")) };
3139 }
3140
3141 const LINK_ADDR1: FakeLinkAddress = FakeLinkAddress([1]);
3142 const LINK_ADDR2: FakeLinkAddress = FakeLinkAddress([2]);
3143 const LINK_ADDR3: FakeLinkAddress = FakeLinkAddress([3]);
3144
3145 impl<I: Ip, L: LinkDevice> NudTimerId<I, L, FakeWeakDeviceId<FakeLinkDeviceId>> {
3146 fn neighbor() -> Self {
3147 Self {
3148 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3149 timer_type: NudTimerType::Neighbor,
3150 _marker: PhantomData,
3151 }
3152 }
3153
3154 fn garbage_collection() -> Self {
3155 Self {
3156 device_id: FakeWeakDeviceId(FakeLinkDeviceId),
3157 timer_type: NudTimerType::GarbageCollection,
3158 _marker: PhantomData,
3159 }
3160 }
3161 }
3162
3163 fn queue_ip_packet_to_unresolved_neighbor<I: TestIpExt>(
3164 core_ctx: &mut FakeCoreCtxImpl<I>,
3165 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3166 neighbor: SpecifiedAddr<I::Addr>,
3167 pending_frames: &mut VecDeque<Buf<Vec<u8>>>,
3168 body: u8,
3169 expect_event: bool,
3170 ) {
3171 let body = [body];
3172 assert_eq!(
3173 NudHandler::send_ip_packet_to_neighbor(
3174 core_ctx,
3175 bindings_ctx,
3176 &FakeLinkDeviceId,
3177 neighbor,
3178 Buf::new(body, ..),
3179 FakeTxMetadata::default(),
3180 ),
3181 Ok(())
3182 );
3183
3184 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
3185
3186 pending_frames.push_back(Buf::new(body.to_vec(), ..));
3187
3188 assert_neighbor_state_with_ip(
3189 core_ctx,
3190 bindings_ctx,
3191 neighbor,
3192 DynamicNeighborState::Incomplete(Incomplete {
3193 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
3194 pending_frames: pending_frames
3195 .iter()
3196 .cloned()
3197 .map(|buf| (buf, FakeTxMetadata::default()))
3198 .collect(),
3199 notifiers: Vec::new(),
3200 _marker: PhantomData,
3201 }),
3202 expect_event.then_some(ExpectedEvent::Added),
3203 );
3204
3205 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
3206 bindings_ctx,
3207 [(neighbor, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
3208 );
3209 }
3210
3211 fn init_incomplete_neighbor_with_ip<I: TestIpExt>(
3212 core_ctx: &mut FakeCoreCtxImpl<I>,
3213 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3214 ip_address: SpecifiedAddr<I::Addr>,
3215 take_probe: bool,
3216 ) -> VecDeque<Buf<Vec<u8>>> {
3217 let mut pending_frames = VecDeque::new();
3218 queue_ip_packet_to_unresolved_neighbor(
3219 core_ctx,
3220 bindings_ctx,
3221 ip_address,
3222 &mut pending_frames,
3223 1,
3224 true, );
3226 if take_probe {
3227 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, None);
3228 }
3229 pending_frames
3230 }
3231
3232 fn init_incomplete_neighbor<I: TestIpExt>(
3233 core_ctx: &mut FakeCoreCtxImpl<I>,
3234 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3235 take_probe: bool,
3236 ) -> VecDeque<Buf<Vec<u8>>> {
3237 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, take_probe)
3238 }
3239
3240 fn init_stale_neighbor_with_ip<I: TestIpExt>(
3241 core_ctx: &mut FakeCoreCtxImpl<I>,
3242 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3243 ip_address: SpecifiedAddr<I::Addr>,
3244 link_address: FakeLinkAddress,
3245 ) {
3246 NudHandler::handle_neighbor_update(
3247 core_ctx,
3248 bindings_ctx,
3249 &FakeLinkDeviceId,
3250 ip_address,
3251 DynamicNeighborUpdateSource::Probe { link_address },
3252 );
3253 assert_neighbor_state_with_ip(
3254 core_ctx,
3255 bindings_ctx,
3256 ip_address,
3257 DynamicNeighborState::Stale(Stale { link_address }),
3258 Some(ExpectedEvent::Added),
3259 );
3260 }
3261
3262 fn init_stale_neighbor<I: TestIpExt>(
3263 core_ctx: &mut FakeCoreCtxImpl<I>,
3264 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3265 link_address: FakeLinkAddress,
3266 ) {
3267 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3268 }
3269
3270 fn init_reachable_neighbor_with_ip<I: TestIpExt>(
3271 core_ctx: &mut FakeCoreCtxImpl<I>,
3272 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3273 ip_address: SpecifiedAddr<I::Addr>,
3274 link_address: FakeLinkAddress,
3275 ) {
3276 let queued_frame =
3277 init_incomplete_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, true);
3278 NudHandler::handle_neighbor_update(
3279 core_ctx,
3280 bindings_ctx,
3281 &FakeLinkDeviceId,
3282 ip_address,
3283 DynamicNeighborUpdateSource::Confirmation {
3284 link_address: Some(link_address),
3285 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
3286 },
3287 );
3288 assert_neighbor_state_with_ip(
3289 core_ctx,
3290 bindings_ctx,
3291 ip_address,
3292 DynamicNeighborState::Reachable(Reachable {
3293 link_address,
3294 last_confirmed_at: bindings_ctx.now(),
3295 }),
3296 Some(ExpectedEvent::Changed),
3297 );
3298 assert_pending_frame_sent(core_ctx, queued_frame, link_address);
3299 }
3300
3301 fn init_reachable_neighbor<I: TestIpExt>(
3302 core_ctx: &mut FakeCoreCtxImpl<I>,
3303 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3304 link_address: FakeLinkAddress,
3305 ) {
3306 init_reachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3307 }
3308
3309 fn init_delay_neighbor_with_ip<I: TestIpExt>(
3310 core_ctx: &mut FakeCoreCtxImpl<I>,
3311 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3312 ip_address: SpecifiedAddr<I::Addr>,
3313 link_address: FakeLinkAddress,
3314 ) {
3315 init_stale_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3316 assert_eq!(
3317 NudHandler::send_ip_packet_to_neighbor(
3318 core_ctx,
3319 bindings_ctx,
3320 &FakeLinkDeviceId,
3321 ip_address,
3322 Buf::new([1], ..),
3323 FakeTxMetadata::default(),
3324 ),
3325 Ok(())
3326 );
3327 assert_neighbor_state_with_ip(
3328 core_ctx,
3329 bindings_ctx,
3330 ip_address,
3331 DynamicNeighborState::Delay(Delay { link_address }),
3332 Some(ExpectedEvent::Changed),
3333 );
3334 assert_eq!(
3335 core_ctx.inner.take_frames(),
3336 vec![(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![1])],
3337 );
3338 }
3339
3340 fn init_delay_neighbor<I: TestIpExt>(
3341 core_ctx: &mut FakeCoreCtxImpl<I>,
3342 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3343 link_address: FakeLinkAddress,
3344 ) {
3345 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3346 }
3347
3348 fn init_probe_neighbor_with_ip<I: TestIpExt>(
3349 core_ctx: &mut FakeCoreCtxImpl<I>,
3350 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3351 ip_address: SpecifiedAddr<I::Addr>,
3352 link_address: FakeLinkAddress,
3353 take_probe: bool,
3354 ) {
3355 init_delay_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address);
3356 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3357 core_ctx.nud.state.timer_heap.neighbor.assert_top(&ip_address, &NudEvent::DelayFirstProbe);
3358 assert_eq!(
3359 bindings_ctx.trigger_timers_for(DELAY_FIRST_PROBE_TIME.into(), core_ctx),
3360 [NudTimerId::neighbor()]
3361 );
3362 assert_neighbor_state_with_ip(
3363 core_ctx,
3364 bindings_ctx,
3365 ip_address,
3366 DynamicNeighborState::Probe(Probe {
3367 link_address,
3368 transmit_counter: NonZeroU16::new(max_unicast_solicit - 1),
3369 }),
3370 Some(ExpectedEvent::Changed),
3371 );
3372 if take_probe {
3373 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3374 }
3375 }
3376
3377 fn init_probe_neighbor<I: TestIpExt>(
3378 core_ctx: &mut FakeCoreCtxImpl<I>,
3379 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3380 link_address: FakeLinkAddress,
3381 take_probe: bool,
3382 ) {
3383 init_probe_neighbor_with_ip(
3384 core_ctx,
3385 bindings_ctx,
3386 I::LOOKUP_ADDR1,
3387 link_address,
3388 take_probe,
3389 );
3390 }
3391
3392 fn init_unreachable_neighbor_with_ip<I: TestIpExt>(
3393 core_ctx: &mut FakeCoreCtxImpl<I>,
3394 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3395 ip_address: SpecifiedAddr<I::Addr>,
3396 link_address: FakeLinkAddress,
3397 ) {
3398 init_probe_neighbor_with_ip(core_ctx, bindings_ctx, ip_address, link_address, false);
3399 let retransmit_timeout = core_ctx.inner.retransmit_timeout();
3400 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
3401 for _ in 0..max_unicast_solicit {
3402 assert_neighbor_probe_sent_for_ip(core_ctx, ip_address, Some(LINK_ADDR1));
3403 assert_eq!(
3404 bindings_ctx.trigger_timers_for(retransmit_timeout.into(), core_ctx),
3405 [NudTimerId::neighbor()]
3406 );
3407 }
3408 assert_neighbor_state_with_ip(
3409 core_ctx,
3410 bindings_ctx,
3411 ip_address,
3412 DynamicNeighborState::Unreachable(Unreachable {
3413 link_address,
3414 mode: UnreachableMode::WaitingForPacketSend,
3415 }),
3416 Some(ExpectedEvent::Changed),
3417 );
3418 }
3419
3420 fn init_unreachable_neighbor<I: TestIpExt>(
3421 core_ctx: &mut FakeCoreCtxImpl<I>,
3422 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3423 link_address: FakeLinkAddress,
3424 ) {
3425 init_unreachable_neighbor_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, link_address);
3426 }
3427
3428 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
3429 enum InitialState {
3430 Incomplete,
3431 Stale,
3432 Reachable,
3433 Delay,
3434 Probe,
3435 Unreachable,
3436 }
3437
3438 fn init_neighbor_in_state<I: TestIpExt>(
3439 core_ctx: &mut FakeCoreCtxImpl<I>,
3440 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3441 state: InitialState,
3442 ) -> DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>> {
3443 match state {
3444 InitialState::Incomplete => {
3445 let _: VecDeque<Buf<Vec<u8>>> =
3446 init_incomplete_neighbor(core_ctx, bindings_ctx, true);
3447 }
3448 InitialState::Reachable => {
3449 init_reachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3450 }
3451 InitialState::Stale => {
3452 init_stale_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3453 }
3454 InitialState::Delay => {
3455 init_delay_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3456 }
3457 InitialState::Probe => {
3458 init_probe_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, true);
3459 }
3460 InitialState::Unreachable => {
3461 init_unreachable_neighbor(core_ctx, bindings_ctx, LINK_ADDR1);
3462 }
3463 }
3464 assert_matches!(core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
3465 Some(NeighborState::Dynamic(state)) => state.clone()
3466 )
3467 }
3468
3469 #[track_caller]
3470 fn init_static_neighbor_with_ip<I: TestIpExt>(
3471 core_ctx: &mut FakeCoreCtxImpl<I>,
3472 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3473 ip_address: SpecifiedAddr<I::Addr>,
3474 link_address: FakeLinkAddress,
3475 expected_event: ExpectedEvent,
3476 ) {
3477 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3478 NeighborApi::new(&mut ctx)
3479 .insert_static_entry(&FakeLinkDeviceId, *ip_address, link_address)
3480 .unwrap();
3481 assert_eq!(
3482 ctx.bindings_ctx.take_events(),
3483 [Event {
3484 device: FakeLinkDeviceId,
3485 addr: ip_address,
3486 kind: match expected_event {
3487 ExpectedEvent::Added => EventKind::Added(EventState::Static(link_address)),
3488 ExpectedEvent::Changed => EventKind::Changed(EventState::Static(link_address)),
3489 },
3490 at: ctx.bindings_ctx.now(),
3491 }],
3492 );
3493 }
3494
3495 #[track_caller]
3496 fn init_static_neighbor<I: TestIpExt>(
3497 core_ctx: &mut FakeCoreCtxImpl<I>,
3498 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3499 link_address: FakeLinkAddress,
3500 expected_event: ExpectedEvent,
3501 ) {
3502 init_static_neighbor_with_ip(
3503 core_ctx,
3504 bindings_ctx,
3505 I::LOOKUP_ADDR1,
3506 link_address,
3507 expected_event,
3508 );
3509 }
3510
3511 #[track_caller]
3512 fn delete_neighbor<I: TestIpExt>(
3513 core_ctx: &mut FakeCoreCtxImpl<I>,
3514 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3515 ) {
3516 let mut ctx = CtxPair { core_ctx, bindings_ctx };
3517 NeighborApi::new(&mut ctx)
3518 .remove_entry(&FakeLinkDeviceId, *I::LOOKUP_ADDR1)
3519 .expect("neighbor entry should exist");
3520 assert_eq!(
3521 ctx.bindings_ctx.take_events(),
3522 [Event::removed(&FakeLinkDeviceId, I::LOOKUP_ADDR1, ctx.bindings_ctx.now())],
3523 );
3524 }
3525
3526 #[track_caller]
3527 fn assert_neighbor_state<I: TestIpExt>(
3528 core_ctx: &FakeCoreCtxImpl<I>,
3529 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3530 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3531 event_kind: Option<ExpectedEvent>,
3532 ) {
3533 assert_neighbor_state_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1, state, event_kind);
3534 }
3535
3536 #[derive(Clone, Copy, Debug)]
3537 enum ExpectedEvent {
3538 Added,
3539 Changed,
3540 }
3541
3542 #[track_caller]
3543 fn assert_neighbor_state_with_ip<I: TestIpExt>(
3544 core_ctx: &FakeCoreCtxImpl<I>,
3545 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3546 neighbor: SpecifiedAddr<I::Addr>,
3547 state: DynamicNeighborState<FakeLinkDevice, FakeBindingsCtxImpl<I>>,
3548 expected_event: Option<ExpectedEvent>,
3549 ) {
3550 if let Some(expected_event) = expected_event {
3551 let event_state = EventState::Dynamic(state.to_event_dynamic_state());
3552 assert_eq!(
3553 bindings_ctx.take_events(),
3554 [Event {
3555 device: FakeLinkDeviceId,
3556 addr: neighbor,
3557 kind: match expected_event {
3558 ExpectedEvent::Added => EventKind::Added(event_state),
3559 ExpectedEvent::Changed => EventKind::Changed(event_state),
3560 },
3561 at: bindings_ctx.now(),
3562 }],
3563 );
3564 }
3565
3566 assert_eq!(
3567 core_ctx.nud.state.neighbors.get(&neighbor),
3568 Some(&NeighborState::Dynamic(state))
3569 );
3570 }
3571
3572 #[track_caller]
3573 fn assert_pending_frame_sent<I: TestIpExt>(
3574 core_ctx: &mut FakeCoreCtxImpl<I>,
3575 pending_frames: VecDeque<Buf<Vec<u8>>>,
3576 link_address: FakeLinkAddress,
3577 ) {
3578 assert_eq!(
3579 core_ctx.inner.take_frames(),
3580 pending_frames
3581 .into_iter()
3582 .map(|f| (
3583 FakeNudMessageMeta::IpFrame { dst_link_address: link_address },
3584 f.as_ref().to_vec(),
3585 ))
3586 .collect::<Vec<_>>()
3587 );
3588 }
3589
3590 #[track_caller]
3591 fn assert_neighbor_probe_sent_for_ip<I: TestIpExt>(
3592 core_ctx: &mut FakeCoreCtxImpl<I>,
3593 ip_address: SpecifiedAddr<I::Addr>,
3594 link_address: Option<FakeLinkAddress>,
3595 ) {
3596 assert_eq!(
3597 core_ctx.inner.take_frames(),
3598 [(
3599 FakeNudMessageMeta::NeighborSolicitation {
3600 lookup_addr: ip_address,
3601 remote_link_addr: link_address,
3602 },
3603 Vec::new()
3604 )]
3605 );
3606 }
3607
3608 #[track_caller]
3609 fn assert_neighbor_probe_sent<I: TestIpExt>(
3610 core_ctx: &mut FakeCoreCtxImpl<I>,
3611 link_address: Option<FakeLinkAddress>,
3612 ) {
3613 assert_neighbor_probe_sent_for_ip(core_ctx, I::LOOKUP_ADDR1, link_address);
3614 }
3615
3616 #[track_caller]
3617 fn assert_neighbor_removed_with_ip<I: TestIpExt>(
3618 core_ctx: &mut FakeCoreCtxImpl<I>,
3619 bindings_ctx: &mut FakeBindingsCtxImpl<I>,
3620 neighbor: SpecifiedAddr<I::Addr>,
3621 ) {
3622 super::testutil::assert_neighbor_unknown(core_ctx, FakeLinkDeviceId, neighbor);
3623 assert_eq!(
3624 bindings_ctx.take_events(),
3625 [Event::removed(&FakeLinkDeviceId, neighbor, bindings_ctx.now())],
3626 );
3627 }
3628
3629 #[ip_test(I)]
3630 fn serialization_failure_doesnt_schedule_timer<I: TestIpExt>() {
3631 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3632
3633 let packet = Buf::new([0; 2], ..).with_size_limit(1);
3636
3637 let err = assert_matches!(
3638 NudHandler::send_ip_packet_to_neighbor(
3639 &mut core_ctx,
3640 &mut bindings_ctx,
3641 &FakeLinkDeviceId,
3642 I::LOOKUP_ADDR1,
3643 packet,
3644 FakeTxMetadata::default(),
3645 ),
3646 Err(ErrorAndSerializer { error, serializer: _ }) => error
3647 );
3648 assert_eq!(err, SendFrameErrorReason::SizeConstraintsViolation);
3649
3650 super::testutil::assert_neighbor_unknown(&mut core_ctx, FakeLinkDeviceId, I::LOOKUP_ADDR1);
3653 assert_eq!(core_ctx.inner.take_frames(), []);
3654 bindings_ctx.timers.assert_no_timers_installed();
3655 }
3656
3657 #[ip_test(I)]
3658 fn incomplete_to_stale_on_probe<I: TestIpExt>() {
3659 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3660
3661 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3663
3664 NudHandler::handle_neighbor_update(
3666 &mut core_ctx,
3667 &mut bindings_ctx,
3668 &FakeLinkDeviceId,
3669 I::LOOKUP_ADDR1,
3670 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
3671 );
3672
3673 assert_neighbor_state(
3675 &core_ctx,
3676 &mut bindings_ctx,
3677 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3678 Some(ExpectedEvent::Changed),
3679 );
3680 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3681 }
3682
3683 #[ip_test(I)]
3684 #[test_case(true, true; "solicited override")]
3685 #[test_case(true, false; "solicited non-override")]
3686 #[test_case(false, true; "unsolicited override")]
3687 #[test_case(false, false; "unsolicited non-override")]
3688 fn incomplete_on_confirmation<I: TestIpExt>(solicited_flag: bool, override_flag: bool) {
3689 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3690
3691 let queued_frame = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, true);
3693
3694 NudHandler::handle_neighbor_update(
3696 &mut core_ctx,
3697 &mut bindings_ctx,
3698 &FakeLinkDeviceId,
3699 I::LOOKUP_ADDR1,
3700 DynamicNeighborUpdateSource::Confirmation {
3701 link_address: Some(LINK_ADDR1),
3702 flags: ConfirmationFlags { solicited_flag, override_flag },
3703 },
3704 );
3705
3706 let expected_state = if solicited_flag {
3707 DynamicNeighborState::Reachable(Reachable {
3708 link_address: LINK_ADDR1,
3709 last_confirmed_at: bindings_ctx.now(),
3710 })
3711 } else {
3712 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 })
3713 };
3714 assert_neighbor_state(
3715 &core_ctx,
3716 &mut bindings_ctx,
3717 expected_state,
3718 Some(ExpectedEvent::Changed),
3719 );
3720 assert_pending_frame_sent(&mut core_ctx, queued_frame, LINK_ADDR1);
3721 }
3722
3723 #[ip_test(I)]
3724 fn reachable_to_stale_on_timeout<I: TestIpExt>() {
3725 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3726
3727 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
3729
3730 assert_eq!(
3732 bindings_ctx
3733 .trigger_timers_for(core_ctx.inner.base_reachable_time().into(), &mut core_ctx,),
3734 [NudTimerId::neighbor()]
3735 );
3736 assert_neighbor_state(
3737 &core_ctx,
3738 &mut bindings_ctx,
3739 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
3740 Some(ExpectedEvent::Changed),
3741 );
3742 }
3743
3744 #[ip_test(I)]
3745 #[test_case(InitialState::Reachable, true; "reachable with different address")]
3746 #[test_case(InitialState::Reachable, false; "reachable with same address")]
3747 #[test_case(InitialState::Stale, true; "stale with different address")]
3748 #[test_case(InitialState::Stale, false; "stale with same address")]
3749 #[test_case(InitialState::Delay, true; "delay with different address")]
3750 #[test_case(InitialState::Delay, false; "delay with same address")]
3751 #[test_case(InitialState::Probe, true; "probe with different address")]
3752 #[test_case(InitialState::Probe, false; "probe with same address")]
3753 #[test_case(InitialState::Unreachable, true; "unreachable with different address")]
3754 #[test_case(InitialState::Unreachable, false; "unreachable with same address")]
3755 fn transition_to_stale_on_probe_with_different_address<I: TestIpExt>(
3756 initial_state: InitialState,
3757 update_link_address: bool,
3758 ) {
3759 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3760
3761 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3763
3764 NudHandler::handle_neighbor_update(
3766 &mut core_ctx,
3767 &mut bindings_ctx,
3768 &FakeLinkDeviceId,
3769 I::LOOKUP_ADDR1,
3770 DynamicNeighborUpdateSource::Probe {
3771 link_address: if update_link_address { LINK_ADDR2 } else { LINK_ADDR1 },
3772 },
3773 );
3774
3775 let expected_state = if update_link_address {
3781 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 })
3782 } else {
3783 initial_state
3784 };
3785 assert_neighbor_state(
3786 &core_ctx,
3787 &mut bindings_ctx,
3788 expected_state,
3789 update_link_address.then_some(ExpectedEvent::Changed),
3790 );
3791 }
3792
3793 #[ip_test(I)]
3794 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
3795 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
3796 #[test_case(InitialState::Stale, true; "stale with override flag set")]
3797 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
3798 #[test_case(InitialState::Delay, true; "delay with override flag set")]
3799 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
3800 #[test_case(InitialState::Probe, true; "probe with override flag set")]
3801 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
3802 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
3803 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
3804 fn transition_to_reachable_on_solicited_confirmation_same_address<I: TestIpExt>(
3805 initial_state: InitialState,
3806 override_flag: bool,
3807 ) {
3808 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3809
3810 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3812
3813 NudHandler::handle_neighbor_update(
3815 &mut core_ctx,
3816 &mut bindings_ctx,
3817 &FakeLinkDeviceId,
3818 I::LOOKUP_ADDR1,
3819 DynamicNeighborUpdateSource::Confirmation {
3820 link_address: Some(LINK_ADDR1),
3821 flags: ConfirmationFlags { solicited_flag: true, override_flag },
3822 },
3823 );
3824
3825 let now = bindings_ctx.now();
3827 assert_neighbor_state(
3828 &core_ctx,
3829 &mut bindings_ctx,
3830 DynamicNeighborState::Reachable(Reachable {
3831 link_address: LINK_ADDR1,
3832 last_confirmed_at: now,
3833 }),
3834 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
3835 );
3836 }
3837
3838 #[ip_test(I)]
3839 #[test_case(InitialState::Reachable; "reachable")]
3840 #[test_case(InitialState::Stale; "stale")]
3841 #[test_case(InitialState::Delay; "delay")]
3842 #[test_case(InitialState::Probe; "probe")]
3843 #[test_case(InitialState::Unreachable; "unreachable")]
3844 fn transition_to_stale_on_unsolicited_override_confirmation_with_different_address<
3845 I: TestIpExt,
3846 >(
3847 initial_state: InitialState,
3848 ) {
3849 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3850
3851 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3853
3854 NudHandler::handle_neighbor_update(
3856 &mut core_ctx,
3857 &mut bindings_ctx,
3858 &FakeLinkDeviceId,
3859 I::LOOKUP_ADDR1,
3860 DynamicNeighborUpdateSource::Confirmation {
3861 link_address: Some(LINK_ADDR2),
3862 flags: ConfirmationFlags { solicited_flag: false, override_flag: true },
3863 },
3864 );
3865
3866 assert_neighbor_state(
3868 &core_ctx,
3869 &mut bindings_ctx,
3870 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
3871 Some(ExpectedEvent::Changed),
3872 );
3873 }
3874
3875 #[ip_test(I)]
3876 #[test_case(InitialState::Reachable, true; "reachable with override flag set")]
3877 #[test_case(InitialState::Reachable, false; "reachable with override flag not set")]
3878 #[test_case(InitialState::Stale, true; "stale with override flag set")]
3879 #[test_case(InitialState::Stale, false; "stale with override flag not set")]
3880 #[test_case(InitialState::Delay, true; "delay with override flag set")]
3881 #[test_case(InitialState::Delay, false; "delay with override flag not set")]
3882 #[test_case(InitialState::Probe, true; "probe with override flag set")]
3883 #[test_case(InitialState::Probe, false; "probe with override flag not set")]
3884 #[test_case(InitialState::Unreachable, true; "unreachable with override flag set")]
3885 #[test_case(InitialState::Unreachable, false; "unreachable with override flag not set")]
3886 fn noop_on_unsolicited_confirmation_with_same_address<I: TestIpExt>(
3887 initial_state: InitialState,
3888 override_flag: bool,
3889 ) {
3890 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3891
3892 let expected_state =
3894 init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3895
3896 NudHandler::handle_neighbor_update(
3898 &mut core_ctx,
3899 &mut bindings_ctx,
3900 &FakeLinkDeviceId,
3901 I::LOOKUP_ADDR1,
3902 DynamicNeighborUpdateSource::Confirmation {
3903 link_address: Some(LINK_ADDR1),
3904 flags: ConfirmationFlags { solicited_flag: false, override_flag },
3905 },
3906 );
3907
3908 assert_neighbor_state(&core_ctx, &mut bindings_ctx, expected_state, None);
3910 }
3911
3912 #[ip_test(I)]
3913 #[test_case(InitialState::Reachable; "reachable")]
3914 #[test_case(InitialState::Stale; "stale")]
3915 #[test_case(InitialState::Delay; "delay")]
3916 #[test_case(InitialState::Probe; "probe")]
3917 #[test_case(InitialState::Unreachable; "unreachable")]
3918 fn transition_to_reachable_on_solicited_override_confirmation_with_different_address<
3919 I: TestIpExt,
3920 >(
3921 initial_state: InitialState,
3922 ) {
3923 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3924
3925 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
3927
3928 NudHandler::handle_neighbor_update(
3930 &mut core_ctx,
3931 &mut bindings_ctx,
3932 &FakeLinkDeviceId,
3933 I::LOOKUP_ADDR1,
3934 DynamicNeighborUpdateSource::Confirmation {
3935 link_address: Some(LINK_ADDR2),
3936 flags: ConfirmationFlags { solicited_flag: true, override_flag: true },
3937 },
3938 );
3939
3940 let now = bindings_ctx.now();
3942 assert_neighbor_state(
3943 &core_ctx,
3944 &mut bindings_ctx,
3945 DynamicNeighborState::Reachable(Reachable {
3946 link_address: LINK_ADDR2,
3947 last_confirmed_at: now,
3948 }),
3949 Some(ExpectedEvent::Changed),
3950 );
3951 }
3952
3953 #[ip_test(I)]
3954 fn reachable_to_reachable_on_probe_with_same_address<I: TestIpExt>() {
3955 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3956
3957 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
3959
3960 NudHandler::handle_neighbor_update(
3962 &mut core_ctx,
3963 &mut bindings_ctx,
3964 &FakeLinkDeviceId,
3965 I::LOOKUP_ADDR1,
3966 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR1 },
3967 );
3968
3969 let now = bindings_ctx.now();
3971 assert_neighbor_state(
3972 &core_ctx,
3973 &mut bindings_ctx,
3974 DynamicNeighborState::Reachable(Reachable {
3975 link_address: LINK_ADDR1,
3976 last_confirmed_at: now,
3977 }),
3978 None,
3979 );
3980 }
3981
3982 #[ip_test(I)]
3983 #[test_case(true; "solicited")]
3984 #[test_case(false; "unsolicited")]
3985 fn reachable_to_stale_on_non_override_confirmation_with_different_address<I: TestIpExt>(
3986 solicited_flag: bool,
3987 ) {
3988 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
3989
3990 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
3992
3993 NudHandler::handle_neighbor_update(
3995 &mut core_ctx,
3996 &mut bindings_ctx,
3997 &FakeLinkDeviceId,
3998 I::LOOKUP_ADDR1,
3999 DynamicNeighborUpdateSource::Confirmation {
4000 link_address: Some(LINK_ADDR2),
4001 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4002 },
4003 );
4004
4005 assert_neighbor_state(
4008 &core_ctx,
4009 &mut bindings_ctx,
4010 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4011 Some(ExpectedEvent::Changed),
4012 );
4013 }
4014
4015 #[ip_test(I)]
4016 #[test_case(InitialState::Stale, true; "stale solicited")]
4017 #[test_case(InitialState::Stale, false; "stale unsolicited")]
4018 #[test_case(InitialState::Delay, true; "delay solicited")]
4019 #[test_case(InitialState::Delay, false; "delay unsolicited")]
4020 #[test_case(InitialState::Probe, true; "probe solicited")]
4021 #[test_case(InitialState::Probe, false; "probe unsolicited")]
4022 #[test_case(InitialState::Unreachable, true; "unreachable solicited")]
4023 #[test_case(InitialState::Unreachable, false; "unreachable unsolicited")]
4024 fn noop_on_non_override_confirmation_with_different_address<I: TestIpExt>(
4025 initial_state: InitialState,
4026 solicited_flag: bool,
4027 ) {
4028 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4029
4030 let initial_state = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4032
4033 NudHandler::handle_neighbor_update(
4035 &mut core_ctx,
4036 &mut bindings_ctx,
4037 &FakeLinkDeviceId,
4038 I::LOOKUP_ADDR1,
4039 DynamicNeighborUpdateSource::Confirmation {
4040 link_address: Some(LINK_ADDR2),
4041 flags: ConfirmationFlags { override_flag: false, solicited_flag },
4042 },
4043 );
4044
4045 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial_state, None);
4048 }
4049
4050 #[ip_test(I)]
4051 fn stale_to_delay_on_packet_sent<I: TestIpExt>() {
4052 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4053
4054 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4056
4057 let body = 1;
4059 assert_eq!(
4060 NudHandler::send_ip_packet_to_neighbor(
4061 &mut core_ctx,
4062 &mut bindings_ctx,
4063 &FakeLinkDeviceId,
4064 I::LOOKUP_ADDR1,
4065 Buf::new([body], ..),
4066 FakeTxMetadata::default(),
4067 ),
4068 Ok(())
4069 );
4070
4071 assert_neighbor_state(
4073 &core_ctx,
4074 &mut bindings_ctx,
4075 DynamicNeighborState::Delay(Delay { link_address: LINK_ADDR1 }),
4076 Some(ExpectedEvent::Changed),
4077 );
4078 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4079 &mut bindings_ctx,
4080 [(I::LOOKUP_ADDR1, NudEvent::DelayFirstProbe, DELAY_FIRST_PROBE_TIME.get())],
4081 );
4082 assert_pending_frame_sent(
4083 &mut core_ctx,
4084 VecDeque::from([Buf::new(vec![body], ..)]),
4085 LINK_ADDR1,
4086 );
4087 }
4088
4089 #[ip_test(I)]
4090 #[test_case(InitialState::Delay,
4091 NudEvent::DelayFirstProbe;
4092 "delay to probe")]
4093 #[test_case(InitialState::Probe,
4094 NudEvent::RetransmitUnicastProbe;
4095 "probe retransmit unicast probe")]
4096 fn delay_or_probe_to_probe_on_timeout<I: TestIpExt>(
4097 initial_state: InitialState,
4098 expected_initial_event: NudEvent,
4099 ) {
4100 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4101
4102 let _ = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4104
4105 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4106
4107 let (time, transmit_counter) = match initial_state {
4113 InitialState::Delay => {
4114 (DELAY_FIRST_PROBE_TIME, NonZeroU16::new(max_unicast_solicit - 1))
4115 }
4116 InitialState::Probe => {
4117 (core_ctx.inner.state.retrans_timer, NonZeroU16::new(max_unicast_solicit - 2))
4118 }
4119 other => unreachable!("test only covers DELAY and PROBE, got {:?}", other),
4120 };
4121 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4122 &mut bindings_ctx,
4123 [(I::LOOKUP_ADDR1, expected_initial_event, time.get())],
4124 );
4125 assert_eq!(
4126 bindings_ctx.trigger_timers_for(time.into(), &mut core_ctx,),
4127 [NudTimerId::neighbor()]
4128 );
4129 assert_neighbor_state(
4130 &core_ctx,
4131 &mut bindings_ctx,
4132 DynamicNeighborState::Probe(Probe { link_address: LINK_ADDR1, transmit_counter }),
4133 (initial_state != InitialState::Probe).then_some(ExpectedEvent::Changed),
4134 );
4135 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4136 &mut bindings_ctx,
4137 [(
4138 I::LOOKUP_ADDR1,
4139 NudEvent::RetransmitUnicastProbe,
4140 core_ctx.inner.state.retrans_timer.get(),
4141 )],
4142 );
4143 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4144 }
4145
4146 #[ip_test(I)]
4147 fn unreachable_probes_with_exponential_backoff_while_packets_sent<I: TestIpExt>() {
4148 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4149
4150 init_unreachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4151
4152 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4153 let timer_id = NudTimerId::neighbor();
4154
4155 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), []);
4157 assert_eq!(core_ctx.inner.take_frames(), []);
4158
4159 const BODY: u8 = 0x33;
4161 assert_eq!(
4162 NudHandler::send_ip_packet_to_neighbor(
4163 &mut core_ctx,
4164 &mut bindings_ctx,
4165 &FakeLinkDeviceId,
4166 I::LOOKUP_ADDR1,
4167 Buf::new([BODY], ..),
4168 FakeTxMetadata::default(),
4169 ),
4170 Ok(())
4171 );
4172 assert_eq!(
4173 core_ctx.inner.take_frames(),
4174 [
4175 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4176 (
4177 FakeNudMessageMeta::NeighborSolicitation {
4178 lookup_addr: I::LOOKUP_ADDR1,
4179 remote_link_addr: None,
4180 },
4181 Vec::new()
4182 )
4183 ]
4184 );
4185
4186 let next_backoff_timer = |core_ctx: &mut FakeCoreCtxImpl<I>, probes_sent| {
4187 UnreachableMode::Backoff {
4188 probes_sent: NonZeroU32::new(probes_sent).unwrap(),
4189 packet_sent: false,
4190 }
4191 .next_backoff_retransmit_timeout::<I, _>(&mut core_ctx.inner.state)
4192 .get()
4193 };
4194
4195 const ITERATIONS: u8 = 2;
4196 for i in 1..ITERATIONS {
4197 let probes_sent = u32::from(i);
4198
4199 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 + i], ..),
4208 FakeTxMetadata::default(),
4209 ),
4210 Ok(())
4211 );
4212 assert_eq!(
4213 core_ctx.inner.take_frames(),
4214 [(FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY + i])]
4215 );
4216
4217 assert_eq!(
4222 bindings_ctx.trigger_timers_for(
4223 next_backoff_timer(&mut core_ctx, probes_sent),
4224 &mut core_ctx,
4225 ),
4226 [timer_id]
4227 );
4228 assert_neighbor_probe_sent(&mut core_ctx, None);
4229 bindings_ctx.timers.assert_timers_installed([(
4230 timer_id,
4231 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, probes_sent + 1),
4232 )]);
4233 }
4234
4235 let current_timer = next_backoff_timer(&mut core_ctx, u32::from(ITERATIONS));
4238 assert_eq!(bindings_ctx.trigger_timers_for(current_timer, &mut core_ctx,), [timer_id]);
4239 assert_eq!(core_ctx.inner.take_frames(), []);
4240 bindings_ctx.timers.assert_no_timers_installed();
4241
4242 assert_eq!(
4245 NudHandler::send_ip_packet_to_neighbor(
4246 &mut core_ctx,
4247 &mut bindings_ctx,
4248 &FakeLinkDeviceId,
4249 I::LOOKUP_ADDR1,
4250 Buf::new([BODY], ..),
4251 FakeTxMetadata::default(),
4252 ),
4253 Ok(())
4254 );
4255 assert_eq!(
4256 core_ctx.inner.take_frames(),
4257 [
4258 (FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 }, vec![BODY]),
4259 (
4260 FakeNudMessageMeta::NeighborSolicitation {
4261 lookup_addr: I::LOOKUP_ADDR1,
4262 remote_link_addr: None,
4263 },
4264 Vec::new()
4265 )
4266 ]
4267 );
4268 bindings_ctx.timers.assert_timers_installed([(
4269 timer_id,
4270 bindings_ctx.now() + next_backoff_timer(&mut core_ctx, 1),
4271 )]);
4272 }
4273
4274 #[ip_test(I)]
4275 #[test_case(true; "solicited confirmation")]
4276 #[test_case(false; "unsolicited confirmation")]
4277 fn confirmation_should_not_create_entry<I: TestIpExt>(solicited_flag: bool) {
4278 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4279
4280 let link_address = Some(FakeLinkAddress([1]));
4281 NudHandler::handle_neighbor_update(
4282 &mut core_ctx,
4283 &mut bindings_ctx,
4284 &FakeLinkDeviceId,
4285 I::LOOKUP_ADDR1,
4286 DynamicNeighborUpdateSource::Confirmation {
4287 link_address,
4288 flags: ConfirmationFlags { solicited_flag, override_flag: false },
4289 },
4290 );
4291 assert_eq!(core_ctx.nud.state.neighbors, HashMap::new());
4292 }
4293
4294 #[ip_test(I)]
4295 #[test_case(true; "set_with_dynamic")]
4296 #[test_case(false; "set_with_static")]
4297 fn pending_frames<I: TestIpExt>(dynamic: bool) {
4298 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4299 assert_eq!(core_ctx.inner.take_frames(), []);
4300
4301 const MAX_PENDING_FRAMES_U8: u8 = MAX_PENDING_FRAMES as u8;
4305 let expected_pending_frames = (0..MAX_PENDING_FRAMES_U8)
4306 .map(|i| (Buf::new(vec![i], ..), FakeTxMetadata::default()))
4307 .collect::<VecDeque<_>>();
4308
4309 for (body, meta) in expected_pending_frames.iter() {
4310 assert_eq!(
4311 NudHandler::send_ip_packet_to_neighbor(
4312 &mut core_ctx,
4313 &mut bindings_ctx,
4314 &FakeLinkDeviceId,
4315 I::LOOKUP_ADDR1,
4316 body.clone(),
4317 meta.clone(),
4318 ),
4319 Ok(())
4320 );
4321 }
4322 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4323 assert_neighbor_probe_sent(&mut core_ctx, None);
4325 assert_neighbor_state(
4326 &core_ctx,
4327 &mut bindings_ctx,
4328 DynamicNeighborState::Incomplete(Incomplete {
4329 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4330 pending_frames: expected_pending_frames.clone(),
4331 notifiers: Vec::new(),
4332 _marker: PhantomData,
4333 }),
4334 Some(ExpectedEvent::Added),
4335 );
4336
4337 assert_eq!(
4339 NudHandler::send_ip_packet_to_neighbor(
4340 &mut core_ctx,
4341 &mut bindings_ctx,
4342 &FakeLinkDeviceId,
4343 I::LOOKUP_ADDR1,
4344 Buf::new([123], ..),
4345 FakeTxMetadata::default(),
4346 ),
4347 Ok(())
4348 );
4349 assert_eq!(core_ctx.inner.take_frames(), []);
4350 assert_neighbor_state(
4351 &core_ctx,
4352 &mut bindings_ctx,
4353 DynamicNeighborState::Incomplete(Incomplete {
4354 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4355 pending_frames: expected_pending_frames.clone(),
4356 notifiers: Vec::new(),
4357 _marker: PhantomData,
4358 }),
4359 None,
4360 );
4361
4362 if dynamic {
4364 NudHandler::handle_neighbor_update(
4365 &mut core_ctx,
4366 &mut bindings_ctx,
4367 &FakeLinkDeviceId,
4368 I::LOOKUP_ADDR1,
4369 DynamicNeighborUpdateSource::Confirmation {
4370 link_address: Some(LINK_ADDR1),
4371 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4372 },
4373 );
4374 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4375 &mut bindings_ctx,
4376 [(
4377 I::LOOKUP_ADDR1,
4378 NudEvent::ReachableTime,
4379 core_ctx.inner.base_reachable_time().get(),
4380 )],
4381 );
4382 let last_confirmed_at = bindings_ctx.now();
4383 assert_neighbor_state(
4384 &core_ctx,
4385 &mut bindings_ctx,
4386 DynamicNeighborState::Reachable(Reachable {
4387 link_address: LINK_ADDR1,
4388 last_confirmed_at,
4389 }),
4390 Some(ExpectedEvent::Changed),
4391 );
4392 } else {
4393 init_static_neighbor(
4394 &mut core_ctx,
4395 &mut bindings_ctx,
4396 LINK_ADDR1,
4397 ExpectedEvent::Changed,
4398 );
4399 bindings_ctx.timers.assert_no_timers_installed();
4400 }
4401 assert_eq!(
4402 core_ctx.inner.take_frames(),
4403 expected_pending_frames
4404 .into_iter()
4405 .map(|(p, FakeTxMetadata)| (
4406 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4407 p.as_ref().to_vec()
4408 ))
4409 .collect::<Vec<_>>()
4410 );
4411 }
4412
4413 #[ip_test(I)]
4414 fn static_neighbor<I: TestIpExt>() {
4415 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4416
4417 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4418 bindings_ctx.timers.assert_no_timers_installed();
4419 assert_eq!(core_ctx.inner.take_frames(), []);
4420 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4421
4422 NudHandler::handle_neighbor_update(
4424 &mut core_ctx,
4425 &mut bindings_ctx,
4426 &FakeLinkDeviceId,
4427 I::LOOKUP_ADDR1,
4428 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4429 );
4430 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4431
4432 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4433
4434 let neighbors = &core_ctx.nud.state.neighbors;
4435 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4436 }
4437
4438 #[ip_test(I)]
4439 fn dynamic_neighbor<I: TestIpExt>() {
4440 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4441
4442 init_stale_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4443 bindings_ctx.timers.assert_no_timers_installed();
4444 assert_eq!(core_ctx.inner.take_frames(), []);
4445 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4446
4447 NudHandler::handle_neighbor_update(
4449 &mut core_ctx,
4450 &mut bindings_ctx,
4451 &FakeLinkDeviceId,
4452 I::LOOKUP_ADDR1,
4453 DynamicNeighborUpdateSource::Probe { link_address: LINK_ADDR2 },
4454 );
4455 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR2);
4456 assert_eq!(core_ctx.inner.take_frames(), []);
4457 assert_neighbor_state(
4458 &core_ctx,
4459 &mut bindings_ctx,
4460 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR2 }),
4461 Some(ExpectedEvent::Changed),
4462 );
4463
4464 init_static_neighbor_with_ip(
4466 &mut core_ctx,
4467 &mut bindings_ctx,
4468 I::LOOKUP_ADDR1,
4469 LINK_ADDR3,
4470 ExpectedEvent::Changed,
4471 );
4472 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR3);
4473 assert_eq!(core_ctx.inner.take_frames(), []);
4474 }
4475
4476 #[ip_test(I)]
4477 fn send_solicitation_on_lookup<I: TestIpExt>() {
4478 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4479 bindings_ctx.timers.assert_no_timers_installed();
4480 assert_eq!(core_ctx.inner.take_frames(), []);
4481
4482 let mut pending_frames = VecDeque::new();
4483
4484 queue_ip_packet_to_unresolved_neighbor(
4485 &mut core_ctx,
4486 &mut bindings_ctx,
4487 I::LOOKUP_ADDR1,
4488 &mut pending_frames,
4489 1,
4490 true, );
4492 assert_neighbor_probe_sent(&mut core_ctx, None);
4493
4494 queue_ip_packet_to_unresolved_neighbor(
4495 &mut core_ctx,
4496 &mut bindings_ctx,
4497 I::LOOKUP_ADDR1,
4498 &mut pending_frames,
4499 2,
4500 false, );
4502 assert_eq!(core_ctx.inner.take_frames(), []);
4503
4504 NudHandler::handle_neighbor_update(
4506 &mut core_ctx,
4507 &mut bindings_ctx,
4508 &FakeLinkDeviceId,
4509 I::LOOKUP_ADDR1,
4510 DynamicNeighborUpdateSource::Confirmation {
4511 link_address: Some(LINK_ADDR1),
4512 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4513 },
4514 );
4515 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4516
4517 let now = bindings_ctx.now();
4518 assert_neighbor_state(
4519 &core_ctx,
4520 &mut bindings_ctx,
4521 DynamicNeighborState::Reachable(Reachable {
4522 link_address: LINK_ADDR1,
4523 last_confirmed_at: now,
4524 }),
4525 Some(ExpectedEvent::Changed),
4526 );
4527 assert_eq!(
4528 core_ctx.inner.take_frames(),
4529 pending_frames
4530 .into_iter()
4531 .map(|f| (
4532 FakeNudMessageMeta::IpFrame { dst_link_address: LINK_ADDR1 },
4533 f.as_ref().to_vec(),
4534 ))
4535 .collect::<Vec<_>>()
4536 );
4537 }
4538
4539 #[ip_test(I)]
4540 fn solicitation_failure_in_incomplete<I: TestIpExt>() {
4541 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4542 bindings_ctx.timers.assert_no_timers_installed();
4543 assert_eq!(core_ctx.inner.take_frames(), []);
4544
4545 let pending_frames = init_incomplete_neighbor(&mut core_ctx, &mut bindings_ctx, false);
4546
4547 let timer_id = NudTimerId::neighbor();
4548
4549 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4550 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4551
4552 for i in 1..=max_multicast_solicit {
4553 assert_neighbor_state(
4554 &core_ctx,
4555 &mut bindings_ctx,
4556 DynamicNeighborState::Incomplete(Incomplete {
4557 transmit_counter: NonZeroU16::new(max_multicast_solicit - i),
4558 pending_frames: pending_frames
4559 .iter()
4560 .cloned()
4561 .map(|b| (b, FakeTxMetadata::default()))
4562 .collect(),
4563 notifiers: Vec::new(),
4564 _marker: PhantomData,
4565 }),
4566 None,
4567 );
4568
4569 bindings_ctx
4570 .timers
4571 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4572 assert_neighbor_probe_sent(&mut core_ctx, None);
4573
4574 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4575 }
4576
4577 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1);
4579 bindings_ctx.timers.assert_no_timers_installed();
4580
4581 assert_eq!(core_ctx.inner.take_frames(), []);
4585 assert_eq!(core_ctx.counters().as_ref().icmp_dest_unreachable_dropped.get(), 1);
4586 }
4587
4588 #[ip_test(I)]
4589 fn solicitation_failure_in_probe<I: TestIpExt>() {
4590 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4591 bindings_ctx.timers.assert_no_timers_installed();
4592 assert_eq!(core_ctx.inner.take_frames(), []);
4593
4594 init_probe_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, false);
4595
4596 let timer_id = NudTimerId::neighbor();
4597 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4598 let max_unicast_solicit = core_ctx.inner.max_unicast_solicit().get();
4599 for i in 1..=max_unicast_solicit {
4600 assert_neighbor_state(
4601 &core_ctx,
4602 &mut bindings_ctx,
4603 DynamicNeighborState::Probe(Probe {
4604 transmit_counter: NonZeroU16::new(max_unicast_solicit - i),
4605 link_address: LINK_ADDR1,
4606 }),
4607 None,
4608 );
4609
4610 bindings_ctx
4611 .timers
4612 .assert_timers_installed([(timer_id, bindings_ctx.now() + ONE_SECOND.get())]);
4613 assert_neighbor_probe_sent(&mut core_ctx, Some(LINK_ADDR1));
4614
4615 assert_eq!(bindings_ctx.trigger_timers_for(retrans_timer, &mut core_ctx,), [timer_id]);
4616 }
4617
4618 assert_neighbor_state(
4619 &core_ctx,
4620 &mut bindings_ctx,
4621 DynamicNeighborState::Unreachable(Unreachable {
4622 link_address: LINK_ADDR1,
4623 mode: UnreachableMode::WaitingForPacketSend,
4624 }),
4625 Some(ExpectedEvent::Changed),
4626 );
4627 bindings_ctx.timers.assert_no_timers_installed();
4628 assert_eq!(core_ctx.inner.take_frames(), []);
4629 }
4630
4631 #[ip_test(I)]
4632 fn flush_entries<I: TestIpExt>() {
4633 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4634 bindings_ctx.timers.assert_no_timers_installed();
4635 assert_eq!(core_ctx.inner.take_frames(), []);
4636
4637 init_static_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1, ExpectedEvent::Added);
4638 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR2, LINK_ADDR2);
4639 let pending_frames = init_incomplete_neighbor_with_ip(
4640 &mut core_ctx,
4641 &mut bindings_ctx,
4642 I::LOOKUP_ADDR3,
4643 true,
4644 );
4645 let pending_frames =
4646 pending_frames.into_iter().map(|b| (b, FakeTxMetadata::default())).collect();
4647
4648 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4649 assert_eq!(
4650 core_ctx.nud.state.neighbors,
4651 HashMap::from([
4652 (I::LOOKUP_ADDR1, NeighborState::Static(LINK_ADDR1)),
4653 (
4654 I::LOOKUP_ADDR2,
4655 NeighborState::Dynamic(DynamicNeighborState::Stale(Stale {
4656 link_address: LINK_ADDR2,
4657 })),
4658 ),
4659 (
4660 I::LOOKUP_ADDR3,
4661 NeighborState::Dynamic(DynamicNeighborState::Incomplete(Incomplete {
4662 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4663 pending_frames,
4664 notifiers: Vec::new(),
4665 _marker: PhantomData,
4666 })),
4667 ),
4668 ]),
4669 );
4670 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4671 &mut bindings_ctx,
4672 [(I::LOOKUP_ADDR3, NudEvent::RetransmitMulticastProbe, ONE_SECOND.get())],
4673 );
4674
4675 NudHandler::flush(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId);
4677 let neighbors = &core_ctx.nud.state.neighbors;
4678 assert!(neighbors.is_empty(), "neighbor table should be empty: {:?}", neighbors);
4679 assert_eq!(
4680 bindings_ctx.take_events().into_iter().collect::<HashSet<_>>(),
4681 [I::LOOKUP_ADDR1, I::LOOKUP_ADDR2, I::LOOKUP_ADDR3]
4682 .into_iter()
4683 .map(|addr| { Event::removed(&FakeLinkDeviceId, addr, bindings_ctx.now()) })
4684 .collect(),
4685 );
4686 bindings_ctx.timers.assert_no_timers_installed();
4687 }
4688
4689 #[ip_test(I)]
4690 fn delete_dynamic_entry<I: TestIpExt>() {
4691 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4692 bindings_ctx.timers.assert_no_timers_installed();
4693 assert_eq!(core_ctx.inner.take_frames(), []);
4694
4695 init_reachable_neighbor(&mut core_ctx, &mut bindings_ctx, LINK_ADDR1);
4696 check_lookup_has(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
4697
4698 delete_neighbor(&mut core_ctx, &mut bindings_ctx);
4699
4700 let neighbors = &core_ctx.nud.state.neighbors;
4702 assert!(neighbors.is_empty(), "neighbor table should be empty: {neighbors:?}");
4703 bindings_ctx.timers.assert_no_timers_installed();
4704 }
4705
4706 #[ip_test(I)]
4707 #[test_case(InitialState::Reachable; "reachable neighbor")]
4708 #[test_case(InitialState::Stale; "stale neighbor")]
4709 #[test_case(InitialState::Delay; "delay neighbor")]
4710 #[test_case(InitialState::Probe; "probe neighbor")]
4711 #[test_case(InitialState::Unreachable; "unreachable neighbor")]
4712 fn resolve_cached_linked_addr<I: TestIpExt>(initial_state: InitialState) {
4713 let mut ctx = new_context::<I>();
4714 ctx.bindings_ctx.timers.assert_no_timers_installed();
4715 assert_eq!(ctx.core_ctx.inner.take_frames(), []);
4716
4717 let _ = init_neighbor_in_state(&mut ctx.core_ctx, &mut ctx.bindings_ctx, initial_state);
4718
4719 let link_addr = assert_matches!(
4720 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4721 &FakeLinkDeviceId,
4722 &I::LOOKUP_ADDR1,
4723 ),
4724 LinkResolutionResult::Resolved(addr) => addr
4725 );
4726 assert_eq!(link_addr, LINK_ADDR1);
4727 if initial_state == InitialState::Stale {
4728 assert_eq!(
4729 ctx.bindings_ctx.take_events(),
4730 [Event::changed(
4731 &FakeLinkDeviceId,
4732 EventState::Dynamic(EventDynamicState::Delay(LINK_ADDR1)),
4733 I::LOOKUP_ADDR1,
4734 ctx.bindings_ctx.now(),
4735 )],
4736 );
4737 }
4738 }
4739
4740 enum ResolutionSuccess {
4741 Confirmation,
4742 StaticEntryAdded,
4743 }
4744
4745 #[ip_test(I)]
4746 #[test_case(ResolutionSuccess::Confirmation; "incomplete entry timed out")]
4747 #[test_case(ResolutionSuccess::StaticEntryAdded; "incomplete entry removed from table")]
4748 fn dynamic_neighbor_resolution_success<I: TestIpExt>(reason: ResolutionSuccess) {
4749 let mut ctx = new_context::<I>();
4750
4751 let observers = (0..10)
4752 .map(|_| {
4753 let observer = assert_matches!(
4754 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4755 &FakeLinkDeviceId,
4756 &I::LOOKUP_ADDR1,
4757 ),
4758 LinkResolutionResult::Pending(observer) => observer
4759 );
4760 assert_eq!(*observer.lock(), None);
4761 observer
4762 })
4763 .collect::<Vec<_>>();
4764 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
4765 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4766
4767 assert_neighbor_state(
4770 core_ctx,
4771 bindings_ctx,
4772 DynamicNeighborState::Incomplete(Incomplete {
4773 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4774 pending_frames: VecDeque::new(),
4775 notifiers: Vec::new(),
4777 _marker: PhantomData,
4778 }),
4779 Some(ExpectedEvent::Added),
4780 );
4781 assert_neighbor_probe_sent(core_ctx, None);
4782
4783 match reason {
4784 ResolutionSuccess::Confirmation => {
4785 NudHandler::handle_neighbor_update(
4787 core_ctx,
4788 bindings_ctx,
4789 &FakeLinkDeviceId,
4790 I::LOOKUP_ADDR1,
4791 DynamicNeighborUpdateSource::Confirmation {
4792 link_address: Some(LINK_ADDR1),
4793 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
4794 },
4795 );
4796 let now = bindings_ctx.now();
4797 assert_neighbor_state(
4798 core_ctx,
4799 bindings_ctx,
4800 DynamicNeighborState::Reachable(Reachable {
4801 link_address: LINK_ADDR1,
4802 last_confirmed_at: now,
4803 }),
4804 Some(ExpectedEvent::Changed),
4805 );
4806 }
4807 ResolutionSuccess::StaticEntryAdded => {
4808 init_static_neighbor(core_ctx, bindings_ctx, LINK_ADDR1, ExpectedEvent::Changed);
4809 assert_eq!(
4810 core_ctx.nud.state.neighbors.get(&I::LOOKUP_ADDR1),
4811 Some(&NeighborState::Static(LINK_ADDR1))
4812 );
4813 }
4814 }
4815
4816 for observer in observers {
4818 assert_eq!(*observer.lock(), Some(Ok(LINK_ADDR1)));
4819 }
4820 }
4821
4822 enum ResolutionFailure {
4823 Timeout,
4824 Removed,
4825 }
4826
4827 #[ip_test(I)]
4828 #[test_case(ResolutionFailure::Timeout; "incomplete entry timed out")]
4829 #[test_case(ResolutionFailure::Removed; "incomplete entry removed from table")]
4830 fn dynamic_neighbor_resolution_failure<I: TestIpExt>(reason: ResolutionFailure) {
4831 let mut ctx = new_context::<I>();
4832
4833 let observers = (0..10)
4834 .map(|_| {
4835 let observer = assert_matches!(
4836 NeighborApi::new(ctx.as_mut()).resolve_link_addr(
4837 &FakeLinkDeviceId,
4838 &I::LOOKUP_ADDR1,
4839 ),
4840 LinkResolutionResult::Pending(observer) => observer
4841 );
4842 assert_eq!(*observer.lock(), None);
4843 observer
4844 })
4845 .collect::<Vec<_>>();
4846
4847 let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
4848 let max_multicast_solicit = core_ctx.inner.max_multicast_solicit().get();
4849
4850 assert_neighbor_state(
4853 core_ctx,
4854 bindings_ctx,
4855 DynamicNeighborState::Incomplete(Incomplete {
4856 transmit_counter: NonZeroU16::new(max_multicast_solicit - 1),
4857 pending_frames: VecDeque::new(),
4858 notifiers: Vec::new(),
4860 _marker: PhantomData,
4861 }),
4862 Some(ExpectedEvent::Added),
4863 );
4864 assert_neighbor_probe_sent(core_ctx, None);
4865
4866 match reason {
4867 ResolutionFailure::Timeout => {
4868 for _ in 1..=max_multicast_solicit {
4871 let retrans_timer = core_ctx.inner.retransmit_timeout().get();
4872 assert_eq!(
4873 bindings_ctx.trigger_timers_for(retrans_timer, core_ctx),
4874 [NudTimerId::neighbor()]
4875 );
4876 }
4877 }
4878 ResolutionFailure::Removed => {
4879 NudHandler::flush(core_ctx, bindings_ctx, &FakeLinkDeviceId);
4881 }
4882 }
4883
4884 assert_neighbor_removed_with_ip(core_ctx, bindings_ctx, I::LOOKUP_ADDR1);
4885 for observer in observers {
4887 assert_eq!(*observer.lock(), Some(Err(AddressResolutionFailed)));
4888 }
4889 }
4890
4891 #[ip_test(I)]
4892 #[test_case(InitialState::Incomplete, false; "incomplete neighbor")]
4893 #[test_case(InitialState::Reachable, true; "reachable neighbor")]
4894 #[test_case(InitialState::Stale, true; "stale neighbor")]
4895 #[test_case(InitialState::Delay, true; "delay neighbor")]
4896 #[test_case(InitialState::Probe, true; "probe neighbor")]
4897 #[test_case(InitialState::Unreachable, true; "unreachable neighbor")]
4898 fn upper_layer_confirmation<I: TestIpExt>(
4899 initial_state: InitialState,
4900 should_transition_to_reachable: bool,
4901 ) {
4902 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
4903 let base_reachable_time = core_ctx.inner.base_reachable_time().get();
4904
4905 let initial = init_neighbor_in_state(&mut core_ctx, &mut bindings_ctx, initial_state);
4906
4907 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
4908
4909 if !should_transition_to_reachable {
4910 assert_neighbor_state(&core_ctx, &mut bindings_ctx, initial, None);
4911 return;
4912 }
4913
4914 let now = bindings_ctx.now();
4916 assert_neighbor_state(
4917 &core_ctx,
4918 &mut bindings_ctx,
4919 DynamicNeighborState::Reachable(Reachable {
4920 link_address: LINK_ADDR1,
4921 last_confirmed_at: now,
4922 }),
4923 (initial_state != InitialState::Reachable).then_some(ExpectedEvent::Changed),
4924 );
4925 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4926 &mut bindings_ctx,
4927 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time)],
4928 );
4929
4930 bindings_ctx.timers.instant.sleep(base_reachable_time / 2);
4934 confirm_reachable(&mut core_ctx, &mut bindings_ctx, &FakeLinkDeviceId, I::LOOKUP_ADDR1);
4935 let now = bindings_ctx.now();
4936 assert_neighbor_state(
4937 &core_ctx,
4938 &mut bindings_ctx,
4939 DynamicNeighborState::Reachable(Reachable {
4940 link_address: LINK_ADDR1,
4941 last_confirmed_at: now,
4942 }),
4943 None,
4944 );
4945 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4946 &mut bindings_ctx,
4947 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
4948 );
4949
4950 assert_eq!(
4953 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
4954 [NudTimerId::neighbor()]
4955 );
4956 let now = bindings_ctx.now();
4957 assert_neighbor_state(
4958 &core_ctx,
4959 &mut bindings_ctx,
4960 DynamicNeighborState::Reachable(Reachable {
4961 link_address: LINK_ADDR1,
4962 last_confirmed_at: now - base_reachable_time / 2,
4963 }),
4964 None,
4965 );
4966
4967 core_ctx.nud.state.timer_heap.neighbor.assert_timers_after(
4968 &mut bindings_ctx,
4969 [(I::LOOKUP_ADDR1, NudEvent::ReachableTime, base_reachable_time / 2)],
4970 );
4971
4972 assert_eq!(
4975 bindings_ctx.trigger_timers_for(base_reachable_time / 2, &mut core_ctx,),
4976 [NudTimerId::neighbor()]
4977 );
4978 assert_neighbor_state(
4979 &core_ctx,
4980 &mut bindings_ctx,
4981 DynamicNeighborState::Stale(Stale { link_address: LINK_ADDR1 }),
4982 Some(ExpectedEvent::Changed),
4983 );
4984 bindings_ctx.timers.assert_no_timers_installed();
4985 }
4986
4987 fn generate_ip_addr<I: Ip>(i: usize) -> SpecifiedAddr<I::Addr> {
4988 I::map_ip_out(
4989 i,
4990 |i| {
4991 let start = u32::from_be_bytes(net_ip_v4!("192.168.0.1").ipv4_bytes());
4992 let bytes = (start + u32::try_from(i).unwrap()).to_be_bytes();
4993 SpecifiedAddr::new(Ipv4Addr::new(bytes)).unwrap()
4994 },
4995 |i| {
4996 let start = u128::from_be_bytes(net_ip_v6!("fe80::1").ipv6_bytes());
4997 let bytes = (start + u128::try_from(i).unwrap()).to_be_bytes();
4998 SpecifiedAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap()
4999 },
5000 )
5001 }
5002
5003 #[ip_test(I)]
5004 fn garbage_collection_retains_static_entries<I: TestIpExt>() {
5005 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5006
5007 for i in 0..MAX_ENTRIES * 2 {
5011 if i % 2 == 0 {
5012 init_stale_neighbor_with_ip(
5013 &mut core_ctx,
5014 &mut bindings_ctx,
5015 generate_ip_addr::<I>(i),
5016 LINK_ADDR1,
5017 );
5018 } else {
5019 init_static_neighbor_with_ip(
5020 &mut core_ctx,
5021 &mut bindings_ctx,
5022 generate_ip_addr::<I>(i),
5023 LINK_ADDR1,
5024 ExpectedEvent::Added,
5025 );
5026 }
5027 }
5028 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES * 2);
5029
5030 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5032 for event in bindings_ctx.take_events() {
5033 assert_matches!(event, Event {
5034 device,
5035 addr: _,
5036 kind,
5037 at,
5038 } => {
5039 assert_eq!(kind, EventKind::Removed);
5040 assert_eq!(device, FakeLinkDeviceId);
5041 assert_eq!(at, bindings_ctx.now());
5042 });
5043 }
5044 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5045 for (_, neighbor) in core_ctx.nud.state.neighbors {
5046 assert_matches!(neighbor, NeighborState::Static(_));
5047 }
5048 }
5049
5050 #[ip_test(I)]
5051 fn garbage_collection_retains_in_use_entries<I: TestIpExt>() {
5052 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5053
5054 for i in 0..MAX_ENTRIES - 1 {
5056 init_static_neighbor_with_ip(
5057 &mut core_ctx,
5058 &mut bindings_ctx,
5059 generate_ip_addr::<I>(i),
5060 LINK_ADDR1,
5061 ExpectedEvent::Added,
5062 );
5063 }
5064
5065 let stale_entry = generate_ip_addr::<I>(MAX_ENTRIES - 1);
5067 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry, LINK_ADDR1);
5068 let reachable_entry = generate_ip_addr::<I>(MAX_ENTRIES);
5070 init_reachable_neighbor_with_ip(
5071 &mut core_ctx,
5072 &mut bindings_ctx,
5073 reachable_entry,
5074 LINK_ADDR1,
5075 );
5076
5077 collect_garbage(&mut core_ctx, &mut bindings_ctx, FakeLinkDeviceId);
5079 super::testutil::assert_dynamic_neighbor_state(
5080 &mut core_ctx,
5081 FakeLinkDeviceId,
5082 reachable_entry,
5083 DynamicNeighborState::Reachable(Reachable {
5084 link_address: LINK_ADDR1,
5085 last_confirmed_at: bindings_ctx.now(),
5086 }),
5087 );
5088 assert_neighbor_removed_with_ip(&mut core_ctx, &mut bindings_ctx, stale_entry);
5089 }
5090
5091 #[ip_test(I)]
5092 fn garbage_collection_triggered_on_new_stale_entry<I: TestIpExt>() {
5093 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5094 core_ctx.nud.state.last_gc = Some(bindings_ctx.now());
5096
5097 for i in 0..MAX_ENTRIES {
5099 init_static_neighbor_with_ip(
5100 &mut core_ctx,
5101 &mut bindings_ctx,
5102 generate_ip_addr::<I>(i),
5103 LINK_ADDR1,
5104 ExpectedEvent::Added,
5105 );
5106 }
5107
5108 init_stale_neighbor_with_ip(
5111 &mut core_ctx,
5112 &mut bindings_ctx,
5113 generate_ip_addr::<I>(MAX_ENTRIES + 1),
5114 LINK_ADDR1,
5115 );
5116 let expected_gc_time = bindings_ctx.now() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5117 bindings_ctx
5118 .timers
5119 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5120
5121 bindings_ctx.timers.instant.sleep(ONE_SECOND.get());
5125 init_stale_neighbor_with_ip(
5126 &mut core_ctx,
5127 &mut bindings_ctx,
5128 generate_ip_addr::<I>(MAX_ENTRIES + 2),
5129 LINK_ADDR1,
5130 );
5131 bindings_ctx
5132 .timers
5133 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5134 }
5135
5136 #[ip_test(I)]
5137 fn garbage_collection_triggered_on_transition_to_unreachable<I: TestIpExt>() {
5138 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5139 core_ctx.nud.state.last_gc = Some(bindings_ctx.now());
5141
5142 for i in 0..MAX_ENTRIES {
5144 init_static_neighbor_with_ip(
5145 &mut core_ctx,
5146 &mut bindings_ctx,
5147 generate_ip_addr::<I>(i),
5148 LINK_ADDR1,
5149 ExpectedEvent::Added,
5150 );
5151 }
5152 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5153
5154 init_unreachable_neighbor_with_ip(
5157 &mut core_ctx,
5158 &mut bindings_ctx,
5159 generate_ip_addr::<I>(MAX_ENTRIES),
5160 LINK_ADDR1,
5161 );
5162 let expected_gc_time =
5163 core_ctx.nud.state.last_gc.unwrap() + MIN_GARBAGE_COLLECTION_INTERVAL.get();
5164 bindings_ctx
5165 .timers
5166 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5167
5168 init_unreachable_neighbor_with_ip(
5171 &mut core_ctx,
5172 &mut bindings_ctx,
5173 generate_ip_addr::<I>(MAX_ENTRIES + 1),
5174 LINK_ADDR1,
5175 );
5176 bindings_ctx
5177 .timers
5178 .assert_some_timers_installed([(NudTimerId::garbage_collection(), expected_gc_time)]);
5179 }
5180
5181 #[ip_test(I)]
5182 fn garbage_collection_not_triggered_on_new_incomplete_entry<I: TestIpExt>() {
5183 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5184
5185 for i in 0..MAX_ENTRIES {
5187 init_static_neighbor_with_ip(
5188 &mut core_ctx,
5189 &mut bindings_ctx,
5190 generate_ip_addr::<I>(i),
5191 LINK_ADDR1,
5192 ExpectedEvent::Added,
5193 );
5194 }
5195 assert_eq!(core_ctx.nud.state.neighbors.len(), MAX_ENTRIES);
5196
5197 let _: VecDeque<Buf<Vec<u8>>> = init_incomplete_neighbor_with_ip(
5198 &mut core_ctx,
5199 &mut bindings_ctx,
5200 generate_ip_addr::<I>(MAX_ENTRIES),
5201 true,
5202 );
5203 assert_eq!(
5204 bindings_ctx.timers.scheduled_instant(&mut core_ctx.nud.state.timer_heap.gc),
5205 None
5206 );
5207 }
5208
5209 #[ip_test(I)]
5210 fn confirmation_processed_even_if_no_target_link_layer_addr<I: TestIpExt>() {
5211 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context::<I>();
5212
5213 init_stale_neighbor_with_ip(&mut core_ctx, &mut bindings_ctx, I::LOOKUP_ADDR1, LINK_ADDR1);
5215
5216 NudHandler::handle_neighbor_update(
5220 &mut core_ctx,
5221 &mut bindings_ctx,
5222 &FakeLinkDeviceId,
5223 I::LOOKUP_ADDR1,
5224 DynamicNeighborUpdateSource::Confirmation {
5225 link_address: None,
5226 flags: ConfirmationFlags { solicited_flag: true, override_flag: false },
5227 },
5228 );
5229 let now = bindings_ctx.now();
5230 assert_neighbor_state(
5231 &core_ctx,
5232 &mut bindings_ctx,
5233 DynamicNeighborState::Reachable(Reachable {
5234 link_address: LINK_ADDR1,
5235 last_confirmed_at: now,
5236 }),
5237 Some(ExpectedEvent::Changed),
5238 );
5239 }
5240}