1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Declares types and functionality related to multicast routes.

use alloc::fmt::Debug;
use alloc::sync::Arc;
use core::hash::Hash;
use core::sync::atomic::Ordering;
use derivative::Derivative;
use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Ipv6Scope};
use net_types::{
    MulticastAddr, MulticastAddress as _, NonMappedAddr, ScopeableAddress as _, SpecifiedAddr,
    SpecifiedAddress as _, UnicastAddr,
};
use netstack3_base::{
    AtomicInstant, Inspectable, InspectableValue, Inspector, InspectorDeviceExt,
    InstantBindingsTypes, IpExt, StrongDeviceIdentifier,
};

/// A witness type wrapping [`Ipv4Addr`], proving the following properties:
/// * the inner address is specified, and
/// * the inner address is not a multicast address.
/// * the inner address's scope is greater than link local.
///
/// Note, unlike for [`Ipv6SourceAddr`], the `UnicastAddr` witness type cannot
/// be used. This is because "unicastness" is not an absolute property of an
/// IPv4 address: it requires knowing the subnet in which the address is being
/// used.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Ipv4SourceAddr {
    addr: Ipv4Addr,
}

impl Ipv4SourceAddr {
    /// Construct a new [`Ipv4SourceAddr`].
    ///
    /// `None` if the provided address does not have the required properties.
    fn new(addr: Ipv4Addr) -> Option<Self> {
        if addr.is_specified()
            && !addr.is_multicast()
            && !Ipv4::LINK_LOCAL_UNICAST_SUBNET.contains(&addr)
        {
            Some(Ipv4SourceAddr { addr })
        } else {
            None
        }
    }
}

impl From<Ipv4SourceAddr> for Ipv4Addr {
    fn from(addr: Ipv4SourceAddr) -> Self {
        addr.addr
    }
}

/// A witness type wrapping [`Ipv4Addr`], proving the following properties:
/// * the inner address is multicast, and
/// * the inner address's scope is greater than link local.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Ipv4DestinationAddr {
    addr: MulticastAddr<Ipv4Addr>,
}

impl Ipv4DestinationAddr {
    /// Construct a new [`Ipv4DestinationAddr`].
    ///
    /// `None` if the provided address does not have the required properties.
    fn new(addr: Ipv4Addr) -> Option<Self> {
        // As per RFC 5771 Section 4:
        //   Addresses in the Local Network Control Block are used for protocol
        //   control traffic that is not forwarded off link.
        if Ipv4::LINK_LOCAL_MULTICAST_SUBNET.contains(&addr) {
            None
        } else {
            Some(Ipv4DestinationAddr { addr: MulticastAddr::new(addr)? })
        }
    }
}

impl From<Ipv4DestinationAddr> for SpecifiedAddr<Ipv4Addr> {
    fn from(addr: Ipv4DestinationAddr) -> Self {
        addr.addr.into_specified()
    }
}

/// A witness type wrapping [`Ipv6Addr`], proving the following properties:
/// * the inner address is unicast, and
/// * the inner address's scope is greater than link local.
/// * the inner address is non-mapped.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Ipv6SourceAddr {
    addr: NonMappedAddr<UnicastAddr<Ipv6Addr>>,
}

impl Ipv6SourceAddr {
    /// Construct a new [`Ipv6SourceAddr`].
    ///
    /// `None` if the provided address does not have the required properties.
    fn new(addr: Ipv6Addr) -> Option<Self> {
        let addr = NonMappedAddr::new(UnicastAddr::new(addr)?)?;
        match addr.scope() {
            Ipv6Scope::InterfaceLocal | Ipv6Scope::LinkLocal => None,
            Ipv6Scope::Reserved(_) | Ipv6Scope::Unassigned(_) => None,
            Ipv6Scope::AdminLocal
            | Ipv6Scope::SiteLocal
            | Ipv6Scope::OrganizationLocal
            | Ipv6Scope::Global => Some(Ipv6SourceAddr { addr }),
        }
    }
}

impl From<Ipv6SourceAddr> for net_types::ip::Ipv6SourceAddr {
    fn from(addr: Ipv6SourceAddr) -> Self {
        net_types::ip::Ipv6SourceAddr::Unicast(addr.addr)
    }
}

/// A witness type wrapping [`Ipv6Addr`], proving the following properties:
/// * the inner address is multicast, and
/// * the inner address's scope is greater than link local.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Ipv6DestinationAddr {
    addr: MulticastAddr<Ipv6Addr>,
}

