netstack3_ip/device/nud/
api.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Neighbor API structs.
6
7use 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/// Error when a static neighbor entry cannot be inserted.
25#[derive(Debug, PartialEq, Eq, Error)]
26pub enum StaticNeighborInsertionError {
27    /// The MAC address used for a static neighbor entry is not unicast.
28    #[error("MAC address is not unicast")]
29    MacAddressNotUnicast,
30
31    /// The IP address is invalid as the address of a neighbor. A valid address
32    /// is:
33    /// - specified,
34    /// - not multicast,
35    /// - not loopback,
36    /// - not an IPv4-mapped address, and
37    /// - not the limited broadcast address of `255.255.255.255`.
38    #[error("IP address is invalid")]
39    IpAddressInvalid,
40}
41
42/// Error when a neighbor table entry cannot be removed.
43#[derive(Debug, PartialEq, Eq, Error)]
44pub enum NeighborRemovalError {
45    /// The IP address is invalid as the address of a neighbor.
46    #[error("IP address is invalid")]
47    IpAddressInvalid,
48
49    /// Entry cannot be found.
50    #[error(transparent)]
51    NotFound(#[from] NotFoundError),
52}
53
54// TODO(https://fxbug.dev/42083952): Use NeighborAddr to witness these properties.
55fn 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
68/// The neighbor API.
69pub struct NeighborApi<I: Ip, D, C>(C, IpVersionMarker<I>, PhantomData<D>);
70
71impl<I: Ip, D, C> NeighborApi<I, D, C> {
72    /// Creates a new API instance.
73    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    /// Resolve the link-address for a given device's neighbor.
97    ///
98    /// Lookup the given destination IP address in the neighbor table for given
99    /// device, returning either the associated link-address if it is available,
100    /// or an observer that can be used to wait for link address resolution to
101    /// complete.
102    pub fn resolve_link_addr(
103        &mut self,
104        device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
105    // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
106    // disallow subnet and subnet broadcast addresses.
107    // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
108        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                        // Initiate link resolution.
122                        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                /* multicast */ None,
159            );
160        }
161
162        result
163    }
164
165    /// Flush neighbor table entries.
166    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    /// Sets a static neighbor entry for the neighbor.
172    ///
173    /// If no entry exists, a new one may be created. If an entry already
174    /// exists, it will be updated with the provided link address and set to be
175    /// a static entry.
176    ///
177    /// Dynamic updates for the neighbor will be ignored for static entries.
178    pub fn insert_static_entry(
179        &mut self,
180        device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
181        neighbor: I::Addr,
182        // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
183        // disallow the address with all host bits equal to 0, and the
184        // subnet broadcast addresses with all host bits equal to 1.
185        // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
186        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    /// Remove a static or dynamic neighbor table entry.
241    pub fn remove_entry(
242        &mut self,
243        device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
244        // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
245        // disallow the address with all host bits equal to 0, and the
246        // subnet broadcast addresses with all host bits equal to 1.
247        // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
248        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    /// Writes `device`'s neighbor state information into `inspector`.
270    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}