Skip to main content

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