impl Ipv6DestinationAddr {
    /// Construct a new [`Ipv6DestinationAddr`].
    ///
    /// `None` if the provided address does not have the required properties.
    fn new(addr: Ipv6Addr) -> Option<Self> {
        // As per RFC 4291 Section 2.7:
        //   Routers must not forward any multicast packets beyond of the scope
        //   indicated by the scop field in the destination multicast address.
        if addr.scope().multicast_scope_id() <= Ipv6Scope::MULTICAST_SCOPE_ID_LINK_LOCAL {
            None
        } else {
            Some(Ipv6DestinationAddr { addr: MulticastAddr::new(addr)? })
        }
    }
}

impl From<Ipv6DestinationAddr> for SpecifiedAddr<Ipv6Addr> {
    fn from(addr: Ipv6DestinationAddr) -> Self {
        addr.addr.into_specified()
    }
}

/// IP extension trait for multicast routes.
pub trait MulticastRouteIpExt: IpExt {
    /// The type of source address used in [`MulticastRouteKey`].
    type SourceAddress: Clone
        + Debug
        + Eq
        + Hash
        + Ord
        + PartialEq
        + PartialOrd
        + Into<Self::RecvSrcAddr>;
    /// The type of destination address used in [`MulticastRouteKey`].
    type DestinationAddress: Clone
        + Debug
        + Eq
        + Hash
        + Ord
        + PartialEq
        + PartialOrd
        + Into<SpecifiedAddr<Self::Addr>>;
}

impl MulticastRouteIpExt for Ipv4 {
    type SourceAddress = Ipv4SourceAddr;
    type DestinationAddress = Ipv4DestinationAddr;
}

impl MulticastRouteIpExt for Ipv6 {
    type SourceAddress = Ipv6SourceAddr;
    type DestinationAddress = Ipv6DestinationAddr;
}

/// The attributes of a multicast route that uniquely identify it.
#[derive(Clone, Debug, Eq, GenericOverIp, Hash, Ord, PartialEq, PartialOrd)]
#[generic_over_ip(I, Ip)]
pub struct MulticastRouteKey<I: MulticastRouteIpExt> {
    /// The source address packets must have in order to use this route.
    pub(crate) src_addr: I::SourceAddress,
    /// The destination address packets must have in order to use this route.
    pub(crate) dst_addr: I::DestinationAddress,
}

impl<I: MulticastRouteIpExt> MulticastRouteKey<I> {
    /// Construct a new [`MulticastRouteKey`].
    ///
    /// `None` if the provided addresses do not have the required properties.
    pub fn new(src_addr: I::Addr, dst_addr: I::Addr) -> Option<MulticastRouteKey<I>> {
        I::map_ip(
            (src_addr, dst_addr),
            |(src_addr, dst_addr)| {
                Some(MulticastRouteKey {
                    src_addr: Ipv4SourceAddr::new(src_addr)?,
                    dst_addr: Ipv4DestinationAddr::new(dst_addr)?,
                })
            },
            |(src_addr, dst_addr)| {
                Some(MulticastRouteKey {
                    src_addr: Ipv6SourceAddr::new(src_addr)?,
                    dst_addr: Ipv6DestinationAddr::new(dst_addr)?,
                })
            },
        )
    }

    /// Returns the source address, stripped of all its witnesses.
    pub fn src_addr(&self) -> I::Addr {
        I::map_ip(self, |key| key.src_addr.addr, |key| **key.src_addr.addr)
    }

    /// Returns the destination address, stripped of all its witnesses.
    pub fn dst_addr(&self) -> I::Addr {
        I::map_ip(self, |key| *key.dst_addr.addr, |key| *key.dst_addr.addr)
    }
}

impl<I: MulticastRouteIpExt> Inspectable for MulticastRouteKey<I> {
    fn record<II: Inspector>(&self, inspector: &mut II) {
        inspector.record_ip_addr("SourceAddress", self.src_addr());
        inspector.record_ip_addr("DestinationAddress", self.dst_addr());
    }
}

/// An entry in the multicast route table.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct MulticastRouteEntry<D: StrongDeviceIdentifier, BT: InstantBindingsTypes> {
    pub(crate) route: MulticastRoute<D>,
    // NB: Hold the statistics as an AtomicInstant so that they can be updated
    // without write-locking the multicast route table.
    pub(crate) stats: MulticastRouteStats<BT::AtomicInstant>,
}

