1use core::fmt::Display;
8use core::marker::PhantomData;
9
10use net_types::ip::{Ip, IpAddress, IpVersionMarker, Ipv4, Ipv6};
11use net_types::{SpecifiedAddr, UnicastAddr, UnicastAddress as _, Witness as _};
12use netstack3_base::{
13 ContextPair, DeviceIdContext, EventContext as _, Inspector, InstantContext as _, LinkDevice,
14 NotFoundError,
15};
16use thiserror::Error;
17
18use crate::internal::device::nud::{
19 Delay, DynamicNeighborState, EnterProbeError, Entry, Event, Incomplete, LinkResolutionContext,
20 LinkResolutionNotifier, LinkResolutionResult, NeighborState, NudBindingsContext, NudContext,
21 NudHandler, NudState, Probe, Reachable, Stale, Unreachable,
22};
23
24#[derive(Debug, PartialEq, Eq, Error)]
26pub enum StaticNeighborInsertionError {
27 #[error("IP address is invalid")]
35 IpAddressInvalid,
36}
37
38#[derive(Debug, PartialEq, Eq, Error)]
40pub enum TriggerNeighborProbeError {
41 #[error("IP address is invalid")]
43 IpAddressInvalid,
44
45 #[error(transparent)]
47 NotFound(#[from] NotFoundError),
48
49 #[error("link address is unknown")]
51 LinkAddressUnknown,
52}
53
54#[derive(Debug, PartialEq, Eq, Error)]
56pub enum NeighborRemovalError {
57 #[error("IP address is invalid")]
59 IpAddressInvalid,
60
61 #[error(transparent)]
63 NotFound(#[from] NotFoundError),
64}
65
66fn validate_neighbor_addr<A: IpAddress>(addr: A) -> Option<SpecifiedAddr<A>> {
68 let is_valid: bool = A::Version::map_ip(
69 addr,
70 |v4| {
71 !Ipv4::LOOPBACK_SUBNET.contains(&v4)
72 && !Ipv4::MULTICAST_SUBNET.contains(&v4)
73 && v4 != Ipv4::LIMITED_BROADCAST_ADDRESS.get()
74 },
75 |v6| v6 != Ipv6::LOOPBACK_ADDRESS.get() && v6.to_ipv4_mapped().is_none() && v6.is_unicast(),
76 );
77 is_valid.then_some(()).and_then(|()| SpecifiedAddr::new(addr))
78}
79
80pub struct NeighborApi<I: Ip, D, C>(C, IpVersionMarker<I>, PhantomData<D>);
82
83impl<I: Ip, D, C> NeighborApi<I, D, C> {
84 pub fn new(ctx: C) -> Self {
86 Self(ctx, IpVersionMarker::new(), PhantomData)
87 }
88}
89
90impl<I, D, C> NeighborApi<I, D, C>
91where
92 I: Ip,
93 D: LinkDevice,
94 C: ContextPair,
95 C::CoreContext: NudContext<I, D, C::BindingsContext>,
96 C::BindingsContext: NudBindingsContext<I, D, <C::CoreContext as DeviceIdContext<D>>::DeviceId>,
97{
98 fn core_ctx(&mut self) -> &mut C::CoreContext {
99 let Self(pair, IpVersionMarker { .. }, PhantomData) = self;
100 pair.core_ctx()
101 }
102
103 fn contexts(&mut self) -> (&mut C::CoreContext, &mut C::BindingsContext) {
104 let Self(pair, IpVersionMarker { .. }, PhantomData) = self;
105 pair.contexts()
106 }
107
108 pub fn resolve_link_addr(
115 &mut self,
116 device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
117 dst: &SpecifiedAddr<I::Addr>,
121 ) -> LinkResolutionResult<
122 UnicastAddr<D::Address>,
123 <<C::BindingsContext as LinkResolutionContext<D>>::Notifier as LinkResolutionNotifier<
124 D,
125 >>::Observer,
126 >{
127 let (core_ctx, bindings_ctx) = self.contexts();
128 let (result, do_multicast_solicit) = core_ctx.with_nud_state_mut(
129 device_id,
130 |NudState { neighbors, timer_heap, .. }, core_ctx| {
131 match neighbors.entry(*dst) {
132 Entry::Vacant(entry) => {
133 let (notifier, observer) =
135 <C::BindingsContext as LinkResolutionContext<D>>::Notifier::new();
136 let state = entry.insert(NeighborState::Dynamic(
137 DynamicNeighborState::Incomplete(Incomplete::new_with_notifier(
138 core_ctx,
139 bindings_ctx,
140 timer_heap,
141 *dst,
142 notifier,
143 )),
144 ));
145 bindings_ctx.on_event(Event::added(
146 device_id,
147 state.to_event_state(),
148 *dst,
149 bindings_ctx.now(),
150 ));
151 (LinkResolutionResult::Pending(observer), true)
152 }
153 Entry::Occupied(e) => match e.into_mut() {
154 NeighborState::Static(link_address) => {
155 (LinkResolutionResult::Resolved(*link_address), false)
156 }
157 NeighborState::Dynamic(e) => {
158 e.resolve_link_addr(core_ctx, bindings_ctx, timer_heap, device_id, *dst)
159 }
160 },
161 }
162 },
163 );
164
165 if do_multicast_solicit {
166 core_ctx.send_neighbor_solicitation(
167 bindings_ctx,
168 &device_id,
169 *dst,
170 None,
171 );
172 }
173
174 result
175 }
176
177 pub fn flush_table(&mut self, device: &<C::CoreContext as DeviceIdContext<D>>::DeviceId) {
179 let (core_ctx, bindings_ctx) = self.contexts();
180 NudHandler::<I, D, _>::flush(core_ctx, bindings_ctx, device)
181 }
182
183 pub fn insert_static_entry(
191 &mut self,
192 device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
193 neighbor: I::Addr,
194 link_address: UnicastAddr<D::Address>,
199 ) -> Result<(), StaticNeighborInsertionError> {
200 let neighbor = validate_neighbor_addr(neighbor)
201 .ok_or(StaticNeighborInsertionError::IpAddressInvalid)?;
202 let (core_ctx, bindings_ctx) = self.contexts();
203
204 core_ctx.with_nud_state_mut_and_sender_ctx(
205 device_id,
206 |NudState { neighbors, last_gc: _, timer_heap }, core_ctx| match neighbors
207 .entry(neighbor)
208 {
209 Entry::Occupied(mut occupied) => {
210 let previous =
211 core::mem::replace(occupied.get_mut(), NeighborState::Static(link_address));
212 let event_state = occupied.get().to_event_state();
213 if event_state != previous.to_event_state() {
214 bindings_ctx.on_event(Event::changed(
215 device_id,
216 event_state,
217 neighbor,
218 bindings_ctx.now(),
219 ));
220 }
221 match previous {
222 NeighborState::Dynamic(entry) => {
223 entry.cancel_timer_and_complete_resolution(
224 core_ctx,
225 bindings_ctx,
226 timer_heap,
227 neighbor,
228 link_address,
229 );
230 }
231 NeighborState::Static(_) => {}
232 }
233 }
234 Entry::Vacant(vacant) => {
235 let state = vacant.insert(NeighborState::Static(link_address));
236 let event = Event::added(
237 device_id,
238 state.to_event_state(),
239 neighbor,
240 bindings_ctx.now(),
241 );
242 bindings_ctx.on_event(event);
243 }
244 },
245 );
246 Ok(())
247 }
248
249 pub fn probe_entry(
258 &mut self,
259 device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
260 neighbor: I::Addr,
261 ) -> Result<(), TriggerNeighborProbeError> {
262 let (core_ctx, bindings_ctx) = self.contexts();
263 let neighbor =
264 validate_neighbor_addr(neighbor).ok_or(TriggerNeighborProbeError::IpAddressInvalid)?;
265
266 let probe_to_send = core_ctx.with_nud_state_mut(device_id, |nud_state, config_ctx| {
267 let mut neighbor_state = match nud_state.neighbors.entry(neighbor) {
268 Entry::Occupied(neighbor_state) => neighbor_state,
269 Entry::Vacant(_) => return Err(TriggerNeighborProbeError::NotFound(NotFoundError)),
270 };
271 neighbor_state
272 .get_mut()
273 .enter_probe(
274 config_ctx,
275 bindings_ctx,
276 &mut nud_state.timer_heap,
277 neighbor,
278 device_id,
279 )
280 .map_err(|e| match e {
281 EnterProbeError::LinkAddressUnknown => {
282 TriggerNeighborProbeError::LinkAddressUnknown
283 }
284 })
285 })?;
286 match probe_to_send {
287 Some(link_addr) => core_ctx.send_neighbor_solicitation(
288 bindings_ctx,
289 &device_id,
290 neighbor,
291 Some(link_addr),
292 ),
293 None => {}
294 }
295 Ok(())
296 }
297
298 pub fn remove_entry(
300 &mut self,
301 device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
302 neighbor: I::Addr,
307 ) -> Result<(), NeighborRemovalError> {
308 let (core_ctx, bindings_ctx) = self.contexts();
309 let neighbor =
310 validate_neighbor_addr(neighbor).ok_or(NeighborRemovalError::IpAddressInvalid)?;
311
312 core_ctx.with_nud_state_mut(
313 device_id,
314 |NudState { neighbors, last_gc: _, timer_heap }, _config| {
315 match neighbors.remove(&neighbor).ok_or(NotFoundError)? {
316 NeighborState::Dynamic(mut entry) => {
317 entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
318 }
319 NeighborState::Static(_) => {}
320 }
321 bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
322 Ok(())
323 },
324 )
325 }
326
327 pub fn inspect_neighbors<N: Inspector>(
329 &mut self,
330 device: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
331 inspector: &mut N,
332 ) where
333 D::Address: Display,
334 {
335 self.core_ctx().with_nud_state(device, |nud| {
336 nud.neighbors.iter().for_each(|(ip_address, state)| {
337 let (state, link_address, last_confirmed_at) = match state {
338 NeighborState::Static(addr) => ("Static", Some(addr), None),
339 NeighborState::Dynamic(dynamic_state) => match dynamic_state {
340 DynamicNeighborState::Incomplete(Incomplete {
341 transmit_counter: _,
342 pending_frames: _,
343 notifiers: _,
344 _marker,
345 }) => ("Incomplete", None, None),
346 DynamicNeighborState::Reachable(Reachable {
347 link_address,
348 last_confirmed_at,
349 }) => ("Reachable", Some(link_address), Some(last_confirmed_at)),
350 DynamicNeighborState::Stale(Stale { link_address }) => {
351 ("Stale", Some(link_address), None)
352 }
353 DynamicNeighborState::Delay(Delay { link_address }) => {
354 ("Delay", Some(link_address), None)
355 }
356 DynamicNeighborState::Probe(Probe {
357 link_address,
358 transmit_counter: _,
359 }) => ("Probe", Some(link_address), None),
360 DynamicNeighborState::Unreachable(Unreachable {
361 link_address,
362 mode: _,
363 }) => ("Unreachable", Some(link_address), None),
364 },
365 };
366 inspector.record_unnamed_child(|inspector| {
367 inspector.record_str("State", state);
368 inspector.record_ip_addr("IpAddress", ip_address.get());
369 if let Some(link_address) = link_address {
370 inspector.record_display("LinkAddress", link_address);
371 };
372 if let Some(last_confirmed_at) = last_confirmed_at {
373 inspector.record_inspectable_value("LastConfirmedAt", last_confirmed_at);
374 }
375 });
376 })
377 })
378 }
379}