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