impl<D: StrongDeviceIdentifier, BT: InstantBindingsTypes> MulticastRouteEntry<D, BT> {
    /// Writes this [`MulticastRouteEntry`] to the inspector.
    // NB: This exists as a method rather than an implementation of
    // `Inspectable` because we need to restrict the type of `I::DeviceId`.
    pub(crate) fn inspect<I: Inspector, II: InspectorDeviceExt<D>>(&self, inspector: &mut I) {
        let MulticastRouteEntry {
            route: MulticastRoute { input_interface, action },
            stats: MulticastRouteStats { last_used },
        } = self;
        II::record_device(inspector, "InputInterface", input_interface);
        let Action::Forward(targets) = action;
        inspector.record_child("ForwardingTargets", |inspector| {
            for MulticastRouteTarget { output_interface, min_ttl } in targets.iter() {
                inspector.record_unnamed_child(|inspector| {
                    II::record_device(inspector, "OutputInterface", output_interface);
                    inspector.record_uint("MinTTL", *min_ttl);
                });
            }
        });
        inspector.record_child("Statistics", |inspector| {
            last_used.load(Ordering::Relaxed).record("LastUsed", inspector);
        });
    }
}

/// All attributes of a multicast route, excluding the [`MulticastRouteKey`].
///
/// This type acts as a witness that the route is valid.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MulticastRoute<D: StrongDeviceIdentifier> {
    /// The interface on which packets must arrive in order to use this route.
    pub(crate) input_interface: D,
    /// The route's action.
    pub(crate) action: Action<D>,
}

/// The action to be taken for a packet that matches a route.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum Action<D: StrongDeviceIdentifier> {
    /// Forward the packet out of each provided [`Target`].
    Forward(MulticastRouteTargets<D>),
}

/// The collection of targets out of which to forward a multicast packet.
///
/// Note, storing the targets behind an `Arc` allows us to return a reference
/// to the targets, to contexts that are not protected by the multicast route
/// table lock, without cloning the underlying data. Here, an `Arc<Mutex<...>>`
/// is unnecessary, because the underlying targets list is never modified. This
/// is not to say that a route's targets are never modified (e.g. device removal
/// prunes the list of targets); in such cases the route's target list is
/// *replaced* with a new allocation. This strategy allows us to avoid
/// additional locking on the hot path, at the cost of extra allocations for
/// certain control operations.
pub type MulticastRouteTargets<D> = Arc<[MulticastRouteTarget<D>]>;

/// The target out of which to forward a multicast packet.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct MulticastRouteTarget<D: StrongDeviceIdentifier> {
    /// An interface the packet should be forwarded out of.
    pub output_interface: D,
    /// The minimum TTL of the packet in order for it to be forwarded.
    ///
    /// A value of 0 allows packets to be forwarded, regardless of their TTL.
    pub min_ttl: u8,
}

/// Errors returned by [`MulticastRoute::new_forward`].
#[derive(Debug, Eq, PartialEq)]
pub enum ForwardMulticastRouteError {
    /// The route's list of targets is empty.
    EmptyTargetList,
    /// The route's `input_interface` is also listed as a target. This would
    /// create a routing loop.
    InputInterfaceIsTarget,
    /// The route lists the same [`Target`] output_interface multiple times.
    DuplicateTarget,
}

impl<D: StrongDeviceIdentifier> MulticastRoute<D> {
    /// Construct a new [`MulticastRoute`] with [`Action::Forward`].
    pub fn new_forward(
        input_interface: D,
        targets: MulticastRouteTargets<D>,
    ) -> Result<Self, ForwardMulticastRouteError> {
        if targets.is_empty() {
            return Err(ForwardMulticastRouteError::EmptyTargetList);
        }
        if targets.iter().any(|MulticastRouteTarget { output_interface, min_ttl: _ }| {
            output_interface == &input_interface
        }) {
            return Err(ForwardMulticastRouteError::InputInterfaceIsTarget);
        }

        // NB: Search for duplicates by doing a naive n^2 comparison. This is
        // expected to be more performant than other approaches (e.g.
        // sort + dedup, or collecting into a hash map) given how small the vec
        // is expected to be.
        for (index, target_a) in targets.iter().enumerate() {
            // NB: Only check the targets that occur in the vec *after* this
            // one. The targets before this one were checked in previous
            // iterations.
            if targets[index + 1..]
                .iter()
                .any(|target_b| target_a.output_interface == target_b.output_interface)
            {
                return Err(ForwardMulticastRouteError::DuplicateTarget);
            }
        }

        Ok(MulticastRoute { input_interface, action: Action::Forward(targets) })
    }
}

/// Statistics about a [`MulticastRoute`].
#[derive(Debug, Eq, PartialEq)]
pub struct MulticastRouteStats<Instant> {
    /// The last time the route was used to route a packet.
    ///
    /// This value is initialized to the current time when a route is installed
    /// in the route table, and updated every time the route is selected during
    /// multicast route lookup. Notably, it is updated regardless of whether the
    /// packet is actually forwarded; it might be dropped after the routing
    /// decision for a number reasons (e.g. dropped by the filtering engine,
    /// dropped at the device layer, etc).
    pub last_used: Instant,
}

