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::{impl_responder, FidlRouteIpExt, Responder, SliceResponder, WatcherCreationError};
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    /// A required field was unset. The provided string is the human-readable
370    /// name of the unset field.
371    #[error("BaseMatcher is missing from the RuleMatcher")]
372    BaseMatcherMissing,
373    /// Destination Subnet conversion failed.
374    #[error("failed to convert `destination` to net_types subnet: {0:?}")]
375    DestinationSubnet(net_types::ip::SubnetError),
376    /// Unknown union variant.
377    #[error("unexpected union variant for {name}, got ordinal = ({unknown_ordinal})")]
378    #[allow(missing_docs)]
379    UnknownOrdinal { name: &'static str, unknown_ordinal: u64 },
380}
381
382/// The priority of the rule set, all rule sets are linearized based on this.
383#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
384pub struct RuleSetPriority(u32);
385
386impl RuleSetPriority {
387    /// Create a new [`RuleSetPriority`].
388    pub const fn new(x: u32) -> Self {
389        Self(x)
390    }
391}
392
393/// The priority for the default rule set, where the default rule that points
394/// to the main table lives.
395pub const DEFAULT_RULE_SET_PRIORITY: RuleSetPriority =
396    RuleSetPriority(fnet_routes::DEFAULT_RULE_SET_PRIORITY);
397
398/// The index of a rule within a provided rule set.
399#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
400pub struct RuleIndex(u32);
401
402impl RuleIndex {
403    /// Create a new rule index from a scalar.
404    pub const fn new(x: u32) -> Self {
405        Self(x)
406    }
407}
408
409impl From<RuleSetPriority> for u32 {
410    fn from(RuleSetPriority(x): RuleSetPriority) -> Self {
411        x
412    }
413}
414
415impl From<u32> for RuleSetPriority {
416    fn from(x: u32) -> Self {
417        Self(x)
418    }
419}
420
421impl From<RuleIndex> for u32 {
422    fn from(RuleIndex(x): RuleIndex) -> Self {
423        x
424    }
425}
426
427impl From<u32> for RuleIndex {
428    fn from(x: u32) -> Self {
429        Self(x)
430    }
431}
432
433/// How the interface of a packet should be matched against a rule.
434#[derive(Debug, Clone, PartialEq, Eq, Hash)]
435pub enum InterfaceMatcher {
436    /// Match on the name of the device.
437    DeviceName(String),
438}
439
440impl TryFrom<fnet_routes::InterfaceMatcher> for InterfaceMatcher {
441    type Error = RuleFidlConversionError;
442    fn try_from(matcher: fnet_routes::InterfaceMatcher) -> Result<Self, Self::Error> {
443        match matcher {
444            fnet_routes::InterfaceMatcher::DeviceName(name) => Ok(Self::DeviceName(name)),
445            fnet_routes::InterfaceMatcher::__SourceBreaking { unknown_ordinal } => {
446                Err(RuleFidlConversionError::UnknownOrdinal {
447                    name: "InterfaceMatcher",
448                    unknown_ordinal,
449                })
450            }
451        }
452    }
453}
454
455impl From<InterfaceMatcher> for fnet_routes::InterfaceMatcher {
456    fn from(matcher: InterfaceMatcher) -> Self {
457        match matcher {
458            InterfaceMatcher::DeviceName(name) => fnet_routes::InterfaceMatcher::DeviceName(name),
459        }
460    }
461}
462
463/// The matcher part of the rule that is used to match packets.
464///
465/// The default matcher is the one that matches every packets, i.e., all the
466/// fields are none.
467#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)]
468pub struct RuleMatcher<I: Ip> {
469    /// Matches whether the source address of the packet is from the subnet.
470    pub from: Option<Subnet<I::Addr>>,
471    /// Matches the packet iff the packet was locally generated.
472    pub locally_generated: Option<bool>,
473    /// Matches the packet iff the socket that was bound to the device using
474    /// `SO_BINDTODEVICE`.
475    pub bound_device: Option<InterfaceMatcher>,
476    /// The matcher for the MARK_1 domain.
477    pub mark_1: Option<MarkMatcher>,
478    /// The matcher for the MARK_2 domain.
479    pub mark_2: Option<MarkMatcher>,
480}
481
482impl TryFrom<fnet_routes::RuleMatcherV4> for RuleMatcher<Ipv4> {
483    type Error = RuleFidlConversionError;
484    fn try_from(
485        fnet_routes::RuleMatcherV4 {
486            from,
487            base,
488            __source_breaking: fidl::marker::SourceBreaking,
489        }: fnet_routes::RuleMatcherV4,
490    ) -> Result<Self, Self::Error> {
491        let fnet_routes::BaseMatcher {
492            locally_generated,
493            bound_device,
494            mark_1,
495            mark_2,
496            __source_breaking: fidl::marker::SourceBreaking,
497        } = base.ok_or(RuleFidlConversionError::BaseMatcherMissing)?;
498        Ok(Self {
499            from: from
500                .map(|from| from.try_into_ext().map_err(RuleFidlConversionError::DestinationSubnet))
501                .transpose()?,
502            locally_generated,
503            bound_device: bound_device.map(InterfaceMatcher::try_from).transpose()?,
504            mark_1: mark_1.map(MarkMatcher::try_from).transpose()?,
505            mark_2: mark_2.map(MarkMatcher::try_from).transpose()?,
506        })
507    }
508}
509
510impl From<RuleMatcher<Ipv4>> for fnet_routes::RuleMatcherV4 {
511    fn from(
512        RuleMatcher { from, locally_generated, bound_device, mark_1, mark_2 }: RuleMatcher<Ipv4>,
513    ) -> Self {
514        fnet_routes::RuleMatcherV4 {
515            from: from.map(|from| fnet::Ipv4AddressWithPrefix {
516                addr: from.network().into_ext(),
517                prefix_len: from.prefix(),
518            }),
519            base: Some(fnet_routes::BaseMatcher {
520                locally_generated,
521                bound_device: bound_device.map(fnet_routes::InterfaceMatcher::from),
522                mark_1: mark_1.map(Into::into),
523                mark_2: mark_2.map(Into::into),
524                __source_breaking: fidl::marker::SourceBreaking,
525            }),
526            __source_breaking: fidl::marker::SourceBreaking,
527        }
528    }
529}
530
531impl TryFrom<fnet_routes::RuleMatcherV6> for RuleMatcher<Ipv6> {
532    type Error = RuleFidlConversionError;
533    fn try_from(
534        fnet_routes::RuleMatcherV6 {
535            from,
536            base,
537            __source_breaking: fidl::marker::SourceBreaking,
538        }: fnet_routes::RuleMatcherV6,
539    ) -> Result<Self, Self::Error> {
540        let fnet_routes::BaseMatcher {
541            locally_generated,
542            bound_device,
543            mark_1,
544            mark_2,
545            __source_breaking: fidl::marker::SourceBreaking,
546        } = base.ok_or(RuleFidlConversionError::BaseMatcherMissing)?;
547        Ok(Self {
548            from: from
549                .map(|from| from.try_into_ext().map_err(RuleFidlConversionError::DestinationSubnet))
550                .transpose()?,
551            locally_generated,
552            bound_device: bound_device.map(InterfaceMatcher::try_from).transpose()?,
553            mark_1: mark_1.map(MarkMatcher::try_from).transpose()?,
554            mark_2: mark_2.map(MarkMatcher::try_from).transpose()?,
555        })
556    }
557}
558
559impl From<RuleMatcher<Ipv6>> for fnet_routes::RuleMatcherV6 {
560    fn from(
561        RuleMatcher { from, locally_generated, bound_device, mark_1, mark_2 }: RuleMatcher<Ipv6>,
562    ) -> Self {
563        fnet_routes::RuleMatcherV6 {
564            from: from.map(|from| fnet::Ipv6AddressWithPrefix {
565                addr: from.network().into_ext(),
566                prefix_len: from.prefix(),
567            }),
568            base: Some(fnet_routes::BaseMatcher {
569                locally_generated,
570                bound_device: bound_device.map(fnet_routes::InterfaceMatcher::from),
571                mark_1: mark_1.map(Into::into),
572                mark_2: mark_2.map(Into::into),
573                __source_breaking: fidl::marker::SourceBreaking,
574            }),
575            __source_breaking: fidl::marker::SourceBreaking,
576        }
577    }
578}
579
580#[derive(Debug, Clone, Hash, PartialEq, Eq)]
581/// A matcher to be used against the mark value.
582pub enum MarkMatcher {
583    /// This mark domain does not have a mark.
584    Unmarked,
585    /// This mark domain has a mark.
586    Marked {
587        /// Mask to apply before comparing to the range in `between`.
588        mask: u32,
589        /// The mark is between the given range.
590        between: RangeInclusive<u32>,
591    },
592}
593
594impl TryFrom<fnet_routes::MarkMatcher> for MarkMatcher {
595    type Error = RuleFidlConversionError;
596
597    fn try_from(sel: fnet_routes::MarkMatcher) -> Result<Self, Self::Error> {
598        match sel {
599            fnet_routes::MarkMatcher::Unmarked(fnet_routes::Unmarked) => Ok(MarkMatcher::Unmarked),
600            fnet_routes::MarkMatcher::Marked(fnet_routes::Marked {
601                mask,
602                between: fnet_routes::Between { start, end },
603            }) => Ok(MarkMatcher::Marked { mask, between: RangeInclusive::new(start, end) }),
604            fnet_routes::MarkMatcher::__SourceBreaking { unknown_ordinal } => {
605                Err(RuleFidlConversionError::UnknownOrdinal {
606                    name: "MarkMatcher",
607                    unknown_ordinal,
608                })
609            }
610        }
611    }
612}
613
614impl From<MarkMatcher> for fnet_routes::MarkMatcher {
615    fn from(sel: MarkMatcher) -> Self {
616        match sel {
617            MarkMatcher::Unmarked => fnet_routes::MarkMatcher::Unmarked(fnet_routes::Unmarked),
618            MarkMatcher::Marked { mask, between } => {
619                let (start, end) = between.into_inner();
620                fnet_routes::MarkMatcher::Marked(fnet_routes::Marked {
621                    mask,
622                    between: fnet_routes::Between { start, end },
623                })
624            }
625        }
626    }
627}
628
629#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
630/// Actions of a rule if the matcher matches.
631pub enum RuleAction {
632    /// Return network is unreachable.
633    Unreachable,
634    /// Look for a route in the indicated route table. If there is no matching
635    /// route in the target table, the lookup will continue to consider the
636    /// next rule.
637    Lookup(crate::TableId),
638}
639
640impl From<fnet_routes::RuleAction> for RuleAction {
641    fn from(action: fnet_routes::RuleAction) -> Self {
642        match action {
643            fnet_routes::RuleAction::Lookup(table_id) => {
644                RuleAction::Lookup(crate::TableId::new(table_id))
645            }
646            fnet_routes::RuleAction::Unreachable(fnet_routes::Unreachable) => {
647                RuleAction::Unreachable
648            }
649            fnet_routes::RuleAction::__SourceBreaking { unknown_ordinal } => {
650                panic!("unexpected mark matcher variant, unknown ordinal: {unknown_ordinal}")
651            }
652        }
653    }
654}
655
656impl From<RuleAction> for fnet_routes::RuleAction {
657    fn from(action: RuleAction) -> Self {
658        match action {
659            RuleAction::Unreachable => {
660                fnet_routes::RuleAction::Unreachable(fnet_routes::Unreachable)
661            }
662            RuleAction::Lookup(table_id) => fnet_routes::RuleAction::Lookup(table_id.get()),
663        }
664    }
665}
666
667/// GenericOverIp version of RouteTableV{4, 6}Request.
668#[derive(GenericOverIp, Debug)]
669#[generic_over_ip(I, Ip)]
670pub enum RuleTableRequest<I: FidlRuleAdminIpExt> {
671    /// Creates a new rule set for the global rule table.
672    NewRuleSet {
673        /// The priority of the the rule set.
674        priority: RuleSetPriority,
675        /// The server end of the rule set protocol.
676        rule_set: fidl::endpoints::ServerEnd<I::RuleSetMarker>,
677        /// Control handle to the protocol.
678        control_handle: I::RuleTableControlHandle,
679    },
680}
681
682impl From<fnet_routes_admin::RuleTableV4Request> for RuleTableRequest<Ipv4> {
683    fn from(value: fnet_routes_admin::RuleTableV4Request) -> Self {
684        match value {
685            fnet_routes_admin::RuleTableV4Request::NewRuleSet {
686                priority,
687                rule_set,
688                control_handle,
689            } => Self::NewRuleSet { priority: RuleSetPriority(priority), rule_set, control_handle },
690        }
691    }
692}
693
694impl From<fnet_routes_admin::RuleTableV6Request> for RuleTableRequest<Ipv6> {
695    fn from(value: fnet_routes_admin::RuleTableV6Request) -> Self {
696        match value {
697            fnet_routes_admin::RuleTableV6Request::NewRuleSet {
698                priority,
699                rule_set,
700                control_handle,
701            } => Self::NewRuleSet { priority: RuleSetPriority(priority), rule_set, control_handle },
702        }
703    }
704}
705
706/// GenericOverIp version of RuleSetV{4, 6}Request.
707#[derive(GenericOverIp, Debug)]
708#[generic_over_ip(I, Ip)]
709pub enum RuleSetRequest<I: FidlRuleAdminIpExt> {
710    /// Adds a rule to the rule set.
711    AddRule {
712        /// The index of the rule to be added.
713        index: RuleIndex,
714        /// The matcher of the rule.
715        matcher: Result<RuleMatcher<I>, RuleFidlConversionError>,
716        /// The action of the rule.
717        action: RuleAction,
718        /// The responder for this request.
719        responder: I::RuleSetAddRuleResponder,
720    },
721    /// Removes a rule from the rule set.
722    RemoveRule {
723        /// The index of the rule to be removed.
724        index: RuleIndex,
725        /// The responder for this request.
726        responder: I::RuleSetRemoveRuleResponder,
727    },
728    /// Authenticates the rule set for managing routes on a route table.
729    AuthenticateForRouteTable {
730        /// The table id of the table being authenticated for.
731        table: u32,
732        /// The credential proving authorization for this route table.
733        token: fidl::Event,
734        /// The responder for this request.
735        responder: I::RuleSetAuthenticateForRouteTableResponder,
736    },
737    /// Closes the rule set
738    Close {
739        /// The control handle to rule set protocol.
740        control_handle: I::RuleSetControlHandle,
741    },
742}
743
744impl From<fnet_routes_admin::RuleSetV4Request> for RuleSetRequest<Ipv4> {
745    fn from(value: fnet_routes_admin::RuleSetV4Request) -> Self {
746        match value {
747            fnet_routes_admin::RuleSetV4Request::AddRule { index, matcher, action, responder } => {
748                RuleSetRequest::AddRule {
749                    index: RuleIndex(index),
750                    matcher: matcher.try_into(),
751                    action: action.into(),
752                    responder,
753                }
754            }
755            fnet_routes_admin::RuleSetV4Request::RemoveRule { index, responder } => {
756                RuleSetRequest::RemoveRule { index: RuleIndex(index), responder }
757            }
758            fnet_routes_admin::RuleSetV4Request::AuthenticateForRouteTable {
759                table,
760                token,
761                responder,
762            } => RuleSetRequest::AuthenticateForRouteTable { table, token, responder },
763            fnet_routes_admin::RuleSetV4Request::Close { control_handle } => {
764                RuleSetRequest::Close { control_handle }
765            }
766        }
767    }
768}
769impl From<fnet_routes_admin::RuleSetV6Request> for RuleSetRequest<Ipv6> {
770    fn from(value: fnet_routes_admin::RuleSetV6Request) -> Self {
771        match value {
772            fnet_routes_admin::RuleSetV6Request::AddRule { index, matcher, action, responder } => {
773                RuleSetRequest::AddRule {
774                    index: RuleIndex(index),
775                    matcher: matcher.try_into(),
776                    action: action.into(),
777                    responder,
778                }
779            }
780            fnet_routes_admin::RuleSetV6Request::RemoveRule { index, responder } => {
781                RuleSetRequest::RemoveRule { index: RuleIndex(index), responder }
782            }
783            fnet_routes_admin::RuleSetV6Request::AuthenticateForRouteTable {
784                table,
785                token,
786                responder,
787            } => RuleSetRequest::AuthenticateForRouteTable { table, token, responder },
788            fnet_routes_admin::RuleSetV6Request::Close { control_handle } => {
789                RuleSetRequest::Close { control_handle }
790            }
791        }
792    }
793}
794
795/// Rule set creation errors.
796#[derive(Clone, Debug, Error)]
797pub enum RuleSetCreationError {
798    /// Proxy creation failed.
799    #[error("failed to create proxy: {0}")]
800    CreateProxy(fidl::Error),
801    /// Rule set creation failed.
802    #[error("failed to create route set: {0}")]
803    RuleSet(fidl::Error),
804}
805
806/// Creates a new rule set for the rule table.
807pub fn new_rule_set<I: Ip + FidlRuleAdminIpExt>(
808    rule_table_proxy: &<I::RuleTableMarker as ProtocolMarker>::Proxy,
809    priority: RuleSetPriority,
810) -> Result<<I::RuleSetMarker as ProtocolMarker>::Proxy, RuleSetCreationError> {
811    let (rule_set_proxy, rule_set_server_end) = fidl::endpoints::create_proxy::<I::RuleSetMarker>();
812
813    #[derive(GenericOverIp)]
814    #[generic_over_ip(I, Ip)]
815    struct NewRuleSetInput<'a, I: FidlRuleAdminIpExt> {
816        rule_set_server_end: fidl::endpoints::ServerEnd<I::RuleSetMarker>,
817        rule_table_proxy: &'a <I::RuleTableMarker as ProtocolMarker>::Proxy,
818    }
819    let result = I::map_ip_in(
820        NewRuleSetInput::<'_, I> { rule_set_server_end, rule_table_proxy },
821        |NewRuleSetInput { rule_set_server_end, rule_table_proxy }| {
822            rule_table_proxy.new_rule_set(priority.into(), rule_set_server_end)
823        },
824        |NewRuleSetInput { rule_set_server_end, rule_table_proxy }| {
825            rule_table_proxy.new_rule_set(priority.into(), rule_set_server_end)
826        },
827    );
828
829    result.map_err(RuleSetCreationError::RuleSet)?;
830    Ok(rule_set_proxy)
831}
832
833/// Dispatches `authenticate_for_route_table` on either the `RuleSetV4` or
834/// `RuleSetV6` proxy.
835pub async fn authenticate_for_route_table<I: Ip + FidlRuleAdminIpExt>(
836    rule_set: &<I::RuleSetMarker as ProtocolMarker>::Proxy,
837    table_id: u32,
838    token: fidl::Event,
839) -> Result<Result<(), fnet_routes_admin::AuthenticateForRouteTableError>, fidl::Error> {
840    #[derive(GenericOverIp)]
841    #[generic_over_ip(I, Ip)]
842    struct AuthenticateForRouteTableInput<'a, I: FidlRuleAdminIpExt> {
843        rule_set: &'a <I::RuleSetMarker as ProtocolMarker>::Proxy,
844        table_id: u32,
845        token: fidl::Event,
846    }
847
848    I::map_ip_in(
849        AuthenticateForRouteTableInput { rule_set, table_id, token },
850        |AuthenticateForRouteTableInput { rule_set, table_id, token }| {
851            Either::Left(rule_set.authenticate_for_route_table(table_id, token))
852        },
853        |AuthenticateForRouteTableInput { rule_set, table_id, token }| {
854            Either::Right(rule_set.authenticate_for_route_table(table_id, token))
855        },
856    )
857    .await
858}
859
860/// Dispatches `add_rule` on either the `RuleSetV4` or `RuleSetV6` proxy.
861pub async fn add_rule<I: Ip + FidlRuleAdminIpExt>(
862    rule_set: &<I::RuleSetMarker as ProtocolMarker>::Proxy,
863    index: RuleIndex,
864    matcher: RuleMatcher<I>,
865    action: RuleAction,
866) -> Result<Result<(), fnet_routes_admin::RuleSetError>, fidl::Error> {
867    #[derive(GenericOverIp)]
868    #[generic_over_ip(I, Ip)]
869    struct AddRuleInput<'a, I: FidlRuleAdminIpExt> {
870        rule_set: &'a <I::RuleSetMarker as ProtocolMarker>::Proxy,
871        index: RuleIndex,
872        matcher: RuleMatcher<I>,
873        action: RuleAction,
874    }
875
876    I::map_ip_in(
877        AddRuleInput { rule_set, index, matcher, action },
878        |AddRuleInput { rule_set, index, matcher, action }| {
879            Either::Left(rule_set.add_rule(index.into(), &matcher.into(), &action.into()))
880        },
881        |AddRuleInput { rule_set, index, matcher, action }| {
882            Either::Right(rule_set.add_rule(index.into(), &matcher.into(), &action.into()))
883        },
884    )
885    .await
886}
887
888/// Dispatches `remove_rule` on either the `RuleSetV4` or `RuleSetV6` proxy.
889pub async fn remove_rule<I: Ip + FidlRuleAdminIpExt>(
890    rule_set: &<I::RuleSetMarker as ProtocolMarker>::Proxy,
891    index: RuleIndex,
892) -> Result<Result<(), fnet_routes_admin::RuleSetError>, fidl::Error> {
893    #[derive(GenericOverIp)]
894    #[generic_over_ip(I, Ip)]
895    struct RemoveRuleInput<'a, I: FidlRuleAdminIpExt> {
896        rule_set: &'a <I::RuleSetMarker as ProtocolMarker>::Proxy,
897        index: RuleIndex,
898    }
899
900    I::map_ip_in(
901        RemoveRuleInput { rule_set, index },
902        |RemoveRuleInput { rule_set, index }| Either::Left(rule_set.remove_rule(index.into())),
903        |RemoveRuleInput { rule_set, index }| Either::Right(rule_set.remove_rule(index.into())),
904    )
905    .await
906}
907
908/// Dispatches `close` on either the `RuleSetV4` or `RuleSetV6` proxy.
909///
910/// Waits until the channel is closed before returning.
911pub async fn close_rule_set<I: Ip + FidlRuleAdminIpExt>(
912    rule_set: <I::RuleSetMarker as ProtocolMarker>::Proxy,
913) -> Result<(), fidl::Error> {
914    #[derive(GenericOverIp)]
915    #[generic_over_ip(I, Ip)]
916    struct CloseInput<'a, I: FidlRuleAdminIpExt> {
917        rule_set: &'a <I::RuleSetMarker as ProtocolMarker>::Proxy,
918    }
919
920    let result = I::map_ip_in(
921        CloseInput { rule_set: &rule_set },
922        |CloseInput { rule_set }| rule_set.close(),
923        |CloseInput { rule_set }| rule_set.close(),
924    );
925
926    assert!(rule_set
927        .on_closed()
928        .await
929        .expect("failed to wait for signals")
930        .contains(fidl::Signals::CHANNEL_PEER_CLOSED));
931
932    result
933}
934
935/// Dispatches either `GetRuleWatcherV4` or `GetRuleWatcherV6` on the state proxy.
936pub fn get_rule_watcher<I: FidlRuleIpExt + FidlRouteIpExt>(
937    state_proxy: &<I::StateMarker as fidl::endpoints::ProtocolMarker>::Proxy,
938) -> Result<<I::RuleWatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy, WatcherCreationError>
939{
940    let (watcher_proxy, watcher_server_end) =
941        fidl::endpoints::create_proxy::<I::RuleWatcherMarker>();
942
943    #[derive(GenericOverIp)]
944    #[generic_over_ip(I, Ip)]
945    struct GetWatcherInputs<'a, I: FidlRuleIpExt + FidlRouteIpExt> {
946        watcher_server_end: fidl::endpoints::ServerEnd<I::RuleWatcherMarker>,
947        state_proxy: &'a <I::StateMarker as fidl::endpoints::ProtocolMarker>::Proxy,
948    }
949    let result = I::map_ip_in(
950        GetWatcherInputs::<'_, I> { watcher_server_end, state_proxy },
951        |GetWatcherInputs { watcher_server_end, state_proxy }| {
952            state_proxy.get_rule_watcher_v4(
953                watcher_server_end,
954                &fnet_routes::RuleWatcherOptionsV4::default(),
955            )
956        },
957        |GetWatcherInputs { watcher_server_end, state_proxy }| {
958            state_proxy.get_rule_watcher_v6(
959                watcher_server_end,
960                &fnet_routes::RuleWatcherOptionsV6::default(),
961            )
962        },
963    );
964
965    result.map_err(WatcherCreationError::GetWatcher)?;
966    Ok(watcher_proxy)
967}
968
969/// Calls `Watch()` on the provided `RuleWatcherV4` or `RuleWatcherV6` proxy.
970pub async fn watch<'a, I: FidlRuleIpExt>(
971    watcher_proxy: &'a <I::RuleWatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy,
972) -> Result<Vec<I::RuleEvent>, fidl::Error> {
973    #[derive(GenericOverIp)]
974    #[generic_over_ip(I, Ip)]
975    struct WatchInputs<'a, I: FidlRuleIpExt> {
976        watcher_proxy: &'a <I::RuleWatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy,
977    }
978    #[derive(GenericOverIp)]
979    #[generic_over_ip(I, Ip)]
980    struct WatchOutputs<I: FidlRuleIpExt> {
981        watch_fut: fidl::client::QueryResponseFut<Vec<I::RuleEvent>>,
982    }
983    let WatchOutputs { watch_fut } = net_types::map_ip_twice!(
984        I,
985        WatchInputs { watcher_proxy },
986        |WatchInputs { watcher_proxy }| { WatchOutputs { watch_fut: watcher_proxy.watch() } }
987    );
988    watch_fut.await
989}
990
991/// Route watcher `Watch` errors.
992#[derive(Clone, Debug, Error)]
993pub enum RuleWatchError {
994    /// The call to `Watch` returned a FIDL error.
995    #[error("the call to `Watch()` failed: {0}")]
996    Fidl(fidl::Error),
997    /// The event returned by `Watch` encountered a conversion error.
998    #[error("failed to convert event returned by `Watch()`: {0}")]
999    Conversion(RuleFidlConversionError),
1000    /// The server returned an empty batch of events.
1001    #[error("the call to `Watch()` returned an empty batch of events")]
1002    EmptyEventBatch,
1003}
1004
1005/// Creates a rules event stream from the state proxy.
1006pub fn rule_event_stream_from_state<I: FidlRuleIpExt + FidlRouteIpExt>(
1007    state: &<I::StateMarker as fidl::endpoints::ProtocolMarker>::Proxy,
1008) -> Result<impl Stream<Item = Result<RuleEvent<I>, RuleWatchError>>, WatcherCreationError> {
1009    let watcher = get_rule_watcher::<I>(state)?;
1010    rule_event_stream_from_watcher(watcher)
1011}
1012
1013/// Turns the provided watcher client into a [`RuleEvent`] stream by applying
1014/// Hanging-Get watch.
1015///
1016/// Each call to `Watch` returns a batch of events, which are flattened into a
1017/// single stream. If an error is encountered while calling `Watch` or while
1018/// converting the event, the stream is immediately terminated.
1019pub fn rule_event_stream_from_watcher<I: FidlRuleIpExt>(
1020    watcher: <I::RuleWatcherMarker as fidl::endpoints::ProtocolMarker>::Proxy,
1021) -> Result<impl Stream<Item = Result<RuleEvent<I>, RuleWatchError>>, WatcherCreationError> {
1022    Ok(stream::ShortCircuit::new(
1023        futures::stream::try_unfold(watcher, |watcher| async {
1024            let events_batch = watch::<I>(&watcher).await.map_err(RuleWatchError::Fidl)?;
1025            if events_batch.is_empty() {
1026                return Err(RuleWatchError::EmptyEventBatch);
1027            }
1028            let events_batch = events_batch
1029                .into_iter()
1030                .map(|event| event.try_into().map_err(RuleWatchError::Conversion));
1031            let event_stream = futures::stream::iter(events_batch);
1032            Ok(Some((event_stream, watcher)))
1033        })
1034        // Flatten the stream of event streams into a single event stream.
1035        .try_flatten(),
1036    ))
1037}
1038
1039/// Errors returned by [`collect_rules_until_idle`].
1040#[derive(Clone, Debug, Error)]
1041pub enum CollectRulesUntilIdleError<I: FidlRuleIpExt> {
1042    /// There was an error in the event stream.
1043    #[error("there was an error in the event stream: {0}")]
1044    ErrorInStream(RuleWatchError),
1045    /// There was an unexpected event in the event stream. Only `existing` or
1046    /// `idle` events are expected.
1047    #[error("there was an unexpected event in the event stream: {0:?}")]
1048    UnexpectedEvent(RuleEvent<I>),
1049    /// The event stream unexpectedly ended.
1050    #[error("the event stream unexpectedly ended")]
1051    StreamEnded,
1052}
1053
1054/// Collects all `existing` events from the stream, stopping once the `idle`
1055/// event is observed.
1056pub async fn collect_rules_until_idle<I: FidlRuleIpExt, C: Extend<InstalledRule<I>> + Default>(
1057    event_stream: impl futures::Stream<Item = Result<RuleEvent<I>, RuleWatchError>> + Unpin,
1058) -> Result<C, CollectRulesUntilIdleError<I>> {
1059    fold::fold_while(
1060        event_stream,
1061        Ok(C::default()),
1062        |existing_rules: Result<C, CollectRulesUntilIdleError<I>>, event| {
1063            futures::future::ready(match existing_rules {
1064                Err(_) => {
1065                    unreachable!("`existing_rules` must be `Ok`, because we stop folding on err")
1066                }
1067                Ok(mut existing_rules) => match event {
1068                    Err(e) => {
1069                        fold::FoldWhile::Done(Err(CollectRulesUntilIdleError::ErrorInStream(e)))
1070                    }
1071                    Ok(e) => match e {
1072                        RuleEvent::Existing(e) => {
1073                            existing_rules.extend([e]);
1074                            fold::FoldWhile::Continue(Ok(existing_rules))
1075                        }
1076                        RuleEvent::Idle => fold::FoldWhile::Done(Ok(existing_rules)),
1077                        e @ RuleEvent::Added(_) | e @ RuleEvent::Removed(_) => {
1078                            fold::FoldWhile::Done(Err(CollectRulesUntilIdleError::UnexpectedEvent(
1079                                e,
1080                            )))
1081                        }
1082                    },
1083                },
1084            })
1085        },
1086    )
1087    .await
1088    .short_circuited()
1089    .map_err(|_accumulated_thus_far: Result<C, CollectRulesUntilIdleError<I>>| {
1090        CollectRulesUntilIdleError::StreamEnded
1091    })?
1092}
1093
1094#[cfg(test)]
1095mod tests {
1096    use assert_matches::assert_matches;
1097    use fnet_routes::BaseMatcher;
1098
1099    use super::*;
1100
1101    #[test]
1102    fn missing_base_matcher_v4() {
1103        let fidl_matcher = fidl_fuchsia_net_routes::RuleMatcherV4 {
1104            from: None,
1105            base: None,
1106            __source_breaking: fidl::marker::SourceBreaking,
1107        };
1108        assert_matches!(
1109            RuleMatcher::try_from(fidl_matcher),
1110            Err(RuleFidlConversionError::BaseMatcherMissing)
1111        );
1112    }
1113
1114    #[test]
1115    fn missing_base_matcher_v6() {
1116        let fidl_matcher = fidl_fuchsia_net_routes::RuleMatcherV6 {
1117            from: None,
1118            base: None,
1119            __source_breaking: fidl::marker::SourceBreaking,
1120        };
1121        assert_matches!(
1122            RuleMatcher::try_from(fidl_matcher),
1123            Err(RuleFidlConversionError::BaseMatcherMissing)
1124        );
1125    }
1126
1127    #[test]
1128    fn invalid_destination_subnet_v4() {
1129        let fidl_matcher = fidl_fuchsia_net_routes::RuleMatcherV4 {
1130            // Invalid, because subnets should not have the "host bits" set.
1131            from: Some(net_declare::fidl_ip_v4_with_prefix!("192.168.0.1/24")),
1132            base: Some(BaseMatcher::default()),
1133            __source_breaking: fidl::marker::SourceBreaking,
1134        };
1135        assert_matches!(
1136            RuleMatcher::try_from(fidl_matcher),
1137            Err(RuleFidlConversionError::DestinationSubnet(_))
1138        );
1139    }
1140
1141    #[test]
1142    fn invalid_destination_subnet_v6() {
1143        let fidl_matcher = fidl_fuchsia_net_routes::RuleMatcherV6 {
1144            // Invalid, because subnets should not have the "host bits" set.
1145            from: Some(net_declare::fidl_ip_v6_with_prefix!("fe80::1/64")),
1146            base: Some(BaseMatcher::default()),
1147            __source_breaking: fidl::marker::SourceBreaking,
1148        };
1149        assert_matches!(
1150            RuleMatcher::try_from(fidl_matcher),
1151            Err(RuleFidlConversionError::DestinationSubnet(_))
1152        );
1153    }
1154
1155    #[test]
1156    fn all_unspecified_matcher_v4() {
1157        let fidl_matcher = fidl_fuchsia_net_routes::RuleMatcherV4 {
1158            from: None,
1159            base: Some(BaseMatcher {
1160                locally_generated: None,
1161                bound_device: None,
1162                mark_1: None,
1163                mark_2: None,
1164                __source_breaking: fidl::marker::SourceBreaking,
1165            }),
1166            __source_breaking: fidl::marker::SourceBreaking,
1167        };
1168        assert_matches!(
1169            RuleMatcher::try_from(fidl_matcher),
1170            Ok(RuleMatcher {
1171                from: None,
1172                locally_generated: None,
1173                bound_device: None,
1174                mark_1: None,
1175                mark_2: None,
1176            })
1177        );
1178    }
1179
1180    #[test]
1181    fn all_unspecified_matcher_v6() {
1182        let fidl_matcher = fidl_fuchsia_net_routes::RuleMatcherV6 {
1183            from: None,
1184            base: Some(BaseMatcher {
1185                locally_generated: None,
1186                bound_device: None,
1187                mark_1: None,
1188                mark_2: None,
1189                __source_breaking: fidl::marker::SourceBreaking,
1190            }),
1191            __source_breaking: fidl::marker::SourceBreaking,
1192        };
1193        assert_matches!(
1194            RuleMatcher::try_from(fidl_matcher),
1195            Ok(RuleMatcher {
1196                from: None,
1197                locally_generated: None,
1198                bound_device: None,
1199                mark_1: None,
1200                mark_2: None,
1201            })
1202        );
1203    }
1204}