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