fidl_fuchsia_net_matchers_ext/
lib.rs

1// Copyright 2025 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//! Extensions for the fuchsia.net.matchers FIDL library.
6//!
7//! Note that this library as written is not meant for inclusion in the SDK. It
8//! is only meant to be used in conjunction with a netstack that is compiled
9//! against the same API level of the `fuchsia.net.matchers` FIDL library. This
10//! library opts in to compile-time and runtime breakage when the FIDL library
11//! is evolved in order to enforce that it is updated along with the FIDL
12//! library itself.
13
14use std::fmt::Debug;
15use std::num::NonZeroU64;
16use std::ops::RangeInclusive;
17
18use fidl::marker::SourceBreaking;
19use fidl_fuchsia_net_ext::IntoExt;
20use thiserror::Error;
21use {
22    fidl_fuchsia_net as fnet, fidl_fuchsia_net_interfaces as fnet_interfaces,
23    fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext,
24    fidl_fuchsia_net_matchers as fnet_matchers,
25};
26
27/// Extension type for [`fnet_matchers::Interface`].
28#[derive(Debug, Clone, PartialEq)]
29pub enum Interface {
30    Id(NonZeroU64),
31    Name(fnet_interfaces::Name),
32    PortClass(fnet_interfaces_ext::PortClass),
33}
34
35/// Errors when creating an [`Interface`].
36#[derive(Debug, Error, PartialEq)]
37pub enum InterfaceError {
38    #[error("interface matcher specified an invalid ID of 0")]
39    ZeroId,
40    #[error(transparent)]
41    UnknownPortClass(fnet_interfaces_ext::UnknownPortClassError),
42    #[error("interface union is of an unknown variant")]
43    UnknownUnionVariant,
44}
45
46impl From<Interface> for fnet_matchers::Interface {
47    fn from(matcher: Interface) -> Self {
48        match matcher {
49            Interface::Id(id) => Self::Id(id.get()),
50            Interface::Name(name) => Self::Name(name),
51            Interface::PortClass(port_class) => Self::PortClass(port_class.into()),
52        }
53    }
54}
55
56impl TryFrom<fnet_matchers::Interface> for Interface {
57    type Error = InterfaceError;
58
59    fn try_from(matcher: fnet_matchers::Interface) -> Result<Self, Self::Error> {
60        match matcher {
61            fnet_matchers::Interface::Id(id) => {
62                let id = NonZeroU64::new(id).ok_or(InterfaceError::ZeroId)?;
63                Ok(Self::Id(id))
64            }
65            fnet_matchers::Interface::Name(name) => Ok(Self::Name(name)),
66            fnet_matchers::Interface::PortClass(port_class) => {
67                port_class.try_into().map(Self::PortClass).map_err(InterfaceError::UnknownPortClass)
68            }
69            fnet_matchers::Interface::__SourceBreaking { .. } => {
70                Err(InterfaceError::UnknownUnionVariant)
71            }
72        }
73    }
74}
75
76/// Extension type for the `Subnet` variant of [`fnet_matchers::Address`].
77///
78/// This type witnesses to the invariant that the prefix length of the subnet is
79/// no greater than the number of bits in the IP address, and that no host bits
80/// in the network address are set.
81#[derive(Clone, Copy, Eq, Hash, PartialEq)]
82pub struct Subnet(fnet::Subnet);
83
84/// Errors when creating a [`Subnet`].
85#[derive(Debug, Error, PartialEq)]
86pub enum SubnetError {
87    #[error("prefix length of subnet is longer than number of bits in IP address")]
88    PrefixTooLong,
89    #[error("host bits are set in subnet network")]
90    HostBitsSet,
91}
92
93impl Subnet {
94    pub fn get(&self) -> fnet::Subnet {
95        let Subnet(subnet) = &self;
96        *subnet
97    }
98}
99
100impl From<Subnet> for fnet::Subnet {
101    fn from(subnet: Subnet) -> Self {
102        let Subnet(subnet) = subnet;
103        subnet
104    }
105}
106
107impl TryFrom<fnet::Subnet> for Subnet {
108    type Error = SubnetError;
109
110    fn try_from(subnet: fnet::Subnet) -> Result<Self, Self::Error> {
111        let fnet::Subnet { addr, prefix_len } = subnet;
112
113        // We convert to `net_types::ip::Subnet` to validate the subnet's
114        // properties, but we don't store the subnet as that type because we
115        // want to avoid forcing all `Resource` types in this library to be
116        // parameterized on IP version.
117        let result = match addr {
118            fnet::IpAddress::Ipv4(v4) => {
119                net_types::ip::Subnet::<net_types::ip::Ipv4Addr>::new(v4.into_ext(), prefix_len)
120                    .map(|_| Subnet(subnet))
121            }
122            fnet::IpAddress::Ipv6(v6) => {
123                net_types::ip::Subnet::<net_types::ip::Ipv6Addr>::new(v6.into_ext(), prefix_len)
124                    .map(|_| Subnet(subnet))
125            }
126        };
127        result.map_err(|e| match e {
128            net_types::ip::SubnetError::PrefixTooLong => SubnetError::PrefixTooLong,
129            net_types::ip::SubnetError::HostBitsSet => SubnetError::HostBitsSet,
130        })
131    }
132}
133
134impl Debug for Subnet {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        let fnet::Subnet { addr, prefix_len } = self.0;
137
138        match addr {
139            fnet::IpAddress::Ipv4(v4) => {
140                let subnet = net_types::ip::Subnet::<net_types::ip::Ipv4Addr>::new(
141                    v4.into_ext(),
142                    prefix_len,
143                );
144
145                match subnet {
146                    Ok(inner) => inner.fmt(f),
147                    Err(err) => err.fmt(f),
148                }
149            }
150            fnet::IpAddress::Ipv6(v6) => {
151                let subnet = net_types::ip::Subnet::<net_types::ip::Ipv6Addr>::new(
152                    v6.into_ext(),
153                    prefix_len,
154                );
155
156                match subnet {
157                    Ok(inner) => inner.fmt(f),
158                    Err(err) => err.fmt(f),
159                }
160            }
161        }
162    }
163}
164
165/// Extension type for [`fnet_matchers::AddressRange`].
166///
167/// This type witnesses to the invariant that `start` is in the same IP family
168/// as `end`, and that `start <= end`. (Comparisons are performed on the
169/// numerical big-endian representation of the IP address.)
170#[derive(Debug, Clone, PartialEq)]
171pub struct AddressRange {
172    range: RangeInclusive<fnet::IpAddress>,
173}
174
175/// Errors when creating an [`AddressRange`].
176#[derive(Debug, Error, PartialEq)]
177pub enum AddressRangeError {
178    #[error("invalid address range (start must be <= end)")]
179    Invalid,
180    #[error("address range start and end addresses are not the same IP family")]
181    FamilyMismatch,
182}
183
184impl AddressRange {
185    pub fn start(&self) -> fnet::IpAddress {
186        *self.range.start()
187    }
188
189    pub fn end(&self) -> fnet::IpAddress {
190        *self.range.end()
191    }
192}
193
194impl From<AddressRange> for fnet_matchers::AddressRange {
195    fn from(range: AddressRange) -> Self {
196        Self { start: range.start(), end: range.end() }
197    }
198}
199
200impl TryFrom<fnet_matchers::AddressRange> for AddressRange {
201    type Error = AddressRangeError;
202
203    fn try_from(range: fnet_matchers::AddressRange) -> Result<Self, Self::Error> {
204        let fnet_matchers::AddressRange { start, end } = range;
205        match (start, end) {
206            (
207                fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: start_bytes }),
208                fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: end_bytes }),
209            ) => {
210                if u32::from_be_bytes(start_bytes) > u32::from_be_bytes(end_bytes) {
211                    Err(AddressRangeError::Invalid)
212                } else {
213                    Ok(Self { range: start..=end })
214                }
215            }
216            (
217                fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr: start_bytes }),
218                fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr: end_bytes }),
219            ) => {
220                if u128::from_be_bytes(start_bytes) > u128::from_be_bytes(end_bytes) {
221                    Err(AddressRangeError::Invalid)
222                } else {
223                    Ok(Self { range: start..=end })
224                }
225            }
226            _ => Err(AddressRangeError::FamilyMismatch),
227        }
228    }
229}
230
231/// Extension type for [`fnet_matchers::Address`].
232#[derive(Clone, PartialEq)]
233pub enum AddressMatcherType {
234    Subnet(Subnet),
235    Range(AddressRange),
236}
237
238/// Errors when creating an [`AddressMatcherType`].
239#[derive(Debug, Error, PartialEq)]
240pub enum AddressMatcherTypeError {
241    #[error("AddressMatcher is of an unknown variant")]
242    UnknownUnionVariant,
243    #[error("subnet conversion error: {0}")]
244    Subnet(SubnetError),
245    #[error("address range conversion error: {0}")]
246    AddressRange(AddressRangeError),
247}
248
249impl From<SubnetError> for AddressMatcherTypeError {
250    fn from(value: SubnetError) -> Self {
251        AddressMatcherTypeError::Subnet(value)
252    }
253}
254impl From<AddressRangeError> for AddressMatcherTypeError {
255    fn from(value: AddressRangeError) -> Self {
256        AddressMatcherTypeError::AddressRange(value)
257    }
258}
259
260impl From<AddressMatcherType> for fnet_matchers::AddressMatcherType {
261    fn from(matcher: AddressMatcherType) -> Self {
262        match matcher {
263            AddressMatcherType::Subnet(subnet) => Self::Subnet(subnet.into()),
264            AddressMatcherType::Range(range) => Self::Range(range.into()),
265        }
266    }
267}
268
269impl TryFrom<fnet_matchers::AddressMatcherType> for AddressMatcherType {
270    type Error = AddressMatcherTypeError;
271
272    fn try_from(matcher: fnet_matchers::AddressMatcherType) -> Result<Self, Self::Error> {
273        match matcher {
274            fnet_matchers::AddressMatcherType::Subnet(subnet) => {
275                Ok(Self::Subnet(subnet.try_into()?))
276            }
277            fnet_matchers::AddressMatcherType::Range(range) => Ok(Self::Range(range.try_into()?)),
278            fnet_matchers::AddressMatcherType::__SourceBreaking { .. } => {
279                Err(AddressMatcherTypeError::UnknownUnionVariant)
280            }
281        }
282    }
283}
284
285impl Debug for AddressMatcherType {
286    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287        match self {
288            AddressMatcherType::Subnet(subnet) => subnet.fmt(f),
289            AddressMatcherType::Range(address_range) => address_range.fmt(f),
290        }
291    }
292}
293
294/// Extension type for [`fnet_matchers::Address`].
295#[derive(Debug, Clone, PartialEq)]
296pub struct Address {
297    pub matcher: AddressMatcherType,
298    pub invert: bool,
299}
300
301/// Errors when creating an [`Address`].
302#[derive(Debug, Error, PartialEq)]
303pub enum AddressError {
304    #[error("address matcher conversion failure: {0}")]
305    AddressMatcherType(AddressMatcherTypeError),
306}
307
308impl From<AddressMatcherTypeError> for AddressError {
309    fn from(value: AddressMatcherTypeError) -> Self {
310        Self::AddressMatcherType(value)
311    }
312}
313
314impl From<Address> for fnet_matchers::Address {
315    fn from(matcher: Address) -> Self {
316        let Address { matcher, invert } = matcher;
317        Self { matcher: matcher.into(), invert }
318    }
319}
320
321impl TryFrom<fnet_matchers::Address> for Address {
322    type Error = AddressError;
323
324    fn try_from(matcher: fnet_matchers::Address) -> Result<Self, Self::Error> {
325        let fnet_matchers::Address { matcher, invert } = matcher;
326        Ok(Self { matcher: matcher.try_into()?, invert })
327    }
328}
329
330/// Extension type for [`fnet_matchers::Port`].
331///
332/// This type witnesses to the invariant that `start <= end`.
333#[derive(Debug, Clone, PartialEq)]
334pub struct Port {
335    range: RangeInclusive<u16>,
336    pub invert: bool,
337}
338
339/// Errors when creating a `Port`.
340#[derive(Debug, Error, PartialEq)]
341pub enum PortError {
342    #[error("invalid port range (start must be <= end)")]
343    InvalidPortRange,
344}
345
346impl Port {
347    pub fn new(start: u16, end: u16, invert: bool) -> Result<Self, PortError> {
348        if start > end {
349            return Err(PortError::InvalidPortRange);
350        }
351        Ok(Self { range: start..=end, invert })
352    }
353
354    pub fn range(&self) -> &RangeInclusive<u16> {
355        &self.range
356    }
357
358    pub fn start(&self) -> u16 {
359        *self.range.start()
360    }
361
362    pub fn end(&self) -> u16 {
363        *self.range.end()
364    }
365}
366
367impl From<Port> for fnet_matchers::Port {
368    fn from(matcher: Port) -> Self {
369        let Port { range, invert } = matcher;
370        Self { start: *range.start(), end: *range.end(), invert }
371    }
372}
373
374impl TryFrom<fnet_matchers::Port> for Port {
375    type Error = PortError;
376
377    fn try_from(matcher: fnet_matchers::Port) -> Result<Self, Self::Error> {
378        let fnet_matchers::Port { start, end, invert } = matcher;
379        if start > end {
380            return Err(PortError::InvalidPortRange);
381        }
382        Ok(Self { range: start..=end, invert })
383    }
384}
385
386/// Extension type for [`fnet_matchers::PacketTransportProtocol`].
387#[derive(Clone, PartialEq)]
388pub enum TransportProtocol {
389    Tcp { src_port: Option<Port>, dst_port: Option<Port> },
390    Udp { src_port: Option<Port>, dst_port: Option<Port> },
391    Icmp,
392    Icmpv6,
393}
394
395/// Errors when creating a [`TransportProtocol`].
396#[derive(Debug, Error, PartialEq)]
397pub enum TransportProtocolError {
398    #[error("invalid port: {0}")]
399    Port(PortError),
400    #[error("TransportProtocol is of an unknown variant")]
401    UnknownUnionVariant,
402}
403
404impl From<PortError> for TransportProtocolError {
405    fn from(value: PortError) -> Self {
406        TransportProtocolError::Port(value)
407    }
408}
409
410impl From<TransportProtocol> for fnet_matchers::PacketTransportProtocol {
411    fn from(matcher: TransportProtocol) -> Self {
412        match matcher {
413            TransportProtocol::Tcp { src_port, dst_port } => Self::Tcp(fnet_matchers::TcpPacket {
414                src_port: src_port.map(Into::into),
415                dst_port: dst_port.map(Into::into),
416                __source_breaking: SourceBreaking,
417            }),
418            TransportProtocol::Udp { src_port, dst_port } => Self::Udp(fnet_matchers::UdpPacket {
419                src_port: src_port.map(Into::into),
420                dst_port: dst_port.map(Into::into),
421                __source_breaking: SourceBreaking,
422            }),
423            TransportProtocol::Icmp => Self::Icmp(fnet_matchers::IcmpPacket::default()),
424            TransportProtocol::Icmpv6 => Self::Icmpv6(fnet_matchers::Icmpv6Packet::default()),
425        }
426    }
427}
428
429impl TryFrom<fnet_matchers::PacketTransportProtocol> for TransportProtocol {
430    type Error = TransportProtocolError;
431
432    fn try_from(matcher: fnet_matchers::PacketTransportProtocol) -> Result<Self, Self::Error> {
433        match matcher {
434            fnet_matchers::PacketTransportProtocol::Tcp(fnet_matchers::TcpPacket {
435                src_port,
436                dst_port,
437                __source_breaking,
438            }) => Ok(Self::Tcp {
439                src_port: src_port.map(TryInto::try_into).transpose()?,
440                dst_port: dst_port.map(TryInto::try_into).transpose()?,
441            }),
442            fnet_matchers::PacketTransportProtocol::Udp(fnet_matchers::UdpPacket {
443                src_port,
444                dst_port,
445                __source_breaking,
446            }) => Ok(Self::Udp {
447                src_port: src_port.map(TryInto::try_into).transpose()?,
448                dst_port: dst_port.map(TryInto::try_into).transpose()?,
449            }),
450            fnet_matchers::PacketTransportProtocol::Icmp(fnet_matchers::IcmpPacket {
451                __source_breaking,
452            }) => Ok(Self::Icmp),
453            fnet_matchers::PacketTransportProtocol::Icmpv6(fnet_matchers::Icmpv6Packet {
454                __source_breaking,
455            }) => Ok(Self::Icmpv6),
456            fnet_matchers::PacketTransportProtocol::__SourceBreaking { .. } => {
457                Err(TransportProtocolError::UnknownUnionVariant)
458            }
459        }
460    }
461}
462
463impl Debug for TransportProtocol {
464    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
465        // Omit empty fields.
466        match self {
467            TransportProtocol::Tcp { src_port, dst_port } => {
468                let mut debug_struct = f.debug_struct("Tcp");
469
470                // Omit empty fields.
471                if let Some(port) = &src_port {
472                    let _ = debug_struct.field("src_port", port);
473                }
474
475                if let Some(port) = &dst_port {
476                    let _ = debug_struct.field("dst_port", port);
477                }
478
479                debug_struct.finish()
480            }
481            TransportProtocol::Udp { src_port, dst_port } => {
482                let mut debug_struct = f.debug_struct("Udp");
483
484                // Omit empty fields.
485                if let Some(port) = &src_port {
486                    let _ = debug_struct.field("src_port", port);
487                }
488
489                if let Some(port) = &dst_port {
490                    let _ = debug_struct.field("dst_port", port);
491                }
492
493                debug_struct.finish()
494            }
495            TransportProtocol::Icmp => f.write_str("Icmp"),
496            TransportProtocol::Icmpv6 => f.write_str("Icmpv6"),
497        }
498    }
499}
500
501#[cfg(test)]
502mod tests {
503    use net_declare::{fidl_ip, fidl_subnet};
504    use test_case::test_case;
505
506    use super::*;
507
508    #[test_case(
509        fnet_matchers::Interface::Id(1),
510        Interface::Id(NonZeroU64::new(1).unwrap());
511        "Interface"
512    )]
513    #[test_case(
514        fnet_matchers::AddressMatcherType::Subnet(fidl_subnet!("192.0.2.0/24")),
515        AddressMatcherType::Subnet(Subnet(fidl_subnet!("192.0.2.0/24")));
516        "AddressMatcherType"
517    )]
518    #[test_case(
519        fnet_matchers::Address {
520            matcher: fnet_matchers::AddressMatcherType::Subnet(fidl_subnet!("192.0.2.0/24")),
521            invert: true,
522        },
523        Address {
524            matcher: AddressMatcherType::Subnet(Subnet(fidl_subnet!("192.0.2.0/24"))),
525            invert: true,
526        };
527        "Address"
528    )]
529    #[test_case(
530        fnet_matchers::AddressRange {
531            start: fidl_ip!("192.0.2.0"),
532            end: fidl_ip!("192.0.2.1"),
533        },
534        AddressRange {
535            range: fidl_ip!("192.0.2.0")..=fidl_ip!("192.0.2.1"),
536        };
537        "AddressRange"
538    )]
539    #[test_case(
540        fnet_matchers::PacketTransportProtocol::Udp(fnet_matchers::UdpPacket {
541            src_port: Some(fnet_matchers::Port { start: 1024, end: u16::MAX, invert: false }),
542            dst_port: None,
543            ..Default::default()
544        }),
545        TransportProtocol::Udp {
546            src_port: Some(Port { range: 1024..=u16::MAX, invert: false }),
547            dst_port: None,
548        };
549        "TransportProtocol"
550    )]
551    fn convert_from_fidl_and_back<F, E>(fidl_type: F, local_type: E)
552    where
553        E: TryFrom<F> + Clone + Debug + PartialEq,
554        <E as TryFrom<F>>::Error: Debug + PartialEq,
555        F: From<E> + Clone + Debug + PartialEq,
556    {
557        assert_eq!(fidl_type.clone().try_into(), Ok(local_type.clone()));
558        assert_eq!(<_ as Into<F>>::into(local_type), fidl_type.clone());
559    }
560
561    #[test]
562    fn interface_matcher_try_from_unknown_variant() {
563        assert_eq!(
564            Interface::try_from(fnet_matchers::Interface::__SourceBreaking { unknown_ordinal: 0 }),
565            Err(InterfaceError::UnknownUnionVariant)
566        );
567    }
568
569    #[test]
570    fn interface_matcher_try_from_invalid() {
571        assert_eq!(
572            Interface::try_from(fnet_matchers::Interface::Id(0)),
573            Err(InterfaceError::ZeroId)
574        );
575    }
576
577    #[test]
578    fn address_matcher_type_try_from_unknown_variant() {
579        assert_eq!(
580            AddressMatcherType::try_from(fnet_matchers::AddressMatcherType::__SourceBreaking {
581                unknown_ordinal: 0
582            }),
583            Err(AddressMatcherTypeError::UnknownUnionVariant)
584        );
585    }
586
587    #[test]
588    fn subnet_try_from_invalid() {
589        assert_eq!(
590            Subnet::try_from(fnet::Subnet { addr: fidl_ip!("192.0.2.1"), prefix_len: 33 }),
591            Err(SubnetError::PrefixTooLong)
592        );
593        assert_eq!(Subnet::try_from(fidl_subnet!("192.0.2.1/24")), Err(SubnetError::HostBitsSet));
594    }
595
596    #[test]
597    fn address_range_try_from_invalid() {
598        assert_eq!(
599            AddressRange::try_from(fnet_matchers::AddressRange {
600                start: fidl_ip!("192.0.2.1"),
601                end: fidl_ip!("192.0.2.0"),
602            }),
603            Err(AddressRangeError::Invalid)
604        );
605        assert_eq!(
606            AddressRange::try_from(fnet_matchers::AddressRange {
607                start: fidl_ip!("2001:db8::1"),
608                end: fidl_ip!("2001:db8::"),
609            }),
610            Err(AddressRangeError::Invalid)
611        );
612    }
613
614    #[test]
615    fn address_range_try_from_family_mismatch() {
616        assert_eq!(
617            AddressRange::try_from(fnet_matchers::AddressRange {
618                start: fidl_ip!("192.0.2.0"),
619                end: fidl_ip!("2001:db8::"),
620            }),
621            Err(AddressRangeError::FamilyMismatch)
622        );
623    }
624
625    #[test]
626    fn port_matcher_try_from_invalid() {
627        assert_eq!(
628            Port::try_from(fnet_matchers::Port { start: 1, end: 0, invert: false }),
629            Err(PortError::InvalidPortRange)
630        );
631    }
632
633    #[test]
634    fn transport_protocol_try_from_unknown_variant() {
635        assert_eq!(
636            TransportProtocol::try_from(fnet_matchers::PacketTransportProtocol::__SourceBreaking {
637                unknown_ordinal: 0
638            }),
639            Err(TransportProtocolError::UnknownUnionVariant)
640        );
641    }
642}