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 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/// Error when a static neighbor entry cannot be inserted.
26#[derive(Debug, PartialEq, Eq, Error)]
27pub enum StaticNeighborInsertionError {
28    /// The IP address is invalid as the address of a neighbor. A valid address
29    /// is:
30    /// - specified,
31    /// - not multicast,
32    /// - not loopback,
33    /// - not an IPv4-mapped address, and
34    /// - not the limited broadcast address of `255.255.255.255`.
35    #[error("IP address is invalid")]
36    IpAddressInvalid,
37
38    /// The neighbor table is full and the entry cannot be added.
39    #[error("The neighbor table is full")]
40    TableFull,
41}
42
43/// Error when a probe cannot be triggered on a neighbor.
44#[derive(Debug, PartialEq, Eq, Error)]
45pub enum TriggerNeighborProbeError {
46    /// The IP address is invalid as the address of a neighbor.
47    #[error("IP address is invalid")]
48    IpAddressInvalid,
49
50    /// Entry cannot be found.
51    #[error(transparent)]
52    NotFound(#[from] NotFoundError),
53
54    /// The link address of the neighbor is unknown.
55    #[error("link address is unknown")]
56    LinkAddressUnknown,
57}
58
59/// Error when a neighbor table entry cannot be removed.
60#[derive(Debug, PartialEq, Eq, Error)]
61pub enum NeighborRemovalError {
62    /// The IP address is invalid as the address of a neighbor.
63    #[error("IP address is invalid")]
64    IpAddressInvalid,
65
66    /// Entry cannot be found.
67    #[error(transparent)]
68    NotFound(#[from] NotFoundError),
69}
70
71// TODO(https://fxbug.dev/42083952): Use NeighborAddr to witness these properties.
72fn 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
85/// The neighbor API.
86pub struct NeighborApi<I: Ip, D, C>(C, IpVersionMarker<I>, PhantomData<D>);
87
88impl<I: Ip, D, C> NeighborApi<I, D, C> {
89    /// Creates a new API instance.
90    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    /// Resolve the link-address for a given device's neighbor.
114    ///
115    /// Lookup the given destination IP address in the neighbor table for given
116    /// device, returning either the associated link-address if it is available,
117    /// or an observer that can be used to wait for link address resolution to
118    /// complete.
119    pub fn resolve_link_addr(
120        &mut self,
121        device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
122    // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
123    // disallow subnet and subnet broadcast addresses.
124    // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
125        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                    // Initiate link resolution.
138                    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                /* multicast */ None,
186            );
187        }
188
189        result
190    }
191
192    /// Flush neighbor table entries.
193    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    /// Sets a static neighbor entry for the neighbor.
199    ///
200    /// If no entry exists, a new one may be created. If an entry already
201    /// exists, it will be updated with the provided link address and set to be
202    /// a static entry.
203    ///
204    /// Dynamic updates for the neighbor will be ignored for static entries.
205    pub fn insert_static_entry(
206        &mut self,
207        device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
208        neighbor: I::Addr,
209        // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
210        // disallow the address with all host bits equal to 0, and the
211        // subnet broadcast addresses with all host bits equal to 1.
212        // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
213        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    /// Immediately triggers a unicast probe to be sent to `neighbor`.
273    ///
274    /// For IPv6, this probe is an NDP Neighbor Solicitation, while for IPv4
275    /// it's an ARP Request.
276    ///
277    /// Returns an error if the probe was not sent, unless the neighbor was
278    /// already in the Probe state, in which case it succeeds without sending
279    /// another probe.
280    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    /// Remove a static or dynamic neighbor table entry.
322    pub fn remove_entry(
323        &mut self,
324        device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
325        // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
326        // disallow the address with all host bits equal to 0, and the
327        // subnet broadcast addresses with all host bits equal to 1.
328        // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
329        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    /// Writes `device`'s neighbor state information into `inspector`.
351    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}