Skip to main content

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