1use core::fmt::Display;
8use core::marker::PhantomData;
9
10use log::warn;
11use net_types::ip::{Ip, IpAddress, IpVersionMarker, Ipv4, Ipv6};
12use net_types::{SpecifiedAddr, UnicastAddr, UnicastAddress as _, Witness as _};
13use netstack3_base::{
14 ContextPair, DeviceIdContext, EventContext as _, Inspector, InstantContext as _, LinkDevice,
15 NotFoundError,
16};
17use thiserror::Error;
18
19use crate::internal::device::nud::{
20 Delay, DynamicNeighborState, EnterProbeError, Entry, Event, Incomplete, LinkResolutionContext,
21 LinkResolutionNotifier, LinkResolutionResult, NeighborState, NudBindingsContext, NudContext,
22 NudHandler, NudState, Probe, Reachable, Stale, TableFullError, Unreachable,
23};
24
25#[derive(Debug, PartialEq, Eq, Error)]
27pub enum StaticNeighborInsertionError {
28 #[error("IP address is invalid")]
36 IpAddressInvalid,
37
38 #[error("The neighbor table is full")]
40 TableFull,
41}
42
43#[derive(Debug, PartialEq, Eq, Error)]
45pub enum TriggerNeighborProbeError {
46 #[error("IP address is invalid")]
48 IpAddressInvalid,
49
50 #[error(transparent)]
52 NotFound(#[from] NotFoundError),
53
54 #[error("link address is unknown")]
56 LinkAddressUnknown,
57}
58
59#[derive(Debug, PartialEq, Eq, Error)]
61pub enum NeighborRemovalError {
62 #[error("IP address is invalid")]
64 IpAddressInvalid,
65
66 #[error(transparent)]
68 NotFound(#[from] NotFoundError),
69}
70
71fn validate_neighbor_addr<A: IpAddress>(addr: A) -> Option<SpecifiedAddr<A>> {
73 let is_valid: bool = A::Version::map_ip(
74 addr,
75 |v4| {
76 !Ipv4::LOOPBACK_SUBNET.contains(&v4)
77 && !Ipv4::MULTICAST_SUBNET.contains(&v4)
78 && v4 != Ipv4::LIMITED_BROADCAST_ADDRESS.get()
79 },
80 |v6| v6 != Ipv6::LOOPBACK_ADDRESS.get() && v6.to_ipv4_mapped().is_none() && v6.is_unicast(),
81 );
82 is_valid.then_some(()).and_then(|()| SpecifiedAddr::new(addr))
83}
84
85pub struct NeighborApi<I: Ip, D, C>(C, IpVersionMarker<I>, PhantomData<D>);
87
88impl<I: Ip, D, C> NeighborApi<I, D, C> {
89 pub fn new(ctx: C) -> Self {
91 Self(ctx, IpVersionMarker::new(), PhantomData)
92 }
93}
94
95impl<I, D, C> NeighborApi<I, D, C>
96where
97 I: Ip,
98 D: LinkDevice,
99 C: ContextPair,
100 C::CoreContext: NudContext<I, D, C::BindingsContext>,
101 C::BindingsContext: NudBindingsContext<I, D, <C::CoreContext as DeviceIdContext<D>>::DeviceId>,
102{
103 fn core_ctx(&mut self) -> &mut C::CoreContext {
104 let Self(pair, IpVersionMarker { .. }, PhantomData) = self;
105 pair.core_ctx()
106 }
107
108 fn contexts(&mut self) -> (&mut C::CoreContext, &mut C::BindingsContext) {
109 let Self(pair, IpVersionMarker { .. }, PhantomData) = self;
110 pair.contexts()
111 }
112
113 pub fn resolve_link_addr(
120 &mut self,
121 device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
122 dst: &SpecifiedAddr<I::Addr>,
126 ) -> LinkResolutionResult<
127 UnicastAddr<D::Address>,
128 <<C::BindingsContext as LinkResolutionContext<D>>::Notifier as LinkResolutionNotifier<
129 D,
130 >>::Observer,
131 >{
132 let (core_ctx, bindings_ctx) = self.contexts();
133 let (result, do_multicast_solicit) = core_ctx.with_nud_state_mut(
134 device_id,
135 |NudState { neighbors, gc_state, timer_heap }, core_ctx| match neighbors.get_mut(dst) {
136 None => {
137 let (notifier, observer) =
139 <C::BindingsContext as LinkResolutionContext<D>>::Notifier::new();
140 let neighbor = NeighborState::Dynamic(DynamicNeighborState::Incomplete(
141 Incomplete::new_with_notifier(
142 core_ctx,
143 bindings_ctx,
144 timer_heap,
145 *dst,
146 notifier,
147 ),
148 ));
149 let result = crate::internal::device::nud::insert_new_entry(
150 neighbors,
151 gc_state,
152 timer_heap,
153 bindings_ctx,
154 device_id,
155 *dst,
156 neighbor,
157 );
158 match result {
159 Ok(_entry) => (LinkResolutionResult::Pending(observer), true),
160 Err(TableFullError { entry }) => {
161 warn!("Neighbor table full; failed to insert {entry:?}");
162 let (notifier, observer) =
163 <C::BindingsContext as LinkResolutionContext<D>>::Notifier::new();
164 notifier.notify(Err(netstack3_base::AddressResolutionFailed));
165 (LinkResolutionResult::Pending(observer), false)
166 }
167 }
168 }
169 Some(entry) => match entry {
170 NeighborState::Static(link_address) => {
171 (LinkResolutionResult::Resolved(*link_address), false)
172 }
173 NeighborState::Dynamic(e) => {
174 e.resolve_link_addr(core_ctx, bindings_ctx, timer_heap, device_id, *dst)
175 }
176 },
177 },
178 );
179
180 if do_multicast_solicit {
181 core_ctx.send_neighbor_solicitation(
182 bindings_ctx,
183 &device_id,
184 *dst,
185 None,
186 );
187 }
188
189 result
190 }
191
192 pub fn flush_table(&mut self, device: &<C::CoreContext as DeviceIdContext<D>>::DeviceId) {
194 let (core_ctx, bindings_ctx) = self.contexts();
195 NudHandler::<I, D, _>::flush(core_ctx, bindings_ctx, device)
196 }
197
198 pub fn insert_static_entry(
206 &mut self,
207 device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
208 neighbor: I::Addr,
209 link_address: UnicastAddr<D::Address>,
214 ) -> Result<(), StaticNeighborInsertionError> {
215 let neighbor = validate_neighbor_addr(neighbor)
216 .ok_or(StaticNeighborInsertionError::IpAddressInvalid)?;
217 let (core_ctx, bindings_ctx) = self.contexts();
218
219 core_ctx.with_nud_state_mut_and_sender_ctx(
220 device_id,
221 |NudState { neighbors, gc_state, timer_heap }, core_ctx| match neighbors
222 .get_mut(&neighbor)
223 {
224 Some(entry) => {
225 let previous = core::mem::replace(entry, NeighborState::Static(link_address));
226 let event_state = entry.to_event_state();
227 if event_state != previous.to_event_state() {
228 bindings_ctx.on_event(Event::changed(
229 device_id,
230 event_state,
231 neighbor,
232 bindings_ctx.now(),
233 ));
234 }
235 match previous {
236 NeighborState::Dynamic(entry) => {
237 entry.cancel_timer_and_complete_resolution(
238 core_ctx,
239 bindings_ctx,
240 timer_heap,
241 neighbor,
242 link_address,
243 );
244 }
245 NeighborState::Static(_) => {}
246 }
247 Ok(())
248 }
249 None => {
250 let neighbor_state = NeighborState::Static(link_address);
251 let result = crate::internal::device::nud::insert_new_entry(
252 neighbors,
253 gc_state,
254 timer_heap,
255 bindings_ctx,
256 device_id,
257 neighbor,
258 neighbor_state,
259 );
260 match result {
261 Ok(_entry) => Ok(()),
262 Err(TableFullError { entry }) => {
263 warn!("Neighbor table full; failed to insert {entry:?}");
264 Err(StaticNeighborInsertionError::TableFull)
265 }
266 }
267 }
268 },
269 )
270 }
271
272 pub fn probe_entry(
281 &mut self,
282 device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
283 neighbor: I::Addr,
284 ) -> Result<(), TriggerNeighborProbeError> {
285 let (core_ctx, bindings_ctx) = self.contexts();
286 let neighbor =
287 validate_neighbor_addr(neighbor).ok_or(TriggerNeighborProbeError::IpAddressInvalid)?;
288
289 let probe_to_send = core_ctx.with_nud_state_mut(device_id, |nud_state, config_ctx| {
290 let mut neighbor_state = match nud_state.neighbors.entry(neighbor) {
291 Entry::Occupied(neighbor_state) => neighbor_state,
292 Entry::Vacant(_) => return Err(TriggerNeighborProbeError::NotFound(NotFoundError)),
293 };
294 neighbor_state
295 .get_mut()
296 .enter_probe(
297 config_ctx,
298 bindings_ctx,
299 &mut nud_state.timer_heap,
300 neighbor,
301 device_id,
302 )
303 .map_err(|e| match e {
304 EnterProbeError::LinkAddressUnknown => {
305 TriggerNeighborProbeError::LinkAddressUnknown
306 }
307 })
308 })?;
309 match probe_to_send {
310 Some(link_addr) => core_ctx.send_neighbor_solicitation(
311 bindings_ctx,
312 &device_id,
313 neighbor,
314 Some(link_addr),
315 ),
316 None => {}
317 }
318 Ok(())
319 }
320
321 pub fn remove_entry(
323 &mut self,
324 device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
325 neighbor: I::Addr,
330 ) -> Result<(), NeighborRemovalError> {
331 let (core_ctx, bindings_ctx) = self.contexts();
332 let neighbor =
333 validate_neighbor_addr(neighbor).ok_or(NeighborRemovalError::IpAddressInvalid)?;
334
335 core_ctx.with_nud_state_mut(
336 device_id,
337 |NudState { neighbors, gc_state: _, timer_heap }, _config| {
338 match neighbors.remove(&neighbor).ok_or(NotFoundError)? {
339 NeighborState::Dynamic(mut entry) => {
340 entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
341 }
342 NeighborState::Static(_) => {}
343 }
344 bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
345 Ok(())
346 },
347 )
348 }
349
350 pub fn inspect_neighbors<N: Inspector>(
352 &mut self,
353 device: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
354 inspector: &mut N,
355 ) where
356 D::Address: Display,
357 {
358 self.core_ctx().with_nud_state(device, |nud| {
359 nud.neighbors.iter().for_each(|(ip_address, state)| {
360 let (state, link_address, last_confirmed_at) = match state {
361 NeighborState::Static(addr) => ("Static", Some(addr), None),
362 NeighborState::Dynamic(dynamic_state) => match dynamic_state {
363 DynamicNeighborState::Incomplete(Incomplete {
364 transmit_counter: _,
365 pending_frames: _,
366 notifiers: _,
367 _marker,
368 }) => ("Incomplete", None, None),
369 DynamicNeighborState::Reachable(Reachable {
370 link_address,
371 last_confirmed_at,
372 }) => ("Reachable", Some(link_address), Some(last_confirmed_at)),
373 DynamicNeighborState::Stale(Stale { link_address }) => {
374 ("Stale", Some(link_address), None)
375 }
376 DynamicNeighborState::Delay(Delay { link_address }) => {
377 ("Delay", Some(link_address), None)
378 }
379 DynamicNeighborState::Probe(Probe {
380 link_address,
381 transmit_counter: _,
382 }) => ("Probe", Some(link_address), None),
383 DynamicNeighborState::Unreachable(Unreachable {
384 link_address,
385 mode: _,
386 }) => ("Unreachable", Some(link_address), None),
387 },
388 };
389 inspector.record_unnamed_child(|inspector| {
390 inspector.record_str("State", state);
391 inspector.record_ip_addr("IpAddress", ip_address.get());
392 if let Some(link_address) = link_address {
393 inspector.record_display("LinkAddress", link_address);
394 };
395 if let Some(last_confirmed_at) = last_confirmed_at {
396 inspector.record_inspectable_value("LastConfirmedAt", last_confirmed_at);
397 }
398 });
399 })
400 })
401 }
402}