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