Skip to main content

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, 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/// Error when a static neighbor entry cannot be inserted.
25#[derive(Debug, PartialEq, Eq, Error)]
26pub enum StaticNeighborInsertionError {
27    /// The IP address is invalid as the address of a neighbor. A valid address
28    /// is:
29    /// - specified,
30    /// - not multicast,
31    /// - not loopback,
32    /// - not an IPv4-mapped address, and
33    /// - not the limited broadcast address of `255.255.255.255`.
34    #[error("IP address is invalid")]
35    IpAddressInvalid,
36}
37
38/// Error when a probe cannot be triggered on a neighbor.
39#[derive(Debug, PartialEq, Eq, Error)]
40pub enum TriggerNeighborProbeError {
41    /// The IP address is invalid as the address of a neighbor.
42    #[error("IP address is invalid")]
43    IpAddressInvalid,
44
45    /// Entry cannot be found.
46    #[error(transparent)]
47    NotFound(#[from] NotFoundError),
48
49    /// The link address of the neighbor is unknown.
50    #[error("link address is unknown")]
51    LinkAddressUnknown,
52}
53
54/// Error when a neighbor table entry cannot be removed.
55#[derive(Debug, PartialEq, Eq, Error)]
56pub enum NeighborRemovalError {
57    /// The IP address is invalid as the address of a neighbor.
58    #[error("IP address is invalid")]
59    IpAddressInvalid,
60
61    /// Entry cannot be found.
62    #[error(transparent)]
63    NotFound(#[from] NotFoundError),
64}
65
66// TODO(https://fxbug.dev/42083952): Use NeighborAddr to witness these properties.
67fn 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
80/// The neighbor API.
81pub struct NeighborApi<I: Ip, D, C>(C, IpVersionMarker<I>, PhantomData<D>);
82
83impl<I: Ip, D, C> NeighborApi<I, D, C> {
84    /// Creates a new API instance.
85    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    /// Resolve the link-address for a given device's neighbor.
109    ///
110    /// Lookup the given destination IP address in the neighbor table for given
111    /// device, returning either the associated link-address if it is available,
112    /// or an observer that can be used to wait for link address resolution to
113    /// complete.
114    pub fn resolve_link_addr(
115        &mut self,
116        device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
117    // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
118    // disallow subnet and subnet broadcast addresses.
119    // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
120        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                        // Initiate link resolution.
134                        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                /* multicast */ None,
171            );
172        }
173
174        result
175    }
176
177    /// Flush neighbor table entries.
178    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    /// Sets a static neighbor entry for the neighbor.
184    ///
185    /// If no entry exists, a new one may be created. If an entry already
186    /// exists, it will be updated with the provided link address and set to be
187    /// a static entry.
188    ///
189    /// Dynamic updates for the neighbor will be ignored for static entries.
190    pub fn insert_static_entry(
191        &mut self,
192        device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
193        neighbor: I::Addr,
194        // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
195        // disallow the address with all host bits equal to 0, and the
196        // subnet broadcast addresses with all host bits equal to 1.
197        // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
198        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    /// Immediately triggers a unicast probe to be sent to `neighbor`.
250    ///
251    /// For IPv6, this probe is an NDP Neighbor Solicitation, while for IPv4
252    /// it's an ARP Request.
253    ///
254    /// Returns an error if the probe was not sent, unless the neighbor was
255    /// already in the Probe state, in which case it succeeds without sending
256    /// another probe.
257    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    /// Remove a static or dynamic neighbor table entry.
299    pub fn remove_entry(
300        &mut self,
301        device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
302        // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
303        // disallow the address with all host bits equal to 0, and the
304        // subnet broadcast addresses with all host bits equal to 1.
305        // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
306        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    /// Writes `device`'s neighbor state information into `inspector`.
328    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}