#[cfg(test)]
mod tests {
    use super::*;

    use alloc::vec;
    use alloc::vec::Vec;
    use net_declare::{net_ip_v4, net_ip_v6};
    use netstack3_base::testutil::MultipleDevicesId;
    use test_case::test_case;

    const UNICAST_V4: Ipv4Addr = net_ip_v4!("192.0.2.1");
    const MULTICAST_V4: Ipv4Addr = net_ip_v4!("224.0.1.1");
    const LL_UNICAST_V4: Ipv4Addr = net_ip_v4!("169.254.0.1");
    const LL_MULTICAST_V4: Ipv4Addr = net_ip_v4!("224.0.0.1");
    const UNICAST_V6: Ipv6Addr = net_ip_v6!("2001:0DB8::1");
    const MULTICAST_V6: Ipv6Addr = net_ip_v6!("ff0e::1");
    const LL_UNICAST_V6: Ipv6Addr = net_ip_v6!("fe80::1");
    const LL_MULTICAST_V6: Ipv6Addr = net_ip_v6!("ff02::1");
    const V4_MAPPED_V6: Ipv6Addr = net_ip_v6!("::FFFF:192.0.2.1");

    #[test_case(UNICAST_V4, MULTICAST_V4 => true; "success")]
    #[test_case(UNICAST_V4, UNICAST_V4 => false; "unicast_dst")]
    #[test_case(UNICAST_V4, Ipv4::UNSPECIFIED_ADDRESS => false; "unspecified_dst")]
    #[test_case(MULTICAST_V4, MULTICAST_V4 => false; "multicast_src")]
    #[test_case(Ipv4::UNSPECIFIED_ADDRESS, MULTICAST_V4 => false; "unspecified_src")]
    #[test_case(LL_UNICAST_V4, MULTICAST_V4 => false; "ll_unicast_src")]
    #[test_case(UNICAST_V4, LL_MULTICAST_V4 => false; "ll_multicast_dst")]
    fn new_ipv4_route_key(src_addr: Ipv4Addr, dst_addr: Ipv4Addr) -> bool {
        MulticastRouteKey::<Ipv4>::new(src_addr, dst_addr).is_some()
    }

    #[test_case(UNICAST_V6, MULTICAST_V6 => true; "success")]
    #[test_case(UNICAST_V6, UNICAST_V6 => false; "unicast_dst")]
    #[test_case(UNICAST_V6, Ipv6::UNSPECIFIED_ADDRESS => false; "unspecified_dst")]
    #[test_case(MULTICAST_V6, MULTICAST_V6 => false; "multicast_src")]
    #[test_case(Ipv6::UNSPECIFIED_ADDRESS, MULTICAST_V6 => false; "unspecified_src")]
    #[test_case(LL_UNICAST_V6, MULTICAST_V6 => false; "ll_unicast_src")]
    #[test_case(UNICAST_V6, LL_MULTICAST_V6 => false; "ll_multicast_dst")]
    #[test_case(V4_MAPPED_V6, LL_MULTICAST_V6 => false; "mapped_src")]
    fn new_ipv6_route_key(src_addr: Ipv6Addr, dst_addr: Ipv6Addr) -> bool {
        MulticastRouteKey::<Ipv6>::new(src_addr, dst_addr).is_some()
    }

    #[test_case(MultipleDevicesId::A, vec![] =>
        Some(ForwardMulticastRouteError::EmptyTargetList); "empty_target_list")]
    #[test_case(MultipleDevicesId::A, vec![MultipleDevicesId::A] =>
        Some(ForwardMulticastRouteError::InputInterfaceIsTarget); "input_interface_is_target")]
    #[test_case(MultipleDevicesId::A, vec![MultipleDevicesId::B, MultipleDevicesId::B] =>
        Some(ForwardMulticastRouteError::DuplicateTarget); "duplicate_target")]
    #[test_case(MultipleDevicesId::A, vec![MultipleDevicesId::B, MultipleDevicesId::C] =>
        None; "valid_route")]
    fn new_forward(
        input_interface: MultipleDevicesId,
        output_interfaces: Vec<MultipleDevicesId>,
    ) -> Option<ForwardMulticastRouteError> {
        let targets = output_interfaces
            .into_iter()
            .map(|output_interface| MulticastRouteTarget { output_interface, min_ttl: 0 })
            .collect();
        MulticastRoute::new_forward(input_interface, targets).err()
    }
}