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, 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 NeighborRemovalError {
45 #[error("IP address is invalid")]
47 IpAddressInvalid,
48
49 #[error(transparent)]
51 NotFound(#[from] NotFoundError),
52}
53
54fn validate_neighbor_addr<A: IpAddress>(addr: A) -> Option<SpecifiedAddr<A>> {
56 let is_valid: bool = A::Version::map_ip(
57 addr,
58 |v4| {
59 !Ipv4::LOOPBACK_SUBNET.contains(&v4)
60 && !Ipv4::MULTICAST_SUBNET.contains(&v4)
61 && v4 != Ipv4::LIMITED_BROADCAST_ADDRESS.get()
62 },
63 |v6| v6 != Ipv6::LOOPBACK_ADDRESS.get() && v6.to_ipv4_mapped().is_none() && v6.is_unicast(),
64 );
65 is_valid.then_some(()).and_then(|()| SpecifiedAddr::new(addr))
66}
67
68pub struct NeighborApi<I: Ip, D, C>(C, IpVersionMarker<I>, PhantomData<D>);
70
71impl<I: Ip, D, C> NeighborApi<I, D, C> {
72 pub fn new(ctx: C) -> Self {
74 Self(ctx, IpVersionMarker::new(), PhantomData)
75 }
76}
77
78impl<I, D, C> NeighborApi<I, D, C>
79where
80 I: Ip,
81 D: LinkDevice,
82 C: ContextPair,
83 C::CoreContext: NudContext<I, D, C::BindingsContext>,
84 C::BindingsContext: NudBindingsContext<I, D, <C::CoreContext as DeviceIdContext<D>>::DeviceId>,
85{
86 fn core_ctx(&mut self) -> &mut C::CoreContext {
87 let Self(pair, IpVersionMarker { .. }, PhantomData) = self;
88 pair.core_ctx()
89 }
90
91 fn contexts(&mut self) -> (&mut C::CoreContext, &mut C::BindingsContext) {
92 let Self(pair, IpVersionMarker { .. }, PhantomData) = self;
93 pair.contexts()
94 }
95
96 pub fn resolve_link_addr(
103 &mut self,
104 device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
105 dst: &SpecifiedAddr<I::Addr>,
109 ) -> LinkResolutionResult<
110 D::Address,
111 <<C::BindingsContext as LinkResolutionContext<D>>::Notifier as LinkResolutionNotifier<
112 D,
113 >>::Observer,
114 >{
115 let (core_ctx, bindings_ctx) = self.contexts();
116 let (result, do_multicast_solicit) = core_ctx.with_nud_state_mut(
117 device_id,
118 |NudState { neighbors, timer_heap, .. }, core_ctx| {
119 match neighbors.entry(*dst) {
120 Entry::Vacant(entry) => {
121 let (notifier, observer) =
123 <C::BindingsContext as LinkResolutionContext<D>>::Notifier::new();
124 let state = entry.insert(NeighborState::Dynamic(
125 DynamicNeighborState::Incomplete(Incomplete::new_with_notifier(
126 core_ctx,
127 bindings_ctx,
128 timer_heap,
129 *dst,
130 notifier,
131 )),
132 ));
133 bindings_ctx.on_event(Event::added(
134 device_id,
135 state.to_event_state(),
136 *dst,
137 bindings_ctx.now(),
138 ));
139 (LinkResolutionResult::Pending(observer), true)
140 }
141 Entry::Occupied(e) => match e.into_mut() {
142 NeighborState::Static(link_address) => {
143 (LinkResolutionResult::Resolved(*link_address), false)
144 }
145 NeighborState::Dynamic(e) => {
146 e.resolve_link_addr(core_ctx, bindings_ctx, timer_heap, device_id, *dst)
147 }
148 },
149 }
150 },
151 );
152
153 if do_multicast_solicit {
154 core_ctx.send_neighbor_solicitation(
155 bindings_ctx,
156 &device_id,
157 *dst,
158 None,
159 );
160 }
161
162 result
163 }
164
165 pub fn flush_table(&mut self, device: &<C::CoreContext as DeviceIdContext<D>>::DeviceId) {
167 let (core_ctx, bindings_ctx) = self.contexts();
168 NudHandler::<I, D, _>::flush(core_ctx, bindings_ctx, device)
169 }
170
171 pub fn insert_static_entry(
179 &mut self,
180 device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
181 neighbor: I::Addr,
182 link_address: D::Address,
187 ) -> Result<(), StaticNeighborInsertionError> {
188 if !link_address.is_unicast() {
189 return Err(StaticNeighborInsertionError::MacAddressNotUnicast);
190 }
191 let neighbor = validate_neighbor_addr(neighbor)
192 .ok_or(StaticNeighborInsertionError::IpAddressInvalid)?;
193 let (core_ctx, bindings_ctx) = self.contexts();
194
195 core_ctx.with_nud_state_mut_and_sender_ctx(
196 device_id,
197 |NudState { neighbors, last_gc: _, timer_heap }, core_ctx| match neighbors
198 .entry(neighbor)
199 {
200 Entry::Occupied(mut occupied) => {
201 let previous =
202 core::mem::replace(occupied.get_mut(), NeighborState::Static(link_address));
203 let event_state = occupied.get().to_event_state();
204 if event_state != previous.to_event_state() {
205 bindings_ctx.on_event(Event::changed(
206 device_id,
207 event_state,
208 neighbor,
209 bindings_ctx.now(),
210 ));
211 }
212 match previous {
213 NeighborState::Dynamic(entry) => {
214 entry.cancel_timer_and_complete_resolution(
215 core_ctx,
216 bindings_ctx,
217 timer_heap,
218 neighbor,
219 link_address,
220 );
221 }
222 NeighborState::Static(_) => {}
223 }
224 }
225 Entry::Vacant(vacant) => {
226 let state = vacant.insert(NeighborState::Static(link_address));
227 let event = Event::added(
228 device_id,
229 state.to_event_state(),
230 neighbor,
231 bindings_ctx.now(),
232 );
233 bindings_ctx.on_event(event);
234 }
235 },
236 );
237 Ok(())
238 }
239
240 pub fn remove_entry(
242 &mut self,
243 device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
244 neighbor: I::Addr,
249 ) -> Result<(), NeighborRemovalError> {
250 let (core_ctx, bindings_ctx) = self.contexts();
251 let neighbor =
252 validate_neighbor_addr(neighbor).ok_or(NeighborRemovalError::IpAddressInvalid)?;
253
254 core_ctx.with_nud_state_mut(
255 device_id,
256 |NudState { neighbors, last_gc: _, timer_heap }, _config| {
257 match neighbors.remove(&neighbor).ok_or(NotFoundError)? {
258 NeighborState::Dynamic(mut entry) => {
259 entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
260 }
261 NeighborState::Static(_) => {}
262 }
263 bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
264 Ok(())
265 },
266 )
267 }
268
269 pub fn inspect_neighbors<N: Inspector>(
271 &mut self,
272 device: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
273 inspector: &mut N,
274 ) where
275 D::Address: Display,
276 {
277 self.core_ctx().with_nud_state(device, |nud| {
278 nud.neighbors.iter().for_each(|(ip_address, state)| {
279 let (state, link_address, last_confirmed_at) = match state {
280 NeighborState::Static(addr) => ("Static", Some(addr), None),
281 NeighborState::Dynamic(dynamic_state) => match dynamic_state {
282 DynamicNeighborState::Incomplete(Incomplete {
283 transmit_counter: _,
284 pending_frames: _,
285 notifiers: _,
286 _marker,
287 }) => ("Incomplete", None, None),
288 DynamicNeighborState::Reachable(Reachable {
289 link_address,
290 last_confirmed_at,
291 }) => ("Reachable", Some(link_address), Some(last_confirmed_at)),
292 DynamicNeighborState::Stale(Stale { link_address }) => {
293 ("Stale", Some(link_address), None)
294 }
295 DynamicNeighborState::Delay(Delay { link_address }) => {
296 ("Delay", Some(link_address), None)
297 }
298 DynamicNeighborState::Probe(Probe {
299 link_address,
300 transmit_counter: _,
301 }) => ("Probe", Some(link_address), None),
302 DynamicNeighborState::Unreachable(Unreachable {
303 link_address,
304 mode: _,
305 }) => ("Unreachable", Some(link_address), None),
306 },
307 };
308 inspector.record_unnamed_child(|inspector| {
309 inspector.record_str("State", state);
310 inspector.record_ip_addr("IpAddress", ip_address.get());
311 if let Some(link_address) = link_address {
312 inspector.record_display("LinkAddress", link_address);
313 };
314 if let Some(last_confirmed_at) = last_confirmed_at {
315 inspector.record_inspectable_value("LastConfirmedAt", last_confirmed_at);
316 }
317 });
318 })
319 })
320 }
321}