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, 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 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 probe cannot be triggered on a neighbor.
43#[derive(Debug, PartialEq, Eq, Error)]
44pub enum TriggerNeighborProbeError {
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    /// The link address of the neighbor is unknown.
54    #[error("link address is unknown")]
55    LinkAddressUnknown,
56}
57
58/// Error when a neighbor table entry cannot be removed.
59#[derive(Debug, PartialEq, Eq, Error)]
60pub enum NeighborRemovalError {
61    /// The IP address is invalid as the address of a neighbor.
62    #[error("IP address is invalid")]
63    IpAddressInvalid,
64
65    /// Entry cannot be found.
66    #[error(transparent)]
67    NotFound(#[from] NotFoundError),
68}
69
70// TODO(https://fxbug.dev/42083952): Use NeighborAddr to witness these properties.
71fn validate_neighbor_addr<A: IpAddress>(addr: A) -> Option<SpecifiedAddr<A>> {
72    let is_valid: bool = A::Version::map_ip(
73        addr,
74        |v4| {
75            !Ipv4::LOOPBACK_SUBNET.contains(&v4)
76                && !Ipv4::MULTICAST_SUBNET.contains(&v4)
77                && v4 != Ipv4::LIMITED_BROADCAST_ADDRESS.get()
78        },
79        |v6| v6 != Ipv6::LOOPBACK_ADDRESS.get() && v6.to_ipv4_mapped().is_none() && v6.is_unicast(),
80    );
81    is_valid.then_some(()).and_then(|()| SpecifiedAddr::new(addr))
82}
83
84/// The neighbor API.
85pub struct NeighborApi<I: Ip, D, C>(C, IpVersionMarker<I>, PhantomData<D>);
86
87impl<I: Ip, D, C> NeighborApi<I, D, C> {
88    /// Creates a new API instance.
89    pub fn new(ctx: C) -> Self {
90        Self(ctx, IpVersionMarker::new(), PhantomData)
91    }
92}
93
94impl<I, D, C> NeighborApi<I, D, C>
95where
96    I: Ip,
97    D: LinkDevice,
98    C: ContextPair,
99    C::CoreContext: NudContext<I, D, C::BindingsContext>,
100    C::BindingsContext: NudBindingsContext<I, D, <C::CoreContext as DeviceIdContext<D>>::DeviceId>,
101{
102    fn core_ctx(&mut self) -> &mut C::CoreContext {
103        let Self(pair, IpVersionMarker { .. }, PhantomData) = self;
104        pair.core_ctx()
105    }
106
107    fn contexts(&mut self) -> (&mut C::CoreContext, &mut C::BindingsContext) {
108        let Self(pair, IpVersionMarker { .. }, PhantomData) = self;
109        pair.contexts()
110    }
111
112    /// Resolve the link-address for a given device's neighbor.
113    ///
114    /// Lookup the given destination IP address in the neighbor table for given
115    /// device, returning either the associated link-address if it is available,
116    /// or an observer that can be used to wait for link address resolution to
117    /// complete.
118    pub fn resolve_link_addr(
119        &mut self,
120        device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
121    // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
122    // disallow subnet and subnet broadcast addresses.
123    // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
124        dst: &SpecifiedAddr<I::Addr>,
125    ) -> LinkResolutionResult<
126        D::Address,
127        <<C::BindingsContext as LinkResolutionContext<D>>::Notifier as LinkResolutionNotifier<
128            D,
129        >>::Observer,
130    >{
131        let (core_ctx, bindings_ctx) = self.contexts();
132        let (result, do_multicast_solicit) = core_ctx.with_nud_state_mut(
133            device_id,
134            |NudState { neighbors, timer_heap, .. }, core_ctx| {
135                match neighbors.entry(*dst) {
136                    Entry::Vacant(entry) => {
137                        // Initiate link resolution.
138                        let (notifier, observer) =
139                            <C::BindingsContext as LinkResolutionContext<D>>::Notifier::new();
140                        let state = entry.insert(NeighborState::Dynamic(
141                            DynamicNeighborState::Incomplete(Incomplete::new_with_notifier(
142                                core_ctx,
143                                bindings_ctx,
144                                timer_heap,
145                                *dst,
146                                notifier,
147                            )),
148                        ));
149                        bindings_ctx.on_event(Event::added(
150                            device_id,
151                            state.to_event_state(),
152                            *dst,
153                            bindings_ctx.now(),
154                        ));
155                        (LinkResolutionResult::Pending(observer), true)
156                    }
157                    Entry::Occupied(e) => match e.into_mut() {
158                        NeighborState::Static(link_address) => {
159                            (LinkResolutionResult::Resolved(*link_address), false)
160                        }
161                        NeighborState::Dynamic(e) => {
162                            e.resolve_link_addr(core_ctx, bindings_ctx, timer_heap, device_id, *dst)
163                        }
164                    },
165                }
166            },
167        );
168
169        if do_multicast_solicit {
170            core_ctx.send_neighbor_solicitation(
171                bindings_ctx,
172                &device_id,
173                *dst,
174                /* multicast */ None,
175            );
176        }
177
178        result
179    }
180
181    /// Flush neighbor table entries.
182    pub fn flush_table(&mut self, device: &<C::CoreContext as DeviceIdContext<D>>::DeviceId) {
183        let (core_ctx, bindings_ctx) = self.contexts();
184        NudHandler::<I, D, _>::flush(core_ctx, bindings_ctx, device)
185    }
186
187    /// Sets a static neighbor entry for the neighbor.
188    ///
189    /// If no entry exists, a new one may be created. If an entry already
190    /// exists, it will be updated with the provided link address and set to be
191    /// a static entry.
192    ///
193    /// Dynamic updates for the neighbor will be ignored for static entries.
194    pub fn insert_static_entry(
195        &mut self,
196        device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
197        neighbor: I::Addr,
198        // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
199        // disallow the address with all host bits equal to 0, and the
200        // subnet broadcast addresses with all host bits equal to 1.
201        // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
202        link_address: D::Address,
203    ) -> Result<(), StaticNeighborInsertionError> {
204        if !link_address.is_unicast() {
205            return Err(StaticNeighborInsertionError::MacAddressNotUnicast);
206        }
207        let neighbor = validate_neighbor_addr(neighbor)
208            .ok_or(StaticNeighborInsertionError::IpAddressInvalid)?;
209        let (core_ctx, bindings_ctx) = self.contexts();
210
211        core_ctx.with_nud_state_mut_and_sender_ctx(
212            device_id,
213            |NudState { neighbors, last_gc: _, timer_heap }, core_ctx| match neighbors
214                .entry(neighbor)
215            {
216                Entry::Occupied(mut occupied) => {
217                    let previous =
218                        core::mem::replace(occupied.get_mut(), NeighborState::Static(link_address));
219                    let event_state = occupied.get().to_event_state();
220                    if event_state != previous.to_event_state() {
221                        bindings_ctx.on_event(Event::changed(
222                            device_id,
223                            event_state,
224                            neighbor,
225                            bindings_ctx.now(),
226                        ));
227                    }
228                    match previous {
229                        NeighborState::Dynamic(entry) => {
230                            entry.cancel_timer_and_complete_resolution(
231                                core_ctx,
232                                bindings_ctx,
233                                timer_heap,
234                                neighbor,
235                                link_address,
236                            );
237                        }
238                        NeighborState::Static(_) => {}
239                    }
240                }
241                Entry::Vacant(vacant) => {
242                    let state = vacant.insert(NeighborState::Static(link_address));
243                    let event = Event::added(
244                        device_id,
245                        state.to_event_state(),
246                        neighbor,
247                        bindings_ctx.now(),
248                    );
249                    bindings_ctx.on_event(event);
250                }
251            },
252        );
253        Ok(())
254    }
255
256    /// Immediately triggers a unicast probe to be sent to `neighbor`.
257    ///
258    /// For IPv6, this probe is an NDP Neighbor Solicitation, while for IPv4
259    /// it's an ARP Request.
260    ///
261    /// Returns an error if the probe was not sent, unless the neighbor was
262    /// already in the Probe state, in which case it succeeds without sending
263    /// another probe.
264    pub fn probe_entry(
265        &mut self,
266        device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
267        neighbor: I::Addr,
268    ) -> Result<(), TriggerNeighborProbeError> {
269        let (core_ctx, bindings_ctx) = self.contexts();
270        let neighbor =
271            validate_neighbor_addr(neighbor).ok_or(TriggerNeighborProbeError::IpAddressInvalid)?;
272
273        let probe_to_send = core_ctx.with_nud_state_mut(device_id, |nud_state, config_ctx| {
274            let mut neighbor_state = match nud_state.neighbors.entry(neighbor) {
275                Entry::Occupied(neighbor_state) => neighbor_state,
276                Entry::Vacant(_) => return Err(TriggerNeighborProbeError::NotFound(NotFoundError)),
277            };
278            neighbor_state
279                .get_mut()
280                .enter_probe(
281                    config_ctx,
282                    bindings_ctx,
283                    &mut nud_state.timer_heap,
284                    neighbor,
285                    device_id,
286                )
287                .map_err(|e| match e {
288                    EnterProbeError::LinkAddressUnknown => {
289                        TriggerNeighborProbeError::LinkAddressUnknown
290                    }
291                })
292        })?;
293        match probe_to_send {
294            link_addr @ Some(_) => {
295                core_ctx.send_neighbor_solicitation(bindings_ctx, &device_id, neighbor, link_addr)
296            }
297            None => {}
298        }
299        Ok(())
300    }
301
302    /// Remove a static or dynamic neighbor table entry.
303    pub fn remove_entry(
304        &mut self,
305        device_id: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
306        // TODO(https://fxbug.dev/42076887): Use IPv4 subnet information to
307        // disallow the address with all host bits equal to 0, and the
308        // subnet broadcast addresses with all host bits equal to 1.
309        // TODO(https://fxbug.dev/42083952): Use NeighborAddr when available.
310        neighbor: I::Addr,
311    ) -> Result<(), NeighborRemovalError> {
312        let (core_ctx, bindings_ctx) = self.contexts();
313        let neighbor =
314            validate_neighbor_addr(neighbor).ok_or(NeighborRemovalError::IpAddressInvalid)?;
315
316        core_ctx.with_nud_state_mut(
317            device_id,
318            |NudState { neighbors, last_gc: _, timer_heap }, _config| {
319                match neighbors.remove(&neighbor).ok_or(NotFoundError)? {
320                    NeighborState::Dynamic(mut entry) => {
321                        entry.cancel_timer(bindings_ctx, timer_heap, neighbor);
322                    }
323                    NeighborState::Static(_) => {}
324                }
325                bindings_ctx.on_event(Event::removed(device_id, neighbor, bindings_ctx.now()));
326                Ok(())
327            },
328        )
329    }
330
331    /// Writes `device`'s neighbor state information into `inspector`.
332    pub fn inspect_neighbors<N: Inspector>(
333        &mut self,
334        device: &<C::CoreContext as DeviceIdContext<D>>::DeviceId,
335        inspector: &mut N,
336    ) where
337        D::Address: Display,
338    {
339        self.core_ctx().with_nud_state(device, |nud| {
340            nud.neighbors.iter().for_each(|(ip_address, state)| {
341                let (state, link_address, last_confirmed_at) = match state {
342                    NeighborState::Static(addr) => ("Static", Some(addr), None),
343                    NeighborState::Dynamic(dynamic_state) => match dynamic_state {
344                        DynamicNeighborState::Incomplete(Incomplete {
345                            transmit_counter: _,
346                            pending_frames: _,
347                            notifiers: _,
348                            _marker,
349                        }) => ("Incomplete", None, None),
350                        DynamicNeighborState::Reachable(Reachable {
351                            link_address,
352                            last_confirmed_at,
353                        }) => ("Reachable", Some(link_address), Some(last_confirmed_at)),
354                        DynamicNeighborState::Stale(Stale { link_address }) => {
355                            ("Stale", Some(link_address), None)
356                        }
357                        DynamicNeighborState::Delay(Delay { link_address }) => {
358                            ("Delay", Some(link_address), None)
359                        }
360                        DynamicNeighborState::Probe(Probe {
361                            link_address,
362                            transmit_counter: _,
363                        }) => ("Probe", Some(link_address), None),
364                        DynamicNeighborState::Unreachable(Unreachable {
365                            link_address,
366                            mode: _,
367                        }) => ("Unreachable", Some(link_address), None),
368                    },
369                };
370                inspector.record_unnamed_child(|inspector| {
371                    inspector.record_str("State", state);
372                    inspector.record_ip_addr("IpAddress", ip_address.get());
373                    if let Some(link_address) = link_address {
374                        inspector.record_display("LinkAddress", link_address);
375                    };
376                    if let Some(last_confirmed_at) = last_confirmed_at {
377                        inspector.record_inspectable_value("LastConfirmedAt", last_confirmed_at);
378                    }
379                });
380            })
381        })
382    }
383}