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