fidl_fuchsia_net_routes_ext/
rules.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//! Extensions for route rules FIDL.
6
7use std::fmt::Debug;
8use std::ops::RangeInclusive;
9
10use async_utils::{fold, stream};
11use fidl::endpoints::{DiscoverableProtocolMarker, ProtocolMarker, Proxy as _};
12use fidl_fuchsia_net_ext::{IntoExt as _, TryIntoExt as _};
13use futures::future::Either;
14use futures::{Stream, TryStreamExt as _};
15use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv6, Subnet};
16use thiserror::Error;
17use {
18    fidl_fuchsia_net as fnet, fidl_fuchsia_net_routes as fnet_routes,
19    fidl_fuchsia_net_routes_admin as fnet_routes_admin,
20};
21
22use crate::{FidlRouteIpExt, Responder, SliceResponder, WatcherCreationError, impl_responder};
23
24/// Observation extension for the rules part of `fuchsia.net.routes` FIDL API.
25pub trait FidlRuleIpExt: Ip {
26    /// The "rules watcher" protocol to use for this IP version.
27    type RuleWatcherMarker: ProtocolMarker<RequestStream = Self::RuleWatcherRequestStream>;
28    /// The "rules watcher" request stream.
29    type RuleWatcherRequestStream: fidl::endpoints::RequestStream<Ok: Send, ControlHandle: Send>;
30    /// The rule event to be watched.
31    type RuleEvent: From<RuleEvent<Self>>
32        + TryInto<RuleEvent<Self>, Error = RuleFidlConversionError>
33        + Unpin;
34    /// The responder to the watch request.
35    type RuleWatcherWatchResponder: SliceResponder<Self::RuleEvent>;
36
37    /// Turns a FIDL rule watcher request into the extension type.
38    fn into_rule_watcher_request(
39        request: fidl::endpoints::Request<Self::RuleWatcherMarker>,
40    ) -> RuleWatcherRequest<Self>;
41}
42
43impl_responder!(fnet_routes::RuleWatcherV4WatchResponder, &[fnet_routes::RuleEventV4]);
44impl_responder!(fnet_routes::RuleWatcherV6WatchResponder, &[fnet_routes::RuleEventV6]);
45
46impl FidlRuleIpExt for Ipv4 {
47    type RuleWatcherMarker = fnet_routes::RuleWatcherV4Marker;
48    type RuleWatcherRequestStream = fnet_routes::RuleWatcherV4RequestStream;
49    type RuleEvent = fnet_routes::RuleEventV4;
50    type RuleWatcherWatchResponder = fnet_routes::RuleWatcherV4WatchResponder;
51
52    fn into_rule_watcher_request(
53        request: fidl::endpoints::Request<Self::RuleWatcherMarker>,
54    ) -> RuleWatcherRequest<Self> {
55        RuleWatcherRequest::from(request)
56    }
57}
58
59impl FidlRuleIpExt for Ipv6 {
60    type RuleWatcherMarker = fnet_routes::RuleWatcherV6Marker;
61    type RuleWatcherRequestStream = fnet_routes::RuleWatcherV6RequestStream;
62    type RuleEvent = fnet_routes::RuleEventV6;
63    type RuleWatcherWatchResponder = fnet_routes::RuleWatcherV6WatchResponder;
64
65    fn into_rule_watcher_request(
66        request: fidl::endpoints::Request<Self::RuleWatcherMarker>,
67    ) -> RuleWatcherRequest<Self> {
68        RuleWatcherRequest::from(request)
69    }
70}
71
72/// The request for the rules watchers.
73pub enum RuleWatcherRequest<I: FidlRuleIpExt> {
74    /// Hanging-Get style API for observing routing rule changes.
75    Watch {
76        /// Responder for the events.
77        responder: I::RuleWatcherWatchResponder,
78    },
79}
80
81impl From<fnet_routes::RuleWatcherV4Request> for RuleWatcherRequest<Ipv4> {
82    fn from(req: fnet_routes::RuleWatcherV4Request) -> Self {
83        match req {
84            fnet_routes::RuleWatcherV4Request::Watch { responder } => {
85                RuleWatcherRequest::Watch { responder }
86            }
87        }
88    }
89}
90
91impl From<fnet_routes::RuleWatcherV6Request> for RuleWatcherRequest<Ipv6> {
92    fn from(req: fnet_routes::RuleWatcherV6Request) -> Self {
93        match req {
94            fnet_routes::RuleWatcherV6Request::Watch { responder } => {
95                RuleWatcherRequest::Watch { responder }
96            }
97        }
98    }
99}
100
101/// An installed IPv4 routing rule.
102#[derive(Debug, Hash, PartialEq, Eq, Clone)]
103pub struct InstalledRule<I: Ip> {
104    /// Rule sets are ordered by the rule set priority, rule sets are disjoint
105    /// and don’t have interleaving rules among them.
106    pub priority: RuleSetPriority,
107    /// Rules within a rule set are locally ordered, together with the rule set
108    /// priority, this defines a global order for all installed rules.
109    pub index: RuleIndex,
110    /// The matcher part of the rule, the rule is a no-op if the matcher does
111    /// not match the packet.
112    pub matcher: RuleMatcher<I>,
113    /// The action part of the rule that describes what to do if the matcher
114    /// matches the packet.
115    pub action: RuleAction,
116}
117
118impl TryFrom<fnet_routes::InstalledRuleV4> for InstalledRule<Ipv4> {
119    type Error = RuleFidlConversionError;
120    fn try_from(
121        fnet_routes::InstalledRuleV4 {
122        rule_set_priority,
123        rule_index,
124        matcher,
125        action,
126    }: fnet_routes::InstalledRuleV4,
127    ) -> Result<Self, Self::Error> {
128        Ok(Self {
129            priority: rule_set_priority.into(),
130            index: rule_index.into(),
131            matcher: matcher.try_into()?,
132            action: action.into(),
133        })
134    }
135}
136
137impl TryFrom<fnet_routes::InstalledRuleV6> for InstalledRule<Ipv6> {
138    type Error = RuleFidlConversionError;
139    fn try_from(
140        fnet_routes::InstalledRuleV6 {
141        rule_set_priority,
142        rule_index,
143        matcher,
144        action,
145    }: fnet_routes::InstalledRuleV6,
146    ) -> Result<Self, Self::Error> {
147        Ok(Self {
148            priority: rule_set_priority.into(),
149            index: rule_index.into(),
150            matcher: matcher.try_into()?,
151            action: action.into(),
152        })
153    }
154}
155
156impl From<InstalledRule<Ipv4>> for fnet_routes::InstalledRuleV4 {
157    fn from(InstalledRule { priority, index, matcher, action }: InstalledRule<Ipv4>) -> Self {
158        Self {
159            rule_set_priority: priority.into(),
160            rule_index: index.into(),
161            matcher: matcher.into(),
162            action: action.into(),
163        }
164    }
165}
166
167impl From<InstalledRule<Ipv6>> for fnet_routes::InstalledRuleV6 {
168    fn from(InstalledRule { priority, index, matcher, action }: InstalledRule<Ipv6>) -> Self {
169        Self {
170            rule_set_priority: priority.into(),
171            rule_index: index.into(),
172            matcher: matcher.into(),
173            action: action.into(),
174        }
175    }
176}
177
178/// A rules watcher event.
179#[derive(Debug, Clone)]
180pub enum RuleEvent<I: Ip> {
181    /// A rule that already existed when watching started.
182    Existing(InstalledRule<I>),
183    /// Sentinel value indicating no more `existing` events will be
184    /// received.
185    Idle,
186    /// A rule that was added while watching.
187    Added(InstalledRule<I>),
188    /// A rule that was removed while watching.
189    Removed(InstalledRule<I>),
190}
191
192impl TryFrom<fnet_routes::RuleEventV4> for RuleEvent<Ipv4> {
193    type Error = RuleFidlConversionError;
194    fn try_from(event: fnet_routes::RuleEventV4) -> Result<Self, Self::Error> {
195        match event {
196            fnet_routes::RuleEventV4::Existing(rule) => Ok(RuleEvent::Existing(rule.try_into()?)),
197            fnet_routes::RuleEventV4::Idle(fnet_routes::Empty) => Ok(RuleEvent::Idle),
198            fnet_routes::RuleEventV4::Added(rule) => Ok(RuleEvent::Added(rule.try_into()?)),
199            fnet_routes::RuleEventV4::Removed(rule) => Ok(RuleEvent::Removed(rule.try_into()?)),
200            fnet_routes::RuleEventV4::__SourceBreaking { unknown_ordinal } => {
201                Err(RuleFidlConversionError::UnknownOrdinal {
202                    name: "RuleEventV4",
203                    unknown_ordinal,
204                })
205            }
206        }
207    }
208}
209
210impl TryFrom<fnet_routes::RuleEventV6> for RuleEvent<Ipv6> {
211    type Error = RuleFidlConversionError;
212    fn try_from(event: fnet_routes::RuleEventV6) -> Result<Self, Self::Error> {
213        match event {
214            fnet_routes::RuleEventV6::Existing(rule) => Ok(RuleEvent::Existing(rule.try_into()?)),
215            fnet_routes::RuleEventV6::Idle(fnet_routes::Empty) => Ok(RuleEvent::Idle),
216            fnet_routes::RuleEventV6::Added(rule) => Ok(RuleEvent::Added(rule.try_into()?)),
217            fnet_routes::RuleEventV6::Removed(rule) => Ok(RuleEvent::Removed(rule.try_into()?)),
218            fnet_routes::RuleEventV6::__SourceBreaking { unknown_ordinal } => {
219                Err(RuleFidlConversionError::UnknownOrdinal {
220                    name: "RuleEventV6",
221                    unknown_ordinal,
222                })
223            }
224        }
225    }
226}
227
228impl From<RuleEvent<Ipv4>> for fnet_routes::RuleEventV4 {
229    fn from(event: RuleEvent<Ipv4>) -> Self {
230        match event {
231            RuleEvent::Existing(r) => Self::Existing(r.into()),
232            RuleEvent::Idle => Self::Idle(fnet_routes::Empty),
233            RuleEvent::Added(r) => Self::Added(r.into()),
234            RuleEvent::Removed(r) => Self::Removed(r.into()),
235        }
236    }
237}
238
239impl From<RuleEvent<Ipv6>> for fnet_routes::RuleEventV6 {
240    fn from(event: RuleEvent<Ipv6>) -> Self {
241        match event {
242            RuleEvent::Existing(r) => Self::Existing(r.into()),
243            RuleEvent::Idle => Self::Idle(fnet_routes::Empty),
244            RuleEvent::Added(r) => Self::Added(r.into()),
245            RuleEvent::Removed(r) => Self::Removed(r.into()),
246        }
247    }
248}
249
250/// Admin extension for the rules part of `fuchsia.net.routes.admin` FIDL API.
251pub trait FidlRuleAdminIpExt: Ip {
252    /// The "rule table" protocol to use for this IP version.
253    type RuleTableMarker: DiscoverableProtocolMarker<RequestStream = Self::RuleTableRequestStream>;
254    /// The "rule set" protocol to use for this IP Version.
255    type RuleSetMarker: ProtocolMarker<RequestStream = Self::RuleSetRequestStream>;
256    /// The request stream for the rule table protocol.
257    type RuleTableRequestStream: fidl::endpoints::RequestStream<Ok: Send, ControlHandle: Send>;
258    /// The request stream for the rule set protocol.
259    type RuleSetRequestStream: fidl::endpoints::RequestStream<Ok: Send, ControlHandle: Send>;
260    /// The responder for AddRule requests.
261    type RuleSetAddRuleResponder: Responder<
262            Payload = Result<(), fnet_routes_admin::RuleSetError>,
263            ControlHandle = Self::RuleSetControlHandle,
264        >;
265    /// The responder for RemoveRule requests.
266    type RuleSetRemoveRuleResponder: Responder<
267            Payload = Result<(), fnet_routes_admin::RuleSetError>,
268            ControlHandle = Self::RuleSetControlHandle,
269        >;
270    /// The responder for AuthenticateForRouteTable requests.
271    type RuleSetAuthenticateForRouteTableResponder: Responder<
272            Payload = Result<(), fnet_routes_admin::AuthenticateForRouteTableError>,
273            ControlHandle = Self::RuleSetControlHandle,
274        >;
275    /// The control handle for RuleTable protocols.
276    type RuleTableControlHandle: fidl::endpoints::ControlHandle + Send + Clone;
277    /// The control handle for RuleSet protocols.
278    type RuleSetControlHandle: fidl::endpoints::ControlHandle + Send + Clone;
279
280    /// Turns a FIDL rule set request into the extension type.
281    fn into_rule_set_request(
282        request: fidl::endpoints::Request<Self::RuleSetMarker>,
283    ) -> RuleSetRequest<Self>;
284
285    /// Turns a FIDL rule table request into the extension type.
286    fn into_rule_table_request(
287        request: fidl::endpoints::Request<Self::RuleTableMarker>,
288    ) -> RuleTableRequest<Self>;
289}
290
291impl FidlRuleAdminIpExt for Ipv4 {
292    type RuleTableMarker = fnet_routes_admin::RuleTableV4Marker;
293    type RuleSetMarker = fnet_routes_admin::RuleSetV4Marker;
294    type RuleTableRequestStream = fnet_routes_admin::RuleTableV4RequestStream;
295    type RuleSetRequestStream = fnet_routes_admin::RuleSetV4RequestStream;
296    type RuleSetAddRuleResponder = fnet_routes_admin::RuleSetV4AddRuleResponder;
297    type RuleSetRemoveRuleResponder = fnet_routes_admin::RuleSetV4RemoveRuleResponder;
298    type RuleSetAuthenticateForRouteTableResponder =
299        fnet_routes_admin::RuleSetV4AuthenticateForRouteTableResponder;
300    type RuleTableControlHandle = fnet_routes_admin::RuleTableV4ControlHandle;
301    type RuleSetControlHandle = fnet_routes_admin::RuleSetV4ControlHandle;
302
303    fn into_rule_set_request(
304        request: fidl::endpoints::Request<Self::RuleSetMarker>,
305    ) -> RuleSetRequest<Self> {
306        RuleSetRequest::from(request)
307    }
308
309    fn into_rule_table_request(
310        request: fidl::endpoints::Request<Self::RuleTableMarker>,
311    ) -> RuleTableRequest<Self> {
312        RuleTableRequest::from(request)
313    }
314}
315
316impl FidlRuleAdminIpExt for Ipv6 {
317    type RuleTableMarker = fnet_routes_admin::RuleTableV6Marker;
318    type RuleSetMarker = fnet_routes_admin::RuleSetV6Marker;
319    type RuleTableRequestStream = fnet_routes_admin::RuleTableV6RequestStream;
320    type RuleSetRequestStream = fnet_routes_admin::RuleSetV6RequestStream;
321    type RuleSetAddRuleResponder = fnet_routes_admin::RuleSetV6AddRuleResponder;
322    type RuleSetRemoveRuleResponder = fnet_routes_admin::RuleSetV6RemoveRuleResponder;
323    type RuleSetAuthenticateForRouteTableResponder =
324        fnet_routes_admin::RuleSetV6AuthenticateForRouteTableResponder;
325    type RuleTableControlHandle = fnet_routes_admin::RuleTableV6ControlHandle;
326    type RuleSetControlHandle = fnet_routes_admin::RuleSetV6ControlHandle;
327
328    fn into_rule_set_request(
329        request: fidl::endpoints::Request<Self::RuleSetMarker>,
330    ) -> RuleSetRequest<Self> {
331        RuleSetRequest::from(request)
332    }
333
334    fn into_rule_table_request(
335        request: fidl::endpoints::Request<Self::RuleTableMarker>,
336    ) -> RuleTableRequest<Self> {
337        RuleTableRequest::from(request)
338    }
339}
340
341impl_responder!(
342    fnet_routes_admin::RuleSetV4AddRuleResponder,
343    Result<(), fnet_routes_admin::RuleSetError>,
344);
345impl_responder!(
346    fnet_routes_admin::RuleSetV4RemoveRuleResponder,
347    Result<(), fnet_routes_admin::RuleSetError>,
348);
349impl_responder!(
350    fnet_routes_admin::RuleSetV4AuthenticateForRouteTableResponder,
351    Result<(), fnet_routes_admin::AuthenticateForRouteTableError>,
352);
353impl_responder!(
354    fnet_routes_admin::RuleSetV6AddRuleResponder,
355    Result<(), fnet_routes_admin::RuleSetError>,
356);
357impl_responder!(
358    fnet_routes_admin::RuleSetV6RemoveRuleResponder,
359    Result<(), fnet_routes_admin::RuleSetError>,
360);
361impl_responder!(
362    fnet_routes_admin::RuleSetV6AuthenticateForRouteTableResponder,
363    Result<(), fnet_routes_admin::AuthenticateForRouteTableError>,
364);
365
366/// Conversion error for rule elements.
367#[derive(Debug, Error, Clone, Copy, PartialEq)]
368pub enum RuleFidlConversionError {
369    /// Destination Subnet conversion failed.
370    #[error("failed to convert `destination` to net_types subnet: {0:?}")]
371    DestinationSubnet(net_types::ip::SubnetError),
372    /// Unknown union variant.
373    #[error("unexpected union variant for {name}, got ordinal = ({unknown_ordinal})")]
374    #[allow(missing_docs)]
375    UnknownOrdinal { name: &'static str, unknown_ordinal: u64 },
376}
377
378/// The priority of the rule set, all rule sets are linearized based on this.
379#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
380pub struct RuleSetPriority(u32);
381
382impl RuleSetPriority {
383    /// Create a new [`RuleSetPriority`].
384    pub const fn new(x: u32) -> Self {
385        Self(x)
386    }
387}
388
389/// The priority for the default rule set, where the default rule that points
390/// to the main table lives.
391pub const DEFAULT_RULE_SET_PRIORITY: RuleSetPriority =
392    RuleSetPriority(fnet_routes::DEFAULT_RULE_SET_PRIORITY);
393
394/// The index of a rule within a provided rule set.
395#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
396pub struct RuleIndex(u32);
397
398impl RuleIndex {
399    /// Create a new rule index from a scalar.
400    pub const fn new(x: u32) -> Self {
401        Self(x)
402    }
403}
404
405impl From<RuleSetPriority> for u32 {
406    fn from(RuleSetPriority(x): RuleSetPriority) -> Self {
407        x
408    }
409}
410
411impl From<u32> for RuleSetPriority {
412    fn from(x: u32) -> Self {
413        Self(x)
414    }
415}
416
417impl From<RuleIndex> for u32 {
418    fn from(RuleIndex(x): RuleIndex) -> Self {
419        x
420    }
421}
422
423impl From<u32> for RuleIndex {
424    fn from(x: u32) -> Self {
425        Self(x)
426    }
427}
428
429/// How the interface of a packet should be matched against a rule.
430#[derive(Debug, Clone, PartialEq, Eq, Hash)]
431pub enum InterfaceMatcher {
432    /// Match on the name of the device.
433    DeviceName(String),
434    /// There is no bound device.
435    Unbound,
436}
437
438impl TryFrom<fnet_routes::InterfaceMatcher> for InterfaceMatcher {
439    type Error = RuleFidlConversionError;
440    fn try_from(matcher: fnet_routes::InterfaceMatcher) -> Result<Self, Self::Error> {
441        match matcher {
442            fnet_routes::InterfaceMatcher::DeviceName(name) => Ok(Self::DeviceName(name)),
443            fnet_routes::InterfaceMatcher::Unbound(fnet_routes::Unbound) => Ok(Self::Unbound),
444            fnet_routes::InterfaceMatcher::__SourceBreaking { unknown_ordinal } => {
445                Err(RuleFidlConversionError::UnknownOrdinal {
446                    name: "InterfaceMatcher",
447                    unknown_ordinal,
448                })
449            }
450        }
451    }
452}
453
454impl From<InterfaceMatcher> for fnet_routes::InterfaceMatcher {
455    fn from(matcher: InterfaceMatcher) -> Self {
456        match matcher {
457            InterfaceMatcher::DeviceName(name) => fnet_routes::InterfaceMatcher::DeviceName(name),
458            InterfaceMatcher::Unbound => {
459                fnet_routes::InterfaceMatcher::Unbound(fnet_routes::Unbound)
460            }
461        }
462    }
463}
464
465/// The matcher part of the rule that is used to match packets.
466///
467/// The default matcher is the one that matches every packets, i.e., all the
468/// fields are none.
469#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)]
470pub struct RuleMatcher<I: Ip> {
471    /// Matches whether the source address of the packet is from the subnet.
472    pub from: Option<Subnet<I::Addr>>,
473    /// Matches the packet iff the packet was locally generated.
474    pub locally_generated: Option<bool>,
475    /// Matches the packet iff the socket that was bound to the device using
476    /// `SO_BINDTODEVICE`.
477    pub bound_device: Option<InterfaceMatcher>,
478    /// The matcher for the MARK_1 domain.
479    pub mark_1: Option<MarkMatcher>,
480    /// The matcher for the MARK_2 domain.
481    pub mark_2: Option<MarkMatcher>,
482}
483
484impl TryFrom<fnet_routes::RuleMatcherV4> for RuleMatcher<Ipv4> {
485    type Error = RuleFidlConversionError;
486    fn try_from(
487        fnet_routes::RuleMatcherV4 {
488            from,
489            base,
490            __source_breaking: fidl::marker::SourceBreaking,
491        }: fnet_routes::RuleMatcherV4,
492    ) -> Result<Self, Self::Error> {
493        let fnet_routes::BaseMatcher {
494            locally_generated,
495            bound_device,
496            mark_1,
497            mark_2,
498            __source_breaking: fidl::marker::SourceBreaking,
499        } = base.unwrap_or_default();
500        Ok(Self {
501            from: from
502                .map(|from| from.try_into_ext().map_err(RuleFidlConversionError::DestinationSubnet))
503                .transpose()?,
504            locally_generated,
505            bound_device: bound_device.map(InterfaceMatcher::try_from).transpose()?,
506            mark_1: mark_1.map(MarkMatcher::try_from).transpose()?,
507            mark_2: mark_2.map(MarkMatcher::try_from).transpose()?,
508        })
509    }
510}
511
512impl From<RuleMatcher<Ipv4>> for fnet_routes::RuleMatcherV4 {
513    fn from(
514        RuleMatcher { from, locally_generated, bound_device, mark_1, mark_2 }: RuleMatcher<Ipv4>,
515    ) -> Self {
516        let base = fnet_routes::BaseMatcher {
517            locally_generated,
518            bound_device: bound_device.map(fnet_routes::InterfaceMatcher::from),
519            mark_1: mark_1.map(Into::into),
520            mark_2: mark_2.map(Into::into),
521            __source_breaking: fidl::marker::SourceBreaking,
522        };
523        let base = (base != Default::default()).then_some(base);
524        fnet_routes::RuleMatcherV4 {
525            from: from.map(|from| fnet::Ipv4AddressWithPrefix {
526                addr: from.network().into_ext(),
527                prefix_len: from.prefix(),
528            }),
529            base,
530            __source_breaking: fidl::marker::SourceBreaking,
531        }
532    }
533}
534
535impl TryFrom<fnet_routes::RuleMatcherV6> for RuleMatcher<Ipv6> {
536    type Error = RuleFidlConversionError;
537    fn try_from(
538        fnet_routes::RuleMatcherV6 {
539            from,
540            base,
541            __source_breaking: fidl::marker::SourceBreaking,
542        }: fnet_routes::RuleMatcherV6,
543    ) -> Result<Self, Self::Error> {
544        let fnet_routes::BaseMatcher {
545            locally_generated,
546            bound_device,
547            mark_1,
548            mark_2,
549            __source_breaking: fidl::marker::SourceBreaking,
550        } = base.unwrap_or_default();
551        Ok(Self {
552            from: from
553                .map(|from| from.try_into_ext().map_err(RuleFidlConversionError::DestinationSubnet))
554                .transpose()?,
555            locally_generated,
556            bound_device: bound_device.map(InterfaceMatcher::try_from).transpose()?,
557            mark_1: mark_1.map(MarkMatcher::try_from).transpose()?,
558            mark_2: mark_2.map(MarkMatcher::try_from).transpose()?,
559        })
560    }
561}
562
563impl From<RuleMatcher<Ipv6>> for fnet_routes::RuleMatcherV6 {
564    fn from(
565        RuleMatcher { from, locally_generated, bound_device, mark_1, mark_2 }: RuleMatcher<Ipv6>,
566    ) -> Self {
567        let base = fnet_routes::BaseMatcher {
568            locally_generated,
569            bound_device: bound_device.map(fnet_routes::InterfaceMatcher::from),
570            mark_1: mark_1.map(Into::into),
571            mark_2: mark_2.map(Into::into),
572            __source_breaking: fidl::marker::SourceBreaking,
573        };
574        let base = (base != Default::default()).then_some(base);
575        fnet_routes::RuleMatcherV6 {
576            from: from.map(|from| fnet::Ipv6AddressWithPrefix {
577                addr: from.network().into_ext(),
578                prefix_len: from.prefix(),
579            }),
580            base,
581            __source_breaking: fidl::marker::SourceBreaking,
582        }
583    }
584}
585
586#[derive(Debug, Clone, Hash, PartialEq, Eq)]
587/// A matcher to be used against the mark value.
588pub enum MarkMatcher {
589    /// This mark domain does not have a mark.
590    Unmarked,
591    /// This mark domain has a mark.
592    Marked {
593        /// Mask to apply before comparing to the range in `between`.
594        mask: u32,
595        /// The mark is between the given range.
596        between: RangeInclusive<u32>,
597    },
598}
599
600impl TryFrom<fnet_routes::MarkMatcher> for MarkMatcher {
601    type Error = RuleFidlConversionError;
602
603    fn try_from(sel: fnet_routes::MarkMatcher) -> Result<Self, Self::Error> {
604        match sel {
605            fnet_routes::MarkMatcher::Unmarked(fnet_routes::Unmarked) => Ok(MarkMatcher::Unmarked),
606            fnet_routes::MarkMatcher::Marked(fnet_routes::Marked {
607                mask,
608                between: fnet_routes::Between { start, end },
609            }) => Ok(MarkMatcher::Marked { mask, between: RangeInclusive::new(start, end) }),
610            fnet_routes::MarkMatcher::__SourceBreaking { unknown_ordinal } => {
611                Err(RuleFidlConversionError::UnknownOrdinal {
612                    name: "MarkMatcher",
613                    unknown_ordinal,
614                })
615            }
616        }
617    }
618}
619
620impl From<MarkMatcher> for fnet_routes::MarkMatcher {
621    fn from(sel: MarkMatcher) -> Self {
622        match sel {
623            MarkMatcher::Unmarked => fnet_routes::MarkMatcher::Unmarked(fnet_routes::Unmarked),
624            MarkMatcher::Marked { mask, between } => {
625                let (start, end) = between.into_inner();
626                fnet_routes::MarkMatcher::Marked(fnet_routes::Marked {
627                    mask,
628                    between: fnet_routes::Between { start, end },
629                })
630            }
631        }
632    }
633}
634
635#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
636/// Actions of a rule if the matcher matches.
637pub enum RuleAction {
638    /// Return network is unreachable.
639    Unreachable,
640    /// Look for a route in the indicated route table. If there is no matching
641    /// route in the target table, the lookup will continue to consider the
642    /// next rule.
643    Lookup(crate::TableId),
644}
645
646impl From<fnet_routes::RuleAction> for RuleAction {
647    fn from(action: fnet_routes::RuleAction) -> Self {
648        match action {
649            fnet_routes::RuleAction::Lookup(table_id) => {
650                RuleAction::Lookup(crate::TableId::new(table_id))
651            }
652            fnet_routes::RuleAction::Unreachable(fnet_routes::Unreachable) => {
653                RuleAction::Unreachable
654            }
655            fnet_routes::RuleAction::__SourceBreaking { unknown_ordinal } => {
656                panic!("unexpected mark matcher variant, unknown ordinal: {unknown_ordinal}")
657            }
658        }
659    }
660}
661
662impl From<RuleAction> for fnet_routes::RuleAction {
663    fn from(action: RuleAction) -> Self {
664        match action {
665            RuleAction::Unreachable => {
666                fnet_routes::RuleAction::Unreachable(fnet_routes::Unreachable)
667            }
668            RuleAction::Lookup(table_id) => fnet_routes::RuleAction::Lookup(table_id.get()),
669        }
670    }
671}
672
673/// GenericOverIp version of RouteTableV{4, 6}Request.
674#[derive(GenericOverIp, Debug)]
675#[generic_over_ip(I, Ip)]
676pub enum RuleTableRequest<I: FidlRuleAdminIpExt> {
677    /// Creates a new rule set for the global rule table.
678    NewRuleSet {
679        /// The priority of the the rule set.
680        priority: RuleSetPriority,
681        /// The server end of the rule set protocol.
682        rule_set: fidl::endpoints::ServerEnd<I::RuleSetMarker>,
683        /// Control handle to the protocol.
684        control_handle: I::RuleTableControlHandle,
685    },
686}
687
688impl From<fnet_routes_admin::RuleTableV4Request> for RuleTableRequest<Ipv4> {
689    fn from(value: fnet_routes_admin::RuleTableV4Request) -> Self {
690        match value {
691            fnet_routes_admin::RuleTableV4Request::NewRuleSet {
692                priority,
693                rule_set,
694                control_handle,
695            } => Self::NewRuleSet { priority: RuleSetPriority(priority), rule_set, control_handle },
696        }
697    }
698}
699
700impl From<fnet_routes_admin::RuleTableV6Request> for RuleTableRequest<Ipv6> {
701    fn from(value: fnet_routes_admin::RuleTableV6Request) -> Self {
702        match value {
703            fnet_routes_admin::RuleTableV6Request::NewRuleSet {
704                priority,
705                rule_set,
706                control_handle,
707            } => Self::NewRuleSet { priority: RuleSetPriority(priority), rule_set, control_handle },
708        }
709    }
710}
711
712/// GenericOverIp version of RuleSetV{4, 6}Request.
713#[derive(GenericOverIp, Debug)]
714#[generic_over_ip(I, Ip)]
715pub enum RuleSetRequest<I: FidlRuleAdminIpExt> {
716    /// Adds a rule to the rule set.
717    AddRule {
718        /// The index of the rule to be added.
719        index: RuleIndex,
720        /// The matcher of the rule.
721        matcher: Result<RuleMatcher<I>, RuleFidlConversionError>,
722        /// The action of the rule.
723        action: RuleAction,
724        /// The responder for this request.
725        responder: I::RuleSetAddRuleResponder,
726    },
727    /// Removes a rule from the rule set.
728    RemoveRule {
729        /// The index of the rule to be removed.
730        index: RuleIndex,
731        /// The responder for this request.
732        responder: I::RuleSetRemoveRuleResponder,
733    },
734    /// Authenticates the rule set for managing routes on a route table.
735    AuthenticateForRouteTable {
736        /// The table id of the table being authenticated for.
737        table: u32,
738        /// The credential proving authorization for this route table.
739        token: fidl::Event,
740        /// The responder for this request.
741        responder: I::RuleSetAuthenticateForRouteTableResponder,
742    },
743    /// Closes the rule set
744    Close {
745        /// The control handle to rule set protocol.
746        control_handle: I::RuleSetControlHandle,
747    },
748}
749
750impl From<fnet_routes_admin::RuleSetV4Request> for RuleSetRequest<Ipv4> {
751    fn from(value: fnet_routes_admin::RuleSetV4Request) -> Self {
752        match value {
753            fnet_routes_admin::RuleSetV4Request::AddRule { index, matcher, action, responder } => {
754                RuleSetRequest::AddRule {
755                    index: RuleIndex(index),
756                    matcher: matcher.try_into(),
757                    action: action.into(),
758                    responder,
759                }
760            }
761            fnet_routes_admin::RuleSetV4Request::RemoveRule { index, responder } => {
762                RuleSetRequest::RemoveRule { index: RuleIndex(index), responder }
763            }
764            fnet_routes_admin::RuleSetV4Request::AuthenticateForRouteTable {
765                table,
766                token,
767                responder,
768            } => RuleSetRequest::AuthenticateForRouteTable { table, token, responder },
769            fnet_routes_admin::RuleSetV4Request::Close { control_handle } => {
770                RuleSetRequest::Close { control_handle }
771            }
772        }
773    }
774}
775impl From<fnet_routes_admin::RuleSetV6Request> for RuleSetRequest<Ipv6> {
776    fn from(value: fnet_routes_admin::RuleSetV6Request) -> Self {
777        match value {
778            fnet_routes_admin::RuleSetV6Request::AddRule { index, matcher, action, responder } => {
779                RuleSetRequest::AddRule {
780                    index: RuleIndex(index),
781                    matcher: matcher.try_into(),
782                    action: action.into(),
783                    responder,
784                }
785            }
786            fnet_routes_admin::RuleSetV6Request::RemoveRule { index, responder } => {
787                RuleSetRequest::RemoveRule { index: RuleIndex(index), responder }
788            }
789            fnet_routes_admin::RuleSetV6Request::AuthenticateForRouteTable {
790                table,
791                token,
792                responder,
793            } => RuleSetRequest::AuthenticateForRouteTable { table, token, responder },
794            fnet_routes_admin::RuleSetV6Request::Close { control_handle } => {
795                RuleSetRequest::Close { control_handle }
796            }
797        }
798    }
799}
800
801/// Rule set creation errors.
802#[derive(Clone, Debug, Error)]
803pub enum RuleSetCreationError {
804    /// Proxy creation failed.
805    #[error("failed to create proxy: {0}")]
806    CreateProxy(fidl::Error),
807    /// Rule set creation failed.
808    #[error("failed to create route set: {0}")]
809    RuleSet(fidl::Error),
810}
811
812/// Creates a new rule set for the rule table.
813pub fn new_rule_set<I: Ip + FidlRuleAdminIpExt>(
814    rule_table_proxy: &<I::RuleTableMarker as ProtocolMarker>::Proxy,
815    priority: RuleSetPriority,
816) -> Result<<I::RuleSetMarker as ProtocolMarker>::Proxy, RuleSetCreationError> {
817    let (rule_set_proxy, rule_set_server_end) = fidl::endpoints::create_proxy::<I::RuleSetMarker>();
818
819    #[derive(GenericOverIp)]
820    #[generic_over_ip(I, Ip)]
821    struct NewRuleSetInput<'a, I: FidlRuleAdminIpExt> {
822        rule_set_server_end: fidl::endpoints::ServerEnd<I::RuleSetMarker>,
823        rule_table_proxy: &'a <I::RuleTableMarker as ProtocolMarker>::Proxy,
824    }
825    let result = I::map_ip_in(
826        NewRuleSetInput::<'_, I> { rule_set_server_end, rule_table_proxy },
827        |NewRuleSetInput { rule_set_server_end, rule_table_proxy }| {
828            rule_table_proxy.new_rule_set(priority.into(), rule_set_server_end)
829        },
830        |NewRuleSetInput { rule_set_server_end, rule_table_proxy }| {
831            rule_table_proxy.new_rule_set(priority.into(), rule_set_server_end)
832        },
833    );
834
835    result.map_err(RuleSetCreationError::RuleSet)?;
836    Ok(rule_set_proxy)
837}
838
839/// Dispatches `authenticate_for_route_table` on either the `RuleSetV4` or
840/// `RuleSetV6` proxy.
841pub async fn authenticate_for_route_table<I: Ip + FidlRuleAdminIpExt>(
842    rule_set: &<I::RuleSetMarker as ProtocolMarker>::Proxy,
843    table_id: u32,
844    token: fidl::Event,
845) -> Result<Result<(), fnet_routes_admin::AuthenticateForRouteTableError>, fidl::Error> {
846    #[derive(GenericOverIp)]
847    #[generic_over_ip(I, Ip)]
848    struct AuthenticateForRouteTableInput<'a, I: FidlRuleAdminIpExt> {
849        rule_set: &'a <I::RuleSetMarker as ProtocolMarker>::Proxy,
850        table_id: u32,
851        token: fidl::Event,
852    }
853
854    I::map_ip_in(
855        AuthenticateForRouteTableInput { rule_set, table_id, token },
856        |AuthenticateForRouteTableInput { rule_set, table_id, token }| {
857            Either::Left(rule_set.authenticate_for_route_table(table_id, token))
858        },
859        |AuthenticateForRouteTableInput { rule_set, table_id, token }| {
860            Either::Right(rule_set.authenticate_for_route_table(table_id, token))
861        },
862    )
863    .await
864}
865
866/// Dispatches `add_rule` on either the `RuleSetV4` or `RuleSetV6` proxy.
867pub async fn add_rule<I: Ip + FidlRuleAdminIpExt>(
868    rule_set: &<I::RuleSetMarker as ProtocolMarker>::Proxy,
869    index: RuleIndex,
870    matcher: RuleMatcher<I>,
871    action: RuleAction,
872) -> Result<Result<(), fnet_routes_admin::RuleSetError>, fidl::Error> {
873    #[derive(GenericOverIp)]
874    #[generic_over_ip(I, Ip)]
875    struct AddRuleInput<'a, I: FidlRuleAdminIpExt> {
876        rule_set: &'a <I::RuleSetMarker as ProtocolMarker>::Proxy,
877        index: RuleIndex,
878        matcher: RuleMatcher<I>,
879        action: RuleAction,
880    }
881
882    I::map_ip_in(
883        AddRuleInput { rule_set, index, matcher, action },
884        |AddRuleInput { rule_set, index, matcher, action }| {
885            Either::Left(rule_set.add_rule(index.into(), &matcher.into(), &action.into()))
886        },
887        |AddRuleInput { rule_set, index, matcher, action }| {
888            Either::Right(rule_set.add_rule(index.into(), &matcher.into(), &action.into()))
889        },
890    )
891    .await
892}
893
894/// Dispatches `remove_rule` on either the `RuleSetV4` or `RuleSetV6` proxy.
895pub async fn remove_rule<I: Ip + FidlRuleAdminIpExt>(
896    rule_set: &<I::RuleSetMarker as ProtocolMarker>::Proxy,
897    index: RuleIndex,
898) -> Result<Result<(), fnet_routes_admin::RuleSetError>, fidl::Error> {
899    #[derive(GenericOverIp)]
900    #[generic_over_ip(I, Ip)]
901    struct RemoveRuleInput<'a, I: FidlRuleAdminIpExt> {
902        rule_set: &'a <I::RuleSetMarker as ProtocolMarker>::Proxy,
903        index: RuleIndex,
904    }
905
906    I::map_ip_in(
907        RemoveRuleInput { rule_set, index },
908        |RemoveRuleInput { rule_set, index }| Either::Left(rule_set.remove_rule(index.into())),
909        |RemoveRuleInput { rule_set, index }| Either::Right(rule_set.remove_rule(index.into())),
910    )
911    .await
912}
913
914/// Dispatches `close` on either the `RuleSetV4` or `RuleSetV6` proxy.
915///
916/// Waits until the channel is closed before returning.
917pub async fn close_rule_set<I: Ip + FidlRuleAdminIpExt>(
918    rule_set: <I::RuleSetMarker as ProtocolMarker>::Proxy,
919) -> Result<(), fidl::Error> {
920    #[derive(GenericOverIp)]
921    #[generic_over_ip(I, Ip)]
922    struct CloseInput<'a, I: FidlRuleAdminIpExt> {
923        rule_set: &'a <I::RuleSetMarker as ProtocolMarker>::Proxy,
924    }
925
926    let result = I::map_ip_in(
927        CloseInput { rule_set: &rule_set },
928        |CloseInput { rule_set }| rule_set.close(),
929        |CloseInput { rule_set }| rule_set.close(),
930    );
931
932    assert!(
933        rule_set
934            .on_closed()
935            .await
936            .expect("failed to wait for signals")
937            .contains(fidl::Signals::CHANNEL_PEER_CLOSED)
938    );
939
940    result
941}
942
943/// Dispatches either `GetRuleWatcherV4` or `GetRuleWatcherV6` on the state proxy.
944pub fn get_rule_watcher<I: FidlRuleIpExt + FidlRouteIpExt>(
945    state_proxy: &<I::StateMarker as fidl::endpoints::ProtocolMarker>::Proxy,
946) -> Result<<I::RuleWatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy, WatcherCreationError>
947{
948    let (watcher_proxy, watcher_server_end) =
949        fidl::endpoints::create_proxy::<I::RuleWatcherMarker>();
950
951    #[derive(GenericOverIp)]
952    #[generic_over_ip(I, Ip)]
953    struct GetWatcherInputs<'a, I: FidlRuleIpExt + FidlRouteIpExt> {
954        watcher_server_end: fidl::endpoints::ServerEnd<I::RuleWatcherMarker>,
955        state_proxy: &'a <I::StateMarker as fidl::endpoints::ProtocolMarker>::Proxy,
956    }
957    let result = I::map_ip_in(
958        GetWatcherInputs::<'_, I> { watcher_server_end, state_proxy },
959        |GetWatcherInputs { watcher_server_end, state_proxy }| {
960            state_proxy.get_rule_watcher_v4(
961                watcher_server_end,
962                &fnet_routes::RuleWatcherOptionsV4::default(),
963            )
964        },
965        |GetWatcherInputs { watcher_server_end, state_proxy }| {
966            state_proxy.get_rule_watcher_v6(
967                watcher_server_end,
968                &fnet_routes::RuleWatcherOptionsV6::default(),
969            )
970        },
971    );
972
973    result.map_err(WatcherCreationError::GetWatcher)?;
974    Ok(watcher_proxy)
975}
976
977/// Calls `Watch()` on the provided `RuleWatcherV4` or `RuleWatcherV6` proxy.
978pub async fn watch<'a, I: FidlRuleIpExt>(
979    watcher_proxy: &'a <I::RuleWatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy,
980) -> Result<Vec<I::RuleEvent>, fidl::Error> {
981    #[derive(GenericOverIp)]
982    #[generic_over_ip(I, Ip)]
983    struct WatchInputs<'a, I: FidlRuleIpExt> {
984        watcher_proxy: &'a <I::RuleWatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy,
985    }
986    #[derive(GenericOverIp)]
987    #[generic_over_ip(I, Ip)]
988    struct WatchOutputs<I: FidlRuleIpExt> {
989        watch_fut: fidl::client::QueryResponseFut<Vec<I::RuleEvent>>,
990    }
991    let WatchOutputs { watch_fut } = net_types::map_ip_twice!(
992        I,
993        WatchInputs { watcher_proxy },
994        |WatchInputs { watcher_proxy }| { WatchOutputs { watch_fut: watcher_proxy.watch() } }
995    );
996    watch_fut.await
997}
998
999/// Route watcher `Watch` errors.
1000#[derive(Clone, Debug, Error)]
1001pub enum RuleWatchError {
1002    /// The call to `Watch` returned a FIDL error.
1003    #[error("the call to `Watch()` failed: {0}")]
1004    Fidl(fidl::Error),
1005    /// The event returned by `Watch` encountered a conversion error.
1006    #[error("failed to convert event returned by `Watch()`: {0}")]
1007    Conversion(RuleFidlConversionError),
1008    /// The server returned an empty batch of events.
1009    #[error("the call to `Watch()` returned an empty batch of events")]
1010    EmptyEventBatch,
1011}
1012
1013/// Creates a rules event stream from the state proxy.
1014pub fn rule_event_stream_from_state<I: FidlRuleIpExt + FidlRouteIpExt>(
1015    state: &<I::StateMarker as fidl::endpoints::ProtocolMarker>::Proxy,
1016) -> Result<impl Stream<Item = Result<RuleEvent<I>, RuleWatchError>>, WatcherCreationError> {
1017    let watcher = get_rule_watcher::<I>(state)?;
1018    rule_event_stream_from_watcher(watcher)
1019}
1020
1021/// Turns the provided watcher client into a [`RuleEvent`] stream by applying
1022/// Hanging-Get watch.
1023///
1024/// Each call to `Watch` returns a batch of events, which are flattened into a
1025/// single stream. If an error is encountered while calling `Watch` or while
1026/// converting the event, the stream is immediately terminated.
1027pub fn rule_event_stream_from_watcher<I: FidlRuleIpExt>(
1028    watcher: <I::RuleWatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy,
1029) -> Result<impl Stream<Item = Result<RuleEvent<I>, RuleWatchError>>, WatcherCreationError> {
1030    Ok(stream::ShortCircuit::new(
1031        futures::stream::try_unfold(watcher, |watcher| async {
1032            let events_batch = watch::<I>(&watcher).await.map_err(RuleWatchError::Fidl)?;
1033            if events_batch.is_empty() {
1034                return Err(RuleWatchError::EmptyEventBatch);
1035            }
1036            let events_batch = events_batch
1037                .into_iter()
1038                .map(|event| event.try_into().map_err(RuleWatchError::Conversion));
1039            let event_stream = futures::stream::iter(events_batch);
1040            Ok(Some((event_stream, watcher)))
1041        })
1042        // Flatten the stream of event streams into a single event stream.
1043        .try_flatten(),
1044    ))
1045}
1046
1047/// Errors returned by [`collect_rules_until_idle`].
1048#[derive(Clone, Debug, Error)]
1049pub enum CollectRulesUntilIdleError<I: FidlRuleIpExt> {
1050    /// There was an error in the event stream.
1051    #[error("there was an error in the event stream: {0}")]
1052    ErrorInStream(RuleWatchError),
1053    /// There was an unexpected event in the event stream. Only `existing` or
1054    /// `idle` events are expected.
1055    #[error("there was an unexpected event in the event stream: {0:?}")]
1056    UnexpectedEvent(RuleEvent<I>),
1057    /// The event stream unexpectedly ended.
1058    #[error("the event stream unexpectedly ended")]
1059    StreamEnded,
1060}
1061
1062/// Collects all `existing` events from the stream, stopping once the `idle`
1063/// event is observed.
1064pub async fn collect_rules_until_idle<I: FidlRuleIpExt, C: Extend<InstalledRule<I>> + Default>(
1065    event_stream: impl futures::Stream<Item = Result<RuleEvent<I>, RuleWatchError>> + Unpin,
1066) -> Result<C, CollectRulesUntilIdleError<I>> {
1067    fold::fold_while(
1068        event_stream,
1069        Ok(C::default()),
1070        |existing_rules: Result<C, CollectRulesUntilIdleError<I>>, event| {
1071            futures::future::ready(match existing_rules {
1072                Err(_) => {
1073                    unreachable!("`existing_rules` must be `Ok`, because we stop folding on err")
1074                }
1075                Ok(mut existing_rules) => match event {
1076                    Err(e) => {
1077                        fold::FoldWhile::Done(Err(CollectRulesUntilIdleError::ErrorInStream(e)))
1078                    }
1079                    Ok(e) => match e {
1080                        RuleEvent::Existing(e) => {
1081                            existing_rules.extend([e]);
1082                            fold::FoldWhile::Continue(Ok(existing_rules))
1083                        }
1084                        RuleEvent::Idle => fold::FoldWhile::Done(Ok(existing_rules)),
1085                        e @ RuleEvent::Added(_) | e @ RuleEvent::Removed(_) => {
1086                            fold::FoldWhile::Done(Err(CollectRulesUntilIdleError::UnexpectedEvent(
1087                                e,
1088                            )))
1089                        }
1090                    },
1091                },
1092            })
1093        },
1094    )
1095    .await
1096    .short_circuited()
1097    .map_err(|_accumulated_thus_far: Result<C, CollectRulesUntilIdleError<I>>| {
1098        CollectRulesUntilIdleError::StreamEnded
1099    })?
1100}
1101
1102#[cfg(test)]
1103mod tests {
1104    use assert_matches::assert_matches;
1105
1106    use super::*;
1107
1108    #[test]
1109    fn missing_base_matcher_default_v4() {
1110        let fidl_matcher = fidl_fuchsia_net_routes::RuleMatcherV4 {
1111            from: None,
1112            base: None,
1113            __source_breaking: fidl::marker::SourceBreaking,
1114        };
1115        assert_eq!(RuleMatcher::try_from(fidl_matcher), Ok(Default::default()));
1116    }
1117
1118    #[test]
1119    fn missing_base_matcher_default_v6() {
1120        let fidl_matcher = fidl_fuchsia_net_routes::RuleMatcherV6 {
1121            from: None,
1122            base: None,
1123            __source_breaking: fidl::marker::SourceBreaking,
1124        };
1125        assert_eq!(RuleMatcher::try_from(fidl_matcher), Ok(Default::default()));
1126    }
1127
1128    #[test]
1129    fn invalid_destination_subnet_v4() {
1130        let fidl_matcher = fidl_fuchsia_net_routes::RuleMatcherV4 {
1131            // Invalid, because subnets should not have the "host bits" set.
1132            from: Some(net_declare::fidl_ip_v4_with_prefix!("192.168.0.1/24")),
1133            base: None,
1134            __source_breaking: fidl::marker::SourceBreaking,
1135        };
1136        assert_matches!(
1137            RuleMatcher::try_from(fidl_matcher),
1138            Err(RuleFidlConversionError::DestinationSubnet(_))
1139        );
1140    }
1141
1142    #[test]
1143    fn invalid_destination_subnet_v6() {
1144        let fidl_matcher = fidl_fuchsia_net_routes::RuleMatcherV6 {
1145            // Invalid, because subnets should not have the "host bits" set.
1146            from: Some(net_declare::fidl_ip_v6_with_prefix!("fe80::1/64")),
1147            base: None,
1148            __source_breaking: fidl::marker::SourceBreaking,
1149        };
1150        assert_matches!(
1151            RuleMatcher::try_from(fidl_matcher),
1152            Err(RuleFidlConversionError::DestinationSubnet(_))
1153        );
1154    }
1155
1156    #[test]
1157    fn all_unspecified_matcher_v4() {
1158        let fidl_matcher = fidl_fuchsia_net_routes::RuleMatcherV4 {
1159            from: None,
1160            base: None,
1161            __source_breaking: fidl::marker::SourceBreaking,
1162        };
1163        let ext_matcher = RuleMatcher {
1164            from: None,
1165            locally_generated: None,
1166            bound_device: None,
1167            mark_1: None,
1168            mark_2: None,
1169        };
1170        assert_eq!(RuleMatcher::try_from(fidl_matcher.clone()), Ok(ext_matcher.clone()));
1171        assert_eq!(fidl_fuchsia_net_routes::RuleMatcherV4::from(ext_matcher), fidl_matcher,)
1172    }
1173
1174    #[test]
1175    fn all_unspecified_matcher_v6() {
1176        let fidl_matcher = fidl_fuchsia_net_routes::RuleMatcherV6 {
1177            from: None,
1178            base: None,
1179            __source_breaking: fidl::marker::SourceBreaking,
1180        };
1181        let ext_matcher = RuleMatcher {
1182            from: None,
1183            locally_generated: None,
1184            bound_device: None,
1185            mark_1: None,
1186            mark_2: None,
1187        };
1188        assert_eq!(RuleMatcher::try_from(fidl_matcher.clone()), Ok(ext_matcher.clone()));
1189        assert_eq!(fidl_fuchsia_net_routes::RuleMatcherV6::from(ext_matcher), fidl_matcher,)
1190    }
1191}