netstack3_ip/multicast_forwarding/
route.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//! Declares types and functionality related to multicast routes.
6
7use alloc::fmt::Debug;
8use alloc::sync::Arc;
9use core::hash::Hash;
10use core::sync::atomic::Ordering;
11use derivative::Derivative;
12use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Ipv6Scope};
13use net_types::{
14    MulticastAddr, MulticastAddress as _, NonMappedAddr, ScopeableAddress as _, SpecifiedAddr,
15    SpecifiedAddress as _, UnicastAddr,
16};
17use netstack3_base::{
18    AtomicInstant, Inspectable, InspectableValue, Inspector, InspectorDeviceExt,
19    InstantBindingsTypes, IpExt, StrongDeviceIdentifier,
20};
21
22/// A witness type wrapping [`Ipv4Addr`], proving the following properties:
23/// * the inner address is specified, and
24/// * the inner address is not a multicast address.
25/// * the inner address's scope is greater than link local.
26///
27/// Note, unlike for [`Ipv6SourceAddr`], the `UnicastAddr` witness type cannot
28/// be used. This is because "unicastness" is not an absolute property of an
29/// IPv4 address: it requires knowing the subnet in which the address is being
30/// used.
31#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
32pub struct Ipv4SourceAddr {
33    addr: Ipv4Addr,
34}
35
36impl Ipv4SourceAddr {
37    /// Construct a new [`Ipv4SourceAddr`].
38    ///
39    /// `None` if the provided address does not have the required properties.
40    fn new(addr: Ipv4Addr) -> Option<Self> {
41        if addr.is_specified()
42            && !addr.is_multicast()
43            && !Ipv4::LINK_LOCAL_UNICAST_SUBNET.contains(&addr)
44        {
45            Some(Ipv4SourceAddr { addr })
46        } else {
47            None
48        }
49    }
50}
51
52impl From<Ipv4SourceAddr> for Ipv4Addr {
53    fn from(addr: Ipv4SourceAddr) -> Self {
54        addr.addr
55    }
56}
57
58/// A witness type wrapping [`Ipv4Addr`], proving the following properties:
59/// * the inner address is multicast, and
60/// * the inner address's scope is greater than link local.
61#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
62pub struct Ipv4DestinationAddr {
63    addr: MulticastAddr<Ipv4Addr>,
64}
65
66impl Ipv4DestinationAddr {
67    /// Construct a new [`Ipv4DestinationAddr`].
68    ///
69    /// `None` if the provided address does not have the required properties.
70    fn new(addr: Ipv4Addr) -> Option<Self> {
71        // As per RFC 5771 Section 4:
72        //   Addresses in the Local Network Control Block are used for protocol
73        //   control traffic that is not forwarded off link.
74        if Ipv4::LINK_LOCAL_MULTICAST_SUBNET.contains(&addr) {
75            None
76        } else {
77            Some(Ipv4DestinationAddr { addr: MulticastAddr::new(addr)? })
78        }
79    }
80}
81
82impl From<Ipv4DestinationAddr> for SpecifiedAddr<Ipv4Addr> {
83    fn from(addr: Ipv4DestinationAddr) -> Self {
84        addr.addr.into_specified()
85    }
86}
87
88/// A witness type wrapping [`Ipv6Addr`], proving the following properties:
89/// * the inner address is unicast, and
90/// * the inner address's scope is greater than link local.
91/// * the inner address is non-mapped.
92#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
93pub struct Ipv6SourceAddr {
94    addr: NonMappedAddr<UnicastAddr<Ipv6Addr>>,
95}
96
97impl Ipv6SourceAddr {
98    /// Construct a new [`Ipv6SourceAddr`].
99    ///
100    /// `None` if the provided address does not have the required properties.
101    fn new(addr: Ipv6Addr) -> Option<Self> {
102        let addr = NonMappedAddr::new(UnicastAddr::new(addr)?)?;
103        match addr.scope() {
104            Ipv6Scope::InterfaceLocal | Ipv6Scope::LinkLocal => None,
105            Ipv6Scope::Reserved(_) | Ipv6Scope::Unassigned(_) => None,
106            Ipv6Scope::AdminLocal
107            | Ipv6Scope::SiteLocal
108            | Ipv6Scope::OrganizationLocal
109            | Ipv6Scope::Global => Some(Ipv6SourceAddr { addr }),
110        }
111    }
112}
113
114impl From<Ipv6SourceAddr> for net_types::ip::Ipv6SourceAddr {
115    fn from(addr: Ipv6SourceAddr) -> Self {
116        net_types::ip::Ipv6SourceAddr::Unicast(addr.addr)
117    }
118}
119
120/// A witness type wrapping [`Ipv6Addr`], proving the following properties:
121/// * the inner address is multicast, and
122/// * the inner address's scope is greater than link local.
123#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
124pub struct Ipv6DestinationAddr {
125    addr: MulticastAddr<Ipv6Addr>,
126}
127
128impl Ipv6DestinationAddr {
129    /// Construct a new [`Ipv6DestinationAddr`].
130    ///
131    /// `None` if the provided address does not have the required properties.
132    fn new(addr: Ipv6Addr) -> Option<Self> {
133        // As per RFC 4291 Section 2.7:
134        //   Routers must not forward any multicast packets beyond of the scope
135        //   indicated by the scop field in the destination multicast address.
136        if addr.scope().multicast_scope_id() <= Ipv6Scope::MULTICAST_SCOPE_ID_LINK_LOCAL {
137            None
138        } else {
139            Some(Ipv6DestinationAddr { addr: MulticastAddr::new(addr)? })
140        }
141    }
142}
143
144impl From<Ipv6DestinationAddr> for SpecifiedAddr<Ipv6Addr> {
145    fn from(addr: Ipv6DestinationAddr) -> Self {
146        addr.addr.into_specified()
147    }
148}
149
150/// IP extension trait for multicast routes.
151pub trait MulticastRouteIpExt: IpExt {
152    /// The type of source address used in [`MulticastRouteKey`].
153    type SourceAddress: Clone
154        + Debug
155        + Eq
156        + Hash
157        + Ord
158        + PartialEq
159        + PartialOrd
160        + Into<Self::RecvSrcAddr>;
161    /// The type of destination address used in [`MulticastRouteKey`].
162    type DestinationAddress: Clone
163        + Debug
164        + Eq
165        + Hash
166        + Ord
167        + PartialEq
168        + PartialOrd
169        + Into<SpecifiedAddr<Self::Addr>>;
170}
171
172impl MulticastRouteIpExt for Ipv4 {
173    type SourceAddress = Ipv4SourceAddr;
174    type DestinationAddress = Ipv4DestinationAddr;
175}
176
177impl MulticastRouteIpExt for Ipv6 {
178    type SourceAddress = Ipv6SourceAddr;
179    type DestinationAddress = Ipv6DestinationAddr;
180}
181
182/// The attributes of a multicast route that uniquely identify it.
183#[derive(Clone, Debug, Eq, GenericOverIp, Hash, Ord, PartialEq, PartialOrd)]
184#[generic_over_ip(I, Ip)]
185pub struct MulticastRouteKey<I: MulticastRouteIpExt> {
186    /// The source address packets must have in order to use this route.
187    pub(crate) src_addr: I::SourceAddress,
188    /// The destination address packets must have in order to use this route.
189    pub(crate) dst_addr: I::DestinationAddress,
190}
191
192impl<I: MulticastRouteIpExt> MulticastRouteKey<I> {
193    /// Construct a new [`MulticastRouteKey`].
194    ///
195    /// `None` if the provided addresses do not have the required properties.
196    pub fn new(src_addr: I::Addr, dst_addr: I::Addr) -> Option<MulticastRouteKey<I>> {
197        I::map_ip(
198            (src_addr, dst_addr),
199            |(src_addr, dst_addr)| {
200                Some(MulticastRouteKey {
201                    src_addr: Ipv4SourceAddr::new(src_addr)?,
202                    dst_addr: Ipv4DestinationAddr::new(dst_addr)?,
203                })
204            },
205            |(src_addr, dst_addr)| {
206                Some(MulticastRouteKey {
207                    src_addr: Ipv6SourceAddr::new(src_addr)?,
208                    dst_addr: Ipv6DestinationAddr::new(dst_addr)?,
209                })
210            },
211        )
212    }
213
214    /// Returns the source address, stripped of all its witnesses.
215    pub fn src_addr(&self) -> I::Addr {
216        I::map_ip(self, |key| key.src_addr.addr, |key| **key.src_addr.addr)
217    }
218
219    /// Returns the destination address, stripped of all its witnesses.
220    pub fn dst_addr(&self) -> I::Addr {
221        I::map_ip(self, |key| *key.dst_addr.addr, |key| *key.dst_addr.addr)
222    }
223}
224
225impl<I: MulticastRouteIpExt> Inspectable for MulticastRouteKey<I> {
226    fn record<II: Inspector>(&self, inspector: &mut II) {
227        inspector.record_ip_addr("SourceAddress", self.src_addr());
228        inspector.record_ip_addr("DestinationAddress", self.dst_addr());
229    }
230}
231
232/// An entry in the multicast route table.
233#[derive(Derivative)]
234#[derivative(Debug(bound = ""))]
235pub struct MulticastRouteEntry<D: StrongDeviceIdentifier, BT: InstantBindingsTypes> {
236    pub(crate) route: MulticastRoute<D>,
237    // NB: Hold the statistics as an AtomicInstant so that they can be updated
238    // without write-locking the multicast route table.
239    pub(crate) stats: MulticastRouteStats<BT::AtomicInstant>,
240}
241
242impl<D: StrongDeviceIdentifier, BT: InstantBindingsTypes> MulticastRouteEntry<D, BT> {
243    /// Writes this [`MulticastRouteEntry`] to the inspector.
244    // NB: This exists as a method rather than an implementation of
245    // `Inspectable` because we need to restrict the type of `I::DeviceId`.
246    pub(crate) fn inspect<I: Inspector, II: InspectorDeviceExt<D>>(&self, inspector: &mut I) {
247        let MulticastRouteEntry {
248            route: MulticastRoute { input_interface, action },
249            stats: MulticastRouteStats { last_used },
250        } = self;
251        II::record_device(inspector, "InputInterface", input_interface);
252        let Action::Forward(targets) = action;
253        inspector.record_child("ForwardingTargets", |inspector| {
254            for MulticastRouteTarget { output_interface, min_ttl } in targets.iter() {
255                inspector.record_unnamed_child(|inspector| {
256                    II::record_device(inspector, "OutputInterface", output_interface);
257                    inspector.record_uint("MinTTL", *min_ttl);
258                });
259            }
260        });
261        inspector.record_child("Statistics", |inspector| {
262            last_used.load(Ordering::Relaxed).record("LastUsed", inspector);
263        });
264    }
265}
266
267/// All attributes of a multicast route, excluding the [`MulticastRouteKey`].
268///
269/// This type acts as a witness that the route is valid.
270#[derive(Clone, Debug, Eq, PartialEq)]
271pub struct MulticastRoute<D: StrongDeviceIdentifier> {
272    /// The interface on which packets must arrive in order to use this route.
273    pub(crate) input_interface: D,
274    /// The route's action.
275    pub(crate) action: Action<D>,
276}
277
278/// The action to be taken for a packet that matches a route.
279#[derive(Clone, Debug, Eq, PartialEq)]
280pub(crate) enum Action<D: StrongDeviceIdentifier> {
281    /// Forward the packet out of each provided [`Target`].
282    Forward(MulticastRouteTargets<D>),
283}
284
285/// The collection of targets out of which to forward a multicast packet.
286///
287/// Note, storing the targets behind an `Arc` allows us to return a reference
288/// to the targets, to contexts that are not protected by the multicast route
289/// table lock, without cloning the underlying data. Here, an `Arc<Mutex<...>>`
290/// is unnecessary, because the underlying targets list is never modified. This
291/// is not to say that a route's targets are never modified (e.g. device removal
292/// prunes the list of targets); in such cases the route's target list is
293/// *replaced* with a new allocation. This strategy allows us to avoid
294/// additional locking on the hot path, at the cost of extra allocations for
295/// certain control operations.
296pub type MulticastRouteTargets<D> = Arc<[MulticastRouteTarget<D>]>;
297
298/// The target out of which to forward a multicast packet.
299#[derive(Clone, Debug, Eq, Hash, PartialEq)]
300pub struct MulticastRouteTarget<D: StrongDeviceIdentifier> {
301    /// An interface the packet should be forwarded out of.
302    pub output_interface: D,
303    /// The minimum TTL of the packet in order for it to be forwarded.
304    ///
305    /// A value of 0 allows packets to be forwarded, regardless of their TTL.
306    pub min_ttl: u8,
307}
308
309/// Errors returned by [`MulticastRoute::new_forward`].
310#[derive(Debug, Eq, PartialEq)]
311pub enum ForwardMulticastRouteError {
312    /// The route's list of targets is empty.
313    EmptyTargetList,
314    /// The route's `input_interface` is also listed as a target. This would
315    /// create a routing loop.
316    InputInterfaceIsTarget,
317    /// The route lists the same [`Target`] output_interface multiple times.
318    DuplicateTarget,
319}
320
321impl<D: StrongDeviceIdentifier> MulticastRoute<D> {
322    /// Construct a new [`MulticastRoute`] with [`Action::Forward`].
323    pub fn new_forward(
324        input_interface: D,
325        targets: MulticastRouteTargets<D>,
326    ) -> Result<Self, ForwardMulticastRouteError> {
327        if targets.is_empty() {
328            return Err(ForwardMulticastRouteError::EmptyTargetList);
329        }
330        if targets.iter().any(|MulticastRouteTarget { output_interface, min_ttl: _ }| {
331            output_interface == &input_interface
332        }) {
333            return Err(ForwardMulticastRouteError::InputInterfaceIsTarget);
334        }
335
336        // NB: Search for duplicates by doing a naive n^2 comparison. This is
337        // expected to be more performant than other approaches (e.g.
338        // sort + dedup, or collecting into a hash map) given how small the vec
339        // is expected to be.
340        for (index, target_a) in targets.iter().enumerate() {
341            // NB: Only check the targets that occur in the vec *after* this
342            // one. The targets before this one were checked in previous
343            // iterations.
344            if targets[index + 1..]
345                .iter()
346                .any(|target_b| target_a.output_interface == target_b.output_interface)
347            {
348                return Err(ForwardMulticastRouteError::DuplicateTarget);
349            }
350        }
351
352        Ok(MulticastRoute { input_interface, action: Action::Forward(targets) })
353    }
354}
355
356/// Statistics about a [`MulticastRoute`].
357#[derive(Debug, Eq, PartialEq)]
358pub struct MulticastRouteStats<Instant> {
359    /// The last time the route was used to route a packet.
360    ///
361    /// This value is initialized to the current time when a route is installed
362    /// in the route table, and updated every time the route is selected during
363    /// multicast route lookup. Notably, it is updated regardless of whether the
364    /// packet is actually forwarded; it might be dropped after the routing
365    /// decision for a number reasons (e.g. dropped by the filtering engine,
366    /// dropped at the device layer, etc).
367    pub last_used: Instant,
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373
374    use alloc::vec;
375    use alloc::vec::Vec;
376    use net_declare::{net_ip_v4, net_ip_v6};
377    use netstack3_base::testutil::MultipleDevicesId;
378    use test_case::test_case;
379
380    const UNICAST_V4: Ipv4Addr = net_ip_v4!("192.0.2.1");
381    const MULTICAST_V4: Ipv4Addr = net_ip_v4!("224.0.1.1");
382    const LL_UNICAST_V4: Ipv4Addr = net_ip_v4!("169.254.0.1");
383    const LL_MULTICAST_V4: Ipv4Addr = net_ip_v4!("224.0.0.1");
384    const UNICAST_V6: Ipv6Addr = net_ip_v6!("2001:0DB8::1");
385    const MULTICAST_V6: Ipv6Addr = net_ip_v6!("ff0e::1");
386    const LL_UNICAST_V6: Ipv6Addr = net_ip_v6!("fe80::1");
387    const LL_MULTICAST_V6: Ipv6Addr = net_ip_v6!("ff02::1");
388    const V4_MAPPED_V6: Ipv6Addr = net_ip_v6!("::FFFF:192.0.2.1");
389
390    #[test_case(UNICAST_V4, MULTICAST_V4 => true; "success")]
391    #[test_case(UNICAST_V4, UNICAST_V4 => false; "unicast_dst")]
392    #[test_case(UNICAST_V4, Ipv4::UNSPECIFIED_ADDRESS => false; "unspecified_dst")]
393    #[test_case(MULTICAST_V4, MULTICAST_V4 => false; "multicast_src")]
394    #[test_case(Ipv4::UNSPECIFIED_ADDRESS, MULTICAST_V4 => false; "unspecified_src")]
395    #[test_case(LL_UNICAST_V4, MULTICAST_V4 => false; "ll_unicast_src")]
396    #[test_case(UNICAST_V4, LL_MULTICAST_V4 => false; "ll_multicast_dst")]
397    fn new_ipv4_route_key(src_addr: Ipv4Addr, dst_addr: Ipv4Addr) -> bool {
398        MulticastRouteKey::<Ipv4>::new(src_addr, dst_addr).is_some()
399    }
400
401    #[test_case(UNICAST_V6, MULTICAST_V6 => true; "success")]
402    #[test_case(UNICAST_V6, UNICAST_V6 => false; "unicast_dst")]
403    #[test_case(UNICAST_V6, Ipv6::UNSPECIFIED_ADDRESS => false; "unspecified_dst")]
404    #[test_case(MULTICAST_V6, MULTICAST_V6 => false; "multicast_src")]
405    #[test_case(Ipv6::UNSPECIFIED_ADDRESS, MULTICAST_V6 => false; "unspecified_src")]
406    #[test_case(LL_UNICAST_V6, MULTICAST_V6 => false; "ll_unicast_src")]
407    #[test_case(UNICAST_V6, LL_MULTICAST_V6 => false; "ll_multicast_dst")]
408    #[test_case(V4_MAPPED_V6, LL_MULTICAST_V6 => false; "mapped_src")]
409    fn new_ipv6_route_key(src_addr: Ipv6Addr, dst_addr: Ipv6Addr) -> bool {
410        MulticastRouteKey::<Ipv6>::new(src_addr, dst_addr).is_some()
411    }
412
413    #[test_case(MultipleDevicesId::A, vec![] =>
414        Some(ForwardMulticastRouteError::EmptyTargetList); "empty_target_list")]
415    #[test_case(MultipleDevicesId::A, vec![MultipleDevicesId::A] =>
416        Some(ForwardMulticastRouteError::InputInterfaceIsTarget); "input_interface_is_target")]
417    #[test_case(MultipleDevicesId::A, vec![MultipleDevicesId::B, MultipleDevicesId::B] =>
418        Some(ForwardMulticastRouteError::DuplicateTarget); "duplicate_target")]
419    #[test_case(MultipleDevicesId::A, vec![MultipleDevicesId::B, MultipleDevicesId::C] =>
420        None; "valid_route")]
421    fn new_forward(
422        input_interface: MultipleDevicesId,
423        output_interfaces: Vec<MultipleDevicesId>,
424    ) -> Option<ForwardMulticastRouteError> {
425        let targets = output_interfaces
426            .into_iter()
427            .map(|output_interface| MulticastRouteTarget { output_interface, min_ttl: 0 })
428            .collect();
429        MulticastRoute::new_forward(input_interface, targets).err()
430    }
431}