net_cli/
filter.rs

1// Copyright 2022 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
5use std::collections::{BTreeMap, HashMap};
6use std::pin::pin;
7use std::str::FromStr as _;
8
9use anyhow::{anyhow, Error};
10use fidl_fuchsia_net_filter_ext::{
11    self as fnet_filter_ext, ControllerId, Domain, InstalledIpRoutine, InstalledNatRoutine, IpHook,
12    NamespaceId, NatHook, Resource, ResourceId, RoutineId, RoutineType, Rule, RuleId, Update,
13};
14use {
15    fidl_fuchsia_net_ext as fnet_ext, fidl_fuchsia_net_filter as fnet_filter,
16    fidl_fuchsia_net_root as fnet_root,
17};
18
19use crate::{connect_with_context, opts, NetCliDepsConnector};
20
21/// A datatype storing the filtering resource state. Design inspired by nftables.
22///
23/// nftables: table -> chains -> rules
24/// netstack: namespace -> routines -> rules
25///
26/// Note: The Netstack has an additional datatype, Controller at the top level. Each controller has
27/// many namespaces.
28#[derive(Debug, Default)]
29pub struct FilteringResources {
30    controllers: BTreeMap<ControllerId, BTreeMap<NamespaceId, Namespace>>,
31}
32
33impl FilteringResources {
34    /// Gets an iterator over ControllerId, ordered alphabetically.
35    pub fn controllers(&self) -> impl Iterator<Item = &ControllerId> {
36        self.controllers.keys()
37    }
38
39    /// Gets Namespaces, given a ControllerId.
40    pub fn namespaces(
41        &self,
42        controller_id: &ControllerId,
43    ) -> Option<impl Iterator<Item = &Namespace>> {
44        let namespaces = self.controllers.get(controller_id)?.values();
45
46        Some(namespaces)
47    }
48
49    fn find_namespace_mut(
50        &mut self,
51        controller_id: &ControllerId,
52        namespace_id: &NamespaceId,
53    ) -> Option<&mut Namespace> {
54        self.controllers.get_mut(controller_id)?.get_mut(namespace_id)
55    }
56
57    fn add_namespace(
58        &mut self,
59        controller_id: ControllerId,
60        namespace: fidl_fuchsia_net_filter_ext::Namespace,
61    ) -> Option<Resource> {
62        let namespace = Namespace::new(namespace);
63        let namespaces = self.controllers.entry(controller_id).or_default();
64
65        namespaces
66            .insert(namespace.id.clone(), namespace)
67            .map(|namespace| Resource::Namespace(namespace.into()))
68    }
69
70    fn remove_namespace(
71        &mut self,
72        controller_id: &ControllerId,
73        namespace_id: &NamespaceId,
74    ) -> Option<Resource> {
75        let namespaces = self.controllers.get_mut(controller_id)?;
76
77        let removed_namespace = namespaces.remove(namespace_id)?;
78
79        // If last namespace deleted, remove the controller.
80        if namespaces.is_empty() {
81            let _ = self
82                .controllers
83                .remove(controller_id)
84                .expect("Attempted to delete controller but failed.");
85        }
86
87        Some(Resource::Namespace(removed_namespace.into()))
88    }
89}
90
91#[derive(Debug, Clone, PartialEq)]
92pub struct Namespace {
93    pub id: NamespaceId,
94    pub domain: Domain,
95    routines: HashMap<RoutineId, Routine>,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
99pub struct RoutinePriority(pub i32);
100
101#[derive(Debug, Default, PartialEq)]
102pub struct IpRoutines {
103    pub ingress: BTreeMap<RoutinePriority, Vec<Routine>>,
104    pub local_ingress: BTreeMap<RoutinePriority, Vec<Routine>>,
105    pub forwarding: BTreeMap<RoutinePriority, Vec<Routine>>,
106    pub local_egress: BTreeMap<RoutinePriority, Vec<Routine>>,
107    pub egress: BTreeMap<RoutinePriority, Vec<Routine>>,
108    pub uninstalled: Vec<Routine>,
109}
110
111impl IpRoutines {
112    pub fn has_installed_routines(&self) -> bool {
113        !self.ingress.is_empty()
114            || !self.local_ingress.is_empty()
115            || !self.forwarding.is_empty()
116            || !self.local_egress.is_empty()
117            || !self.egress.is_empty()
118    }
119
120    pub fn has_uninstalled_routines(&self) -> bool {
121        !self.uninstalled.is_empty()
122    }
123}
124
125#[derive(Debug, Default, PartialEq)]
126pub struct NatRoutines {
127    pub ingress: BTreeMap<RoutinePriority, Vec<Routine>>,
128    pub local_ingress: BTreeMap<RoutinePriority, Vec<Routine>>,
129    pub local_egress: BTreeMap<RoutinePriority, Vec<Routine>>,
130    pub egress: BTreeMap<RoutinePriority, Vec<Routine>>,
131    pub uninstalled: Vec<Routine>,
132}
133
134impl NatRoutines {
135    pub fn has_installed_routines(&self) -> bool {
136        !self.ingress.is_empty()
137            || !self.local_ingress.is_empty()
138            || !self.local_egress.is_empty()
139            || !self.egress.is_empty()
140    }
141
142    pub fn has_uninstalled_routines(&self) -> bool {
143        !self.uninstalled.is_empty()
144    }
145}
146
147impl Namespace {
148    fn new(namespace: fidl_fuchsia_net_filter_ext::Namespace) -> Self {
149        Self { id: namespace.id, domain: namespace.domain, routines: HashMap::new() }
150    }
151
152    /// Gets routines in a namespace grouped by their RoutineType.
153    pub fn routines(&self) -> (IpRoutines, NatRoutines) {
154        let mut ip_routines = IpRoutines::default();
155        let mut nat_routines = NatRoutines::default();
156
157        self.routines.values().for_each(|routine| {
158            let routine = routine.clone();
159            match routine.routine_type {
160                RoutineType::Nat(None) => nat_routines.uninstalled.push(routine),
161                RoutineType::Ip(None) => ip_routines.uninstalled.push(routine),
162                RoutineType::Nat(Some(InstalledNatRoutine { priority, hook })) => {
163                    let routines_on_hook = match hook {
164                        NatHook::Egress => &mut nat_routines.egress,
165                        NatHook::LocalEgress => &mut nat_routines.local_egress,
166                        NatHook::Ingress => &mut nat_routines.ingress,
167                        NatHook::LocalIngress => &mut nat_routines.local_ingress,
168                    };
169
170                    let routine_priority = RoutinePriority(priority);
171                    routines_on_hook.entry(routine_priority).or_default().push(routine)
172                }
173                RoutineType::Ip(Some(InstalledIpRoutine { priority, hook })) => {
174                    let routines_on_hook = match hook {
175                        IpHook::Egress => &mut ip_routines.egress,
176                        IpHook::LocalEgress => &mut ip_routines.local_egress,
177                        IpHook::Ingress => &mut ip_routines.ingress,
178                        IpHook::LocalIngress => &mut ip_routines.local_ingress,
179                        IpHook::Forwarding => &mut ip_routines.forwarding,
180                    };
181
182                    let routine_priority = RoutinePriority(priority);
183                    routines_on_hook.entry(routine_priority).or_default().push(routine)
184                }
185            }
186        });
187
188        (ip_routines, nat_routines)
189    }
190
191    fn find_routine_mut(&mut self, routine_id: &RoutineId) -> Option<&mut Routine> {
192        self.routines.get_mut(routine_id)
193    }
194
195    fn add_routine(&mut self, routine: fidl_fuchsia_net_filter_ext::Routine) -> Option<Resource> {
196        let routine = Routine::new(routine);
197
198        self.routines
199            .insert(routine.id.clone(), routine)
200            .map(|routine| Resource::Routine(routine.into()))
201    }
202
203    fn remove_routine(&mut self, routine_id: &RoutineId) -> Option<Resource> {
204        self.routines.remove(routine_id).map(|routine| Resource::Routine(routine.into()))
205    }
206}
207
208impl Into<fidl_fuchsia_net_filter_ext::Namespace> for Namespace {
209    fn into(self) -> fidl_fuchsia_net_filter_ext::Namespace {
210        fidl_fuchsia_net_filter_ext::Namespace { id: self.id, domain: self.domain }
211    }
212}
213
214#[derive(Debug, Clone, PartialEq)]
215pub struct Routine {
216    pub id: RoutineId,
217    pub routine_type: RoutineType,
218    rules: BTreeMap<u32, Rule>,
219}
220
221impl Routine {
222    fn new(routine: fidl_fuchsia_net_filter_ext::Routine) -> Self {
223        Self { id: routine.id, routine_type: routine.routine_type, rules: BTreeMap::new() }
224    }
225
226    /// Gets an iterator over the rules in this routine, in order by rule's index.
227    pub fn rules(&self) -> impl Iterator<Item = &Rule> {
228        self.rules.values()
229    }
230
231    fn add_rule(&mut self, rule: Rule) -> Option<Resource> {
232        self.rules
233            .insert(rule.id.index, rule)
234            .map(|existing_rule| Resource::Rule(existing_rule.into()))
235    }
236
237    fn remove_rule(&mut self, rule_id: &RuleId) -> Option<Resource> {
238        self.rules.remove(&rule_id.index).map(|rule| Resource::Rule(rule.into()))
239    }
240}
241
242impl Into<fidl_fuchsia_net_filter_ext::Routine> for Routine {
243    fn into(self) -> fidl_fuchsia_net_filter_ext::Routine {
244        fidl_fuchsia_net_filter_ext::Routine { id: self.id, routine_type: self.routine_type }
245    }
246}
247
248impl Update for FilteringResources {
249    fn add(&mut self, controller: ControllerId, resource: Resource) -> Option<Resource> {
250        match resource {
251            Resource::Namespace(namespace) => self.add_namespace(controller, namespace),
252            Resource::Routine(routine) => {
253                let namespace = self
254                    .find_namespace_mut(&controller, &routine.id.namespace)
255                    .expect("Routine's namespace does not exist.");
256
257                namespace.add_routine(routine)
258            }
259            Resource::Rule(rule) => {
260                let namespace = self
261                    .find_namespace_mut(&controller, &rule.id.routine.namespace)
262                    .expect("Rule's namespace does not exist.");
263
264                let routine = namespace
265                    .find_routine_mut(&rule.id.routine)
266                    .expect("Rule's routine does not exist.");
267
268                routine.add_rule(rule)
269            }
270        }
271    }
272
273    fn remove(&mut self, controller: ControllerId, resource: &ResourceId) -> Option<Resource> {
274        match resource {
275            ResourceId::Namespace(namespace_id) => self.remove_namespace(&controller, namespace_id),
276            ResourceId::Routine(routine_id) => {
277                let namespace = self.find_namespace_mut(&controller, &routine_id.namespace)?;
278
279                namespace.remove_routine(routine_id)
280            }
281            ResourceId::Rule(rule_id) => {
282                let namespace = self.find_namespace_mut(&controller, &rule_id.routine.namespace)?;
283                let routine = namespace.find_routine_mut(&rule_id.routine)?;
284
285                routine.remove_rule(rule_id)
286            }
287        }
288    }
289}
290
291pub(super) async fn do_filter<C: NetCliDepsConnector, W: std::io::Write>(
292    mut out: W,
293    cmd: opts::filter::FilterEnum,
294    connector: &C,
295) -> Result<(), Error> {
296    use opts::filter::{
297        Accept, Action, Create, Domain, Drop, FilterEnum, Hook, Jump, List, Masquerade, Namespace,
298        NamespaceId, PortRange, Redirect, Remove, Resource, ResourceId, Return, Routine, RoutineId,
299        RoutineType, Rule, RuleId, TransparentProxy, TransportProtocolMatcher,
300    };
301
302    match cmd {
303        FilterEnum::List(List {}) => {
304            let state = connect_with_context::<fnet_filter::StateMarker, _>(connector).await?;
305            let stream = fnet_filter_ext::event_stream_from_state(state)?;
306            let mut stream = pin!(stream);
307            let resources: FilteringResources =
308                fnet_filter_ext::get_existing_resources(&mut stream).await?;
309
310            for controller_id @ ControllerId(id) in resources.controllers() {
311                writeln_scoped(
312                    &mut out,
313                    0,
314                    format!("controller: \"{}\"", id).as_str(),
315                    |out, padding| {
316                        for namespace in resources.namespaces(controller_id).unwrap() {
317                            do_filter_print_namespace(out, padding, namespace)?;
318                        }
319
320                        Ok(())
321                    },
322                )?;
323            }
324        }
325        FilterEnum::Create(Create { controller, resource, idempotent }) => {
326            let resource = match resource {
327                Resource::Namespace(Namespace { name, domain }) => {
328                    fnet_filter_ext::Resource::Namespace(fnet_filter_ext::Namespace {
329                        id: fnet_filter_ext::NamespaceId(name),
330                        domain: match domain {
331                            Domain::All => fnet_filter_ext::Domain::AllIp,
332                            Domain::Ipv4 => fnet_filter_ext::Domain::Ipv4,
333                            Domain::Ipv6 => fnet_filter_ext::Domain::Ipv6,
334                        },
335                    })
336                }
337                Resource::Routine(Routine { namespace, name, type_, hook, priority }) => {
338                    fnet_filter_ext::Resource::Routine(fnet_filter_ext::Routine {
339                        id: fnet_filter_ext::RoutineId {
340                            namespace: fnet_filter_ext::NamespaceId(namespace),
341                            name,
342                        },
343                        routine_type: match type_ {
344                            RoutineType::Ip => {
345                                let installation = match (hook, priority) {
346                                    (None, None) => None,
347                                    (Some(hook), priority) => {
348                                        Some(fnet_filter_ext::InstalledIpRoutine {
349                                            hook: match hook {
350                                                Hook::Ingress => fnet_filter_ext::IpHook::Ingress,
351                                                Hook::LocalIngress => {
352                                                    fnet_filter_ext::IpHook::LocalIngress
353                                                }
354                                                Hook::Forwarding => {
355                                                    fnet_filter_ext::IpHook::Forwarding
356                                                }
357                                                Hook::LocalEgress => {
358                                                    fnet_filter_ext::IpHook::LocalEgress
359                                                }
360                                                Hook::Egress => fnet_filter_ext::IpHook::Egress,
361                                            },
362                                            priority: priority.unwrap_or_default(),
363                                        })
364                                    }
365                                    (None, Some(_)) => {
366                                        return Err(anyhow!(
367                                            "cannot specify priority without an installation hook"
368                                        ));
369                                    }
370                                };
371                                fnet_filter_ext::RoutineType::Ip(installation)
372                            }
373                            RoutineType::Nat => {
374                                let installation = match (hook, priority) {
375                                    (None, None) => None,
376                                    (Some(hook), priority) => {
377                                        Some(fnet_filter_ext::InstalledNatRoutine {
378                                            hook: match hook {
379                                                Hook::Ingress => fnet_filter_ext::NatHook::Ingress,
380                                                Hook::LocalIngress => {
381                                                    fnet_filter_ext::NatHook::LocalIngress
382                                                }
383                                                Hook::LocalEgress => {
384                                                    fnet_filter_ext::NatHook::LocalEgress
385                                                }
386                                                Hook::Egress => fnet_filter_ext::NatHook::Egress,
387                                                Hook::Forwarding => {
388                                                    return Err(anyhow!(
389                                                        "NAT routines do not run in forwarding hook"
390                                                    ));
391                                                }
392                                            },
393                                            priority: priority.unwrap_or_default(),
394                                        })
395                                    }
396                                    (None, Some(_)) => {
397                                        return Err(anyhow!(
398                                            "cannot specify priority without an installation hook"
399                                        ));
400                                    }
401                                };
402                                fnet_filter_ext::RoutineType::Nat(installation)
403                            }
404                        },
405                    })
406                }
407                Resource::Rule(Rule {
408                    namespace,
409                    routine,
410                    index,
411                    in_interface,
412                    out_interface,
413                    src_addr,
414                    dst_addr,
415                    transport_protocol,
416                    src_port,
417                    dst_port,
418                    action,
419                }) => fnet_filter_ext::Resource::Rule(fnet_filter_ext::Rule {
420                    id: fnet_filter_ext::RuleId {
421                        routine: fnet_filter_ext::RoutineId {
422                            namespace: fnet_filter_ext::NamespaceId(namespace),
423                            name: routine,
424                        },
425                        index,
426                    },
427                    matchers: {
428                        fnet_filter_ext::Matchers {
429                            in_interface: in_interface.map(Into::into),
430                            out_interface: out_interface.map(Into::into),
431                            src_addr: src_addr.map(Into::into),
432                            dst_addr: dst_addr.map(Into::into),
433                            transport_protocol: match transport_protocol {
434                                None => {
435                                    if src_port != None || dst_port != None {
436                                        return Err(anyhow!(
437                                            "must provide a transport protocol to match on port"
438                                        ));
439                                    }
440                                    None
441                                }
442                                Some(matcher) => Some(match matcher {
443                                    TransportProtocolMatcher::Tcp => {
444                                        fnet_filter_ext::TransportProtocolMatcher::Tcp {
445                                            src_port: src_port.map(Into::into),
446                                            dst_port: dst_port.map(Into::into),
447                                        }
448                                    }
449                                    TransportProtocolMatcher::Udp => {
450                                        fnet_filter_ext::TransportProtocolMatcher::Udp {
451                                            src_port: src_port.map(Into::into),
452                                            dst_port: dst_port.map(Into::into),
453                                        }
454                                    }
455                                    TransportProtocolMatcher::Icmp => {
456                                        if src_port != None || dst_port != None {
457                                            return Err(anyhow!(
458                                                "cannot match on src or dst port for ICMP packets"
459                                            ));
460                                        }
461                                        fnet_filter_ext::TransportProtocolMatcher::Icmp
462                                    }
463                                    TransportProtocolMatcher::Icmpv6 => {
464                                        if src_port != None || dst_port != None {
465                                            return Err(anyhow!(
466                                            "cannot match on src or dst port for ICMPv6 packets"
467                                        ));
468                                        }
469                                        fnet_filter_ext::TransportProtocolMatcher::Icmpv6
470                                    }
471                                }),
472                            },
473                            ..Default::default()
474                        }
475                    },
476                    action: match action {
477                        Action::Accept(Accept {}) => fnet_filter_ext::Action::Accept,
478                        Action::Drop(Drop {}) => fnet_filter_ext::Action::Drop,
479                        Action::Jump(Jump { target }) => fnet_filter_ext::Action::Jump(target),
480                        Action::Return(Return {}) => fnet_filter_ext::Action::Return,
481                        Action::TransparentProxy(TransparentProxy { addr, port }) => {
482                            let tproxy = match (addr, port) {
483                                (None, None) => {
484                                    return Err(anyhow!(
485                                        "transparent proxy must specify at least one of local \
486                                            address and local port"
487                                    ))
488                                }
489                                (Some(addr), None) => {
490                                    let addr = fnet_ext::IpAddress::from_str(&addr)?.into();
491                                    fnet_filter_ext::TransparentProxy::LocalAddr(addr)
492                                }
493                                (None, Some(port)) => {
494                                    fnet_filter_ext::TransparentProxy::LocalPort(port)
495                                }
496                                (Some(addr), Some(port)) => {
497                                    let addr = fnet_ext::IpAddress::from_str(&addr)?.into();
498                                    fnet_filter_ext::TransparentProxy::LocalAddrAndPort(addr, port)
499                                }
500                            };
501                            fnet_filter_ext::Action::TransparentProxy(tproxy)
502                        }
503                        Action::Redirect(Redirect { dst_port }) => {
504                            fnet_filter_ext::Action::Redirect {
505                                dst_port: dst_port
506                                    .map(|PortRange(range)| fnet_filter_ext::PortRange(range)),
507                            }
508                        }
509                        Action::Masquerade(Masquerade { src_port }) => {
510                            fnet_filter_ext::Action::Masquerade {
511                                src_port: src_port
512                                    .map(|PortRange(range)| fnet_filter_ext::PortRange(range)),
513                            }
514                        }
515                    },
516                }),
517            };
518            let root = connect_with_context::<fnet_root::FilterMarker, _>(connector).await?;
519            let mut controller = fnet_filter_ext::Controller::new_root(
520                &root,
521                &fnet_filter_ext::ControllerId(controller),
522            )
523            .await?;
524            controller.push_changes(vec![fnet_filter_ext::Change::Create(resource)]).await?;
525            if idempotent.unwrap_or(false) {
526                controller.commit_idempotent().await?;
527            } else {
528                controller.commit().await?;
529            }
530        }
531        FilterEnum::Remove(Remove { controller, resource, idempotent }) => {
532            let id = match resource {
533                ResourceId::Namespace(NamespaceId { name }) => {
534                    fnet_filter_ext::ResourceId::Namespace(fnet_filter_ext::NamespaceId(name))
535                }
536                ResourceId::Routine(RoutineId { namespace, name }) => {
537                    fnet_filter_ext::ResourceId::Routine(fnet_filter_ext::RoutineId {
538                        namespace: fnet_filter_ext::NamespaceId(namespace),
539                        name,
540                    })
541                }
542                ResourceId::Rule(RuleId { namespace, routine, index }) => {
543                    fnet_filter_ext::ResourceId::Rule(fnet_filter_ext::RuleId {
544                        routine: fnet_filter_ext::RoutineId {
545                            namespace: fnet_filter_ext::NamespaceId(namespace),
546                            name: routine,
547                        },
548                        index,
549                    })
550                }
551            };
552            let root = connect_with_context::<fnet_root::FilterMarker, _>(connector).await?;
553            let mut controller = fnet_filter_ext::Controller::new_root(
554                &root,
555                &fnet_filter_ext::ControllerId(controller),
556            )
557            .await?;
558            controller.push_changes(vec![fnet_filter_ext::Change::Remove(id)]).await?;
559            if idempotent.unwrap_or(false) {
560                controller.commit_idempotent().await?;
561            } else {
562                controller.commit().await?;
563            }
564        }
565    }
566    Ok(())
567}
568
569fn do_filter_print_namespace<W: std::io::Write>(
570    out: &mut W,
571    padding: usize,
572    namespace: &Namespace,
573) -> Result<(), Error> {
574    let Namespace { id, domain, .. } = namespace;
575
576    writeln_scoped(out, padding, format!("namespace: \"{}\"", id.0).as_str(), |out, padding| {
577        writeln!(out, "{:padding$}domain: {:?}", "", domain)?;
578
579        let (ip_routines, nat_routines) = &namespace.routines();
580
581        if ip_routines.has_installed_routines() {
582            writeln_scoped(out, padding, "installed IP routines", |out, padding| {
583                let IpRoutines {
584                    ingress,
585                    local_ingress,
586                    forwarding,
587                    local_egress,
588                    egress,
589                    uninstalled: _,
590                } = ip_routines;
591                for (label, routines) in [
592                    ("INGRESS", ingress),
593                    ("LOCAL INGRESS", local_ingress),
594                    ("FORWARDING", forwarding),
595                    ("LOCAL EGRESS", local_egress),
596                    ("EGRESS", egress),
597                ] {
598                    do_filter_print_installed_routines(out, padding, label, routines)?;
599                }
600
601                Ok(())
602            })?;
603        }
604
605        if ip_routines.has_uninstalled_routines() {
606            writeln_scoped(out, padding, "uninstalled IP routines", |out, padding| {
607                for routine in ip_routines.uninstalled.iter() {
608                    writeln_scoped(out, padding, routine.id.name.as_str(), |out, padding| {
609                        do_filter_print_rules(out, padding, routine.rules())
610                    })?;
611                }
612
613                Ok(())
614            })?;
615        }
616
617        if nat_routines.has_installed_routines() {
618            let NatRoutines { ingress, local_ingress, local_egress, egress, uninstalled: _ } =
619                nat_routines;
620            writeln_scoped(out, padding, "installed NAT routines", |out, padding| {
621                for (label, routines) in [
622                    ("INGRESS", ingress),
623                    ("LOCAL INGRESS", local_ingress),
624                    ("LOCAL EGRESS", local_egress),
625                    ("EGRESS", egress),
626                ] {
627                    do_filter_print_installed_routines(out, padding, label, routines)?;
628                }
629
630                Ok(())
631            })?;
632        }
633
634        if nat_routines.has_uninstalled_routines() {
635            writeln_scoped(out, padding, "uninstalled NAT routines", |out, padding| {
636                for routine in nat_routines.uninstalled.iter() {
637                    writeln_scoped(out, padding, routine.id.name.as_str(), |out, padding| {
638                        do_filter_print_rules(out, padding, routine.rules())
639                    })?;
640                }
641
642                Ok(())
643            })?;
644        }
645        Ok(())
646    })
647}
648
649fn do_filter_print_installed_routines<W: std::io::Write>(
650    out: &mut W,
651    padding: usize,
652    label: &str,
653    routines: &BTreeMap<RoutinePriority, Vec<Routine>>,
654) -> Result<(), Error> {
655    if routines.is_empty() {
656        return Ok(());
657    }
658
659    writeln_scoped(out, padding, label, |out, padding| {
660        for (RoutinePriority(priority), routines) in routines.iter() {
661            for routine @ Routine { id, .. } in routines {
662                writeln_scoped(
663                    out,
664                    padding,
665                    format!("{}. {}", priority, id.name).as_str(),
666                    |out, padding| do_filter_print_rules(out, padding, routine.rules()),
667                )?;
668            }
669        }
670
671        Ok(())
672    })?;
673
674    Ok(())
675}
676
677fn do_filter_print_rules<'a, W: std::io::Write>(
678    out: &mut W,
679    padding: usize,
680    rules: impl Iterator<Item = &'a Rule>,
681) -> Result<(), Error> {
682    for rule in rules {
683        writeln!(
684            out,
685            "{:padding$}{}. {:?} -> {:?}",
686            "", rule.id.index, rule.matchers, rule.action
687        )?;
688    }
689
690    Ok(())
691}
692
693fn writeln_scoped<W: std::io::Write>(
694    out: &mut W,
695    padding: usize,
696    label: &str,
697    inner_scope: impl Fn(&mut W, usize) -> Result<(), Error>,
698) -> Result<(), Error> {
699    writeln!(out, "{:padding$}{label} {{", "")?;
700
701    inner_scope(out, padding + 4)?;
702
703    writeln!(out, "{:padding$}}}", "")?;
704
705    Ok(())
706}
707
708#[cfg(test)]
709mod tests {
710    use assert_matches::assert_matches;
711    use fidl_fuchsia_net_filter_ext::{Action, Matchers};
712    use futures::TryStreamExt as _;
713    use itertools::Itertools;
714    use net_declare::fidl_subnet;
715    use std::num::NonZeroU64;
716
717    use super::*;
718    use crate::testutil::TestConnector;
719
720    /// Test helpers
721    impl FilteringResources {
722        fn find_namespace(
723            &self,
724            controller_id: &ControllerId,
725            namespace_id: &NamespaceId,
726        ) -> Option<&Namespace> {
727            self.controllers.get(controller_id)?.get(namespace_id)
728        }
729
730        fn namespaces_fidl(
731            &self,
732            controller_id: ControllerId,
733        ) -> Option<Vec<fidl_fuchsia_net_filter_ext::Namespace>> {
734            let namespaces = self
735                .namespaces(&controller_id)?
736                .map(|namespace| namespace.clone().into())
737                .collect();
738
739            Some(namespaces)
740        }
741
742        fn routines(
743            &self,
744            controller_id: ControllerId,
745            namespace_id: NamespaceId,
746        ) -> Option<Vec<fidl_fuchsia_net_filter_ext::Routine>> {
747            let routines = self
748                .find_namespace(&controller_id, &namespace_id)?
749                .routines
750                .values()
751                .sorted_by(|routine_a, routine_b| routine_b.priority().cmp(&routine_a.priority()))
752                .map(|routine| routine.clone().into())
753                .collect();
754
755            Some(routines)
756        }
757
758        fn rules(
759            &self,
760            controller_id: ControllerId,
761            namespace_id: NamespaceId,
762            routine_id: RoutineId,
763        ) -> Option<Vec<&Rule>> {
764            self.find_namespace(&controller_id, &namespace_id)?
765                .routines
766                .get(&routine_id)
767                .map(|routine| routine.rules.values().collect_vec())
768        }
769    }
770
771    impl Routine {
772        fn priority(&self) -> Option<i32> {
773            match &self.routine_type {
774                RoutineType::Ip(Some(ip)) => Some(ip.priority),
775                RoutineType::Nat(Some(nat)) => Some(nat.priority),
776                RoutineType::Ip(None) | RoutineType::Nat(None) => None,
777            }
778        }
779    }
780
781    fn test_controller_a() -> ControllerId {
782        ControllerId(String::from("test-controller-a"))
783    }
784
785    fn test_controller_b() -> ControllerId {
786        ControllerId(String::from("test-controller-b"))
787    }
788
789    fn test_controller_c() -> ControllerId {
790        ControllerId(String::from("test-controller-c"))
791    }
792
793    fn test_namespace_a() -> fidl_fuchsia_net_filter_ext::Namespace {
794        fidl_fuchsia_net_filter_ext::Namespace {
795            id: NamespaceId(String::from("test-namespace-a")),
796            domain: Domain::AllIp,
797        }
798    }
799
800    fn test_namespace_resource_a() -> Resource {
801        Resource::Namespace(test_namespace_a())
802    }
803
804    fn test_namespace_b() -> fidl_fuchsia_net_filter_ext::Namespace {
805        fidl_fuchsia_net_filter_ext::Namespace {
806            id: NamespaceId(String::from("test-namespace-b")),
807            domain: Domain::AllIp,
808        }
809    }
810
811    fn test_namespace_resource_b() -> Resource {
812        Resource::Namespace(test_namespace_b())
813    }
814
815    fn test_routine_a() -> fidl_fuchsia_net_filter_ext::Routine {
816        fidl_fuchsia_net_filter_ext::Routine {
817            id: RoutineId {
818                namespace: test_namespace_a().id,
819                name: String::from("test-routine-a"),
820            },
821            routine_type: RoutineType::Ip(None),
822        }
823    }
824
825    fn test_routine_with_priority(
826        name: String,
827        priority: i32,
828    ) -> fidl_fuchsia_net_filter_ext::Routine {
829        fidl_fuchsia_net_filter_ext::Routine {
830            id: RoutineId { namespace: test_namespace_a().id, name },
831            routine_type: RoutineType::Ip(Some(InstalledIpRoutine {
832                priority,
833                hook: IpHook::Ingress,
834            })),
835        }
836    }
837
838    fn test_routine_with_routine_type(
839        name: String,
840        routine_type: RoutineType,
841    ) -> fidl_fuchsia_net_filter_ext::Routine {
842        fidl_fuchsia_net_filter_ext::Routine {
843            id: RoutineId { namespace: test_namespace_a().id, name },
844            routine_type,
845        }
846    }
847
848    fn test_routine_resource_a() -> Resource {
849        Resource::Routine(test_routine_a())
850    }
851
852    fn test_routine_b() -> fidl_fuchsia_net_filter_ext::Routine {
853        fidl_fuchsia_net_filter_ext::Routine {
854            id: RoutineId {
855                namespace: test_namespace_b().id,
856                name: String::from("test-routine-b"),
857            },
858            routine_type: RoutineType::Ip(None),
859        }
860    }
861
862    fn test_routine_resource_b() -> Resource {
863        Resource::Routine(test_routine_b())
864    }
865
866    fn test_rule_a() -> Rule {
867        Rule {
868            id: RuleId { routine: test_routine_a().id, index: 1 },
869            matchers: Matchers::default(),
870            action: Action::Accept,
871        }
872    }
873
874    fn test_rule_a_with_index(index: u32) -> Rule {
875        Rule {
876            id: RuleId { routine: test_routine_a().id, index },
877            matchers: Matchers::default(),
878            action: Action::Accept,
879        }
880    }
881
882    fn test_rule_resource_a() -> Resource {
883        Resource::Rule(test_rule_a())
884    }
885
886    #[test]
887    fn add_namespace_not_existing() {
888        let mut tree = FilteringResources::default();
889
890        let result = tree.add(test_controller_a(), test_namespace_resource_a());
891
892        assert_eq!(result, None);
893        assert_eq!(tree.namespaces_fidl(test_controller_a()), Some(vec![test_namespace_a()]));
894    }
895
896    #[test]
897    fn add_namespace_adds_controller() {
898        let mut tree = FilteringResources::default();
899
900        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
901
902        assert_eq!(tree.controllers().collect_vec(), vec![&test_controller_a()]);
903    }
904
905    #[test]
906    fn controllers_ordered_alphabetically() {
907        let mut tree = FilteringResources::default();
908
909        let _ = tree.add(test_controller_b(), test_namespace_resource_a());
910        let _ = tree.add(test_controller_c(), test_namespace_resource_a());
911        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
912
913        assert_eq!(
914            tree.controllers().collect_vec(),
915            vec![&test_controller_a(), &test_controller_b(), &test_controller_c()]
916        );
917    }
918
919    #[test]
920    fn add_namespace_existing() {
921        let mut tree = FilteringResources::default();
922        let _ = tree.add(test_controller_a(), test_namespace_resource_a()); // already added...
923
924        let result = tree.add(test_controller_a(), test_namespace_resource_a());
925
926        assert_eq!(result, Some(test_namespace_resource_a()));
927        assert_eq!(tree.namespaces_fidl(test_controller_a()), Some(vec![test_namespace_a()]));
928    }
929
930    #[test]
931    fn add_namespace_multiple() {
932        let mut tree = FilteringResources::default();
933
934        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
935        let _ = tree.add(test_controller_a(), test_namespace_resource_b());
936
937        assert_eq!(
938            tree.namespaces_fidl(test_controller_a()),
939            Some(vec![test_namespace_a(), test_namespace_b(),])
940        );
941    }
942
943    #[test]
944    fn add_namespace_scoped_to_controllers() {
945        let mut tree = FilteringResources::default();
946
947        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
948        let _ = tree.add(test_controller_b(), test_namespace_resource_a());
949
950        assert_eq!(tree.namespaces_fidl(test_controller_a()), Some(vec![test_namespace_a()]));
951        assert_eq!(tree.namespaces_fidl(test_controller_b()), Some(vec![test_namespace_a()]));
952    }
953
954    #[test]
955    fn remove_namespace_not_existing() {
956        let mut tree = FilteringResources::default();
957
958        let result = tree.remove(test_controller_a(), &test_namespace_resource_a().id());
959
960        assert_eq!(result, None);
961        assert_eq!(tree.namespaces_fidl(test_controller_a()), None);
962    }
963
964    #[test]
965    fn remove_namespace_existing() {
966        let mut tree = FilteringResources::default();
967        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
968
969        let result = tree.remove(test_controller_a(), &test_namespace_resource_a().id());
970
971        assert_eq!(result, Some(test_namespace_resource_a()));
972        assert_eq!(tree.namespaces_fidl(test_controller_a()), None);
973    }
974
975    #[test]
976    fn remove_namespace_multiple() {
977        let mut tree = FilteringResources::default();
978        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
979        let _ = tree.add(test_controller_a(), test_namespace_resource_b());
980
981        let result = tree.remove(test_controller_a(), &test_namespace_resource_a().id());
982
983        assert_eq!(result, Some(test_namespace_resource_a()));
984        assert_eq!(tree.namespaces_fidl(test_controller_a()), Some(vec![test_namespace_b(),]));
985    }
986
987    #[test]
988    fn remove_namespace_scoped_to_controllers() {
989        let mut tree = FilteringResources::default();
990
991        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
992        let _ = tree.add(test_controller_b(), test_namespace_resource_a());
993
994        let result = tree.remove(test_controller_a(), &test_namespace_resource_a().id());
995
996        assert_eq!(result, Some(test_namespace_resource_a()));
997        assert_eq!(tree.namespaces_fidl(test_controller_a()), None);
998        assert_eq!(tree.namespaces_fidl(test_controller_b()), Some(vec![test_namespace_a()]));
999    }
1000
1001    #[test]
1002    fn remove_last_namespace_on_controller_removes_controller() {
1003        let mut tree = FilteringResources::default();
1004        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
1005        let _ = tree.add(test_controller_a(), test_namespace_resource_b());
1006
1007        let _ = tree.remove(test_controller_a(), &test_namespace_resource_a().id());
1008        assert_eq!(tree.controllers().collect_vec(), vec![&test_controller_a()]); // still 1 left
1009
1010        let _ = tree.remove(test_controller_a(), &test_namespace_resource_b().id());
1011        // now empty
1012        assert_eq!(tree.controllers().collect_vec(), vec![] as Vec<&ControllerId>);
1013    }
1014
1015    #[test]
1016    #[should_panic(expected = "Routine's namespace does not exist.")]
1017    fn add_routine_with_namespace_not_existing_panics() {
1018        let mut tree = FilteringResources::default();
1019
1020        let _ = tree.add(test_controller_a(), test_routine_resource_a());
1021    }
1022
1023    #[test]
1024    fn add_routine_with_namespace_existing() {
1025        let mut tree = FilteringResources::default();
1026        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
1027
1028        let result = tree.add(test_controller_a(), test_routine_resource_a());
1029
1030        assert_eq!(result, None);
1031        assert_eq!(
1032            tree.routines(test_controller_a(), test_namespace_a().id),
1033            Some(vec![test_routine_a()])
1034        );
1035    }
1036
1037    #[test]
1038    fn add_routine_with_routine_existing() {
1039        let mut tree = FilteringResources::default();
1040        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
1041        let _ = tree.add(test_controller_a(), test_routine_resource_a());
1042
1043        let result = tree.add(test_controller_a(), test_routine_resource_a());
1044
1045        assert_eq!(result, Some(test_routine_resource_a()));
1046        assert_eq!(
1047            tree.routines(test_controller_a(), test_namespace_a().id),
1048            Some(vec![test_routine_a()])
1049        );
1050    }
1051
1052    #[test]
1053    fn add_routine_multiple_different_namespaces() {
1054        let mut tree = FilteringResources::default();
1055        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
1056        let _ = tree.add(test_controller_a(), test_namespace_resource_b());
1057
1058        let _ = tree.add(test_controller_a(), test_routine_resource_a());
1059        let _ = tree.add(test_controller_a(), test_routine_resource_b());
1060
1061        assert_eq!(
1062            tree.routines(test_controller_a(), test_namespace_a().id),
1063            Some(vec![test_routine_a()])
1064        );
1065        assert_eq!(
1066            tree.routines(test_controller_a(), test_namespace_b().id),
1067            Some(vec![test_routine_b()])
1068        );
1069    }
1070
1071    #[test]
1072    fn add_routine_multiple_same_namespaces() {
1073        let mut tree = FilteringResources::default();
1074        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
1075
1076        let routine_a = test_routine_with_priority("test-routine-a".into(), 5);
1077        let routine_b = test_routine_with_priority("test-routine-b".into(), 4);
1078        let _ = tree.add(test_controller_a(), Resource::Routine(routine_a.clone()));
1079        let _ = tree.add(test_controller_a(), Resource::Routine(routine_b.clone()));
1080
1081        assert_eq!(
1082            tree.routines(test_controller_a(), test_namespace_a().id),
1083            Some(vec![routine_a, routine_b])
1084        );
1085    }
1086
1087    #[test]
1088    fn add_routines_generates_correct_nat_ip_data_structure() {
1089        let mut namespace = Namespace::new(test_namespace_a());
1090        let routine_priority = RoutinePriority(12);
1091
1092        let ip_ingress = test_routine_with_routine_type(
1093            "routine-ip-ingress".to_string(),
1094            RoutineType::Ip(Some(InstalledIpRoutine {
1095                priority: routine_priority.0,
1096                hook: IpHook::Ingress,
1097            })),
1098        );
1099        let ip_local_ingress = test_routine_with_routine_type(
1100            "routine-ip-local-ingress".to_string(),
1101            RoutineType::Ip(Some(InstalledIpRoutine {
1102                priority: routine_priority.0,
1103                hook: IpHook::LocalIngress,
1104            })),
1105        );
1106        let ip_egress = test_routine_with_routine_type(
1107            "routine-ip-egress".to_string(),
1108            RoutineType::Ip(Some(InstalledIpRoutine {
1109                priority: routine_priority.0,
1110                hook: IpHook::Egress,
1111            })),
1112        );
1113        let ip_local_egress = test_routine_with_routine_type(
1114            "routine-ip-local-egress".to_string(),
1115            RoutineType::Ip(Some(InstalledIpRoutine {
1116                priority: routine_priority.0,
1117                hook: IpHook::LocalEgress,
1118            })),
1119        );
1120        let ip_forwarding = test_routine_with_routine_type(
1121            "routine-ip-forwarding".to_string(),
1122            RoutineType::Ip(Some(InstalledIpRoutine {
1123                priority: routine_priority.0,
1124                hook: IpHook::Forwarding,
1125            })),
1126        );
1127        let ip_uninstalled = test_routine_with_routine_type(
1128            "routine-ip-uninstalled".to_string(),
1129            RoutineType::Ip(None),
1130        );
1131        let nat_ingress = test_routine_with_routine_type(
1132            "routine-nat-ingress".to_string(),
1133            RoutineType::Nat(Some(InstalledNatRoutine {
1134                priority: routine_priority.0,
1135                hook: NatHook::Ingress,
1136            })),
1137        );
1138        let nat_local_ingress = test_routine_with_routine_type(
1139            "routine-nat-local-ingress".to_string(),
1140            RoutineType::Nat(Some(InstalledNatRoutine {
1141                priority: routine_priority.0,
1142                hook: NatHook::LocalIngress,
1143            })),
1144        );
1145        let nat_egress = test_routine_with_routine_type(
1146            "routine-nat-egress".to_string(),
1147            RoutineType::Nat(Some(InstalledNatRoutine {
1148                priority: routine_priority.0,
1149                hook: NatHook::Egress,
1150            })),
1151        );
1152        let nat_local_egress = test_routine_with_routine_type(
1153            "routine-nat-local-egress".to_string(),
1154            RoutineType::Nat(Some(InstalledNatRoutine {
1155                priority: routine_priority.0,
1156                hook: NatHook::LocalEgress,
1157            })),
1158        );
1159        let nat_uninstalled = test_routine_with_routine_type(
1160            "routine-nat-uninstalled".to_string(),
1161            RoutineType::Nat(None),
1162        );
1163
1164        let _ = namespace.add_routine(ip_ingress.clone());
1165        let _ = namespace.add_routine(ip_local_ingress.clone());
1166        let _ = namespace.add_routine(ip_egress.clone());
1167        let _ = namespace.add_routine(ip_local_egress.clone());
1168        let _ = namespace.add_routine(ip_forwarding.clone());
1169        let _ = namespace.add_routine(ip_uninstalled.clone());
1170        let _ = namespace.add_routine(nat_ingress.clone());
1171        let _ = namespace.add_routine(nat_local_ingress.clone());
1172        let _ = namespace.add_routine(nat_egress.clone());
1173        let _ = namespace.add_routine(nat_local_egress.clone());
1174        let _ = namespace.add_routine(nat_uninstalled.clone());
1175
1176        assert_eq!(
1177            namespace.routines(),
1178            (
1179                IpRoutines {
1180                    ingress: BTreeMap::from([(
1181                        routine_priority.clone(),
1182                        vec![Routine::new(ip_ingress)]
1183                    )]),
1184                    local_ingress: BTreeMap::from([(
1185                        routine_priority.clone(),
1186                        vec![Routine::new(ip_local_ingress)]
1187                    )]),
1188                    forwarding: BTreeMap::from([(
1189                        routine_priority.clone(),
1190                        vec![Routine::new(ip_forwarding)]
1191                    )]),
1192                    local_egress: BTreeMap::from([(
1193                        routine_priority.clone(),
1194                        vec![Routine::new(ip_local_egress)]
1195                    )]),
1196                    egress: BTreeMap::from([(
1197                        routine_priority.clone(),
1198                        vec![Routine::new(ip_egress)]
1199                    )]),
1200                    uninstalled: vec![Routine::new(ip_uninstalled)],
1201                },
1202                NatRoutines {
1203                    ingress: BTreeMap::from([(
1204                        routine_priority.clone(),
1205                        vec![Routine::new(nat_ingress)]
1206                    )]),
1207                    local_ingress: BTreeMap::from([(
1208                        routine_priority.clone(),
1209                        vec![Routine::new(nat_local_ingress)]
1210                    )]),
1211                    local_egress: BTreeMap::from([(
1212                        routine_priority.clone(),
1213                        vec![Routine::new(nat_local_egress)]
1214                    )]),
1215                    egress: BTreeMap::from([(
1216                        routine_priority.clone(),
1217                        vec![Routine::new(nat_egress)]
1218                    )]),
1219                    uninstalled: vec![Routine::new(nat_uninstalled)],
1220                },
1221            )
1222        );
1223    }
1224
1225    #[test]
1226    fn add_routines_orders_by_priority() {
1227        let mut namespace = Namespace::new(test_namespace_a());
1228
1229        let routine_first = test_routine_with_priority("routine-a".to_string(), 12);
1230        let routine_second = test_routine_with_priority("routine-b".to_string(), 13);
1231        let routine_third = test_routine_with_priority("routine-c".to_string(), 14);
1232
1233        let _ = namespace.add_routine(routine_third.clone());
1234        let _ = namespace.add_routine(routine_first.clone());
1235        let _ = namespace.add_routine(routine_second.clone());
1236
1237        let routines: Vec<fidl_fuchsia_net_filter_ext::Routine> = namespace
1238            .routines()
1239            .0
1240            .ingress
1241            .values()
1242            .flatten()
1243            .map(|routine| routine.clone().into())
1244            .collect();
1245
1246        assert_eq!(routines, vec![routine_first, routine_second, routine_third]);
1247    }
1248
1249    #[test]
1250    fn add_routines_handles_duplicate_priority() {
1251        const PRIORITY: i32 = 14;
1252        let mut namespace = Namespace::new(test_namespace_a());
1253
1254        let routine_first = test_routine_with_priority("routine-a".to_string(), PRIORITY);
1255        let routine_second = test_routine_with_priority("routine-b".to_string(), PRIORITY);
1256
1257        let _ = namespace.add_routine(routine_first.clone());
1258        let _ = namespace.add_routine(routine_second.clone());
1259
1260        let routines: Vec<fidl_fuchsia_net_filter_ext::Routine> = namespace
1261            .routines()
1262            .0
1263            .ingress
1264            .values()
1265            .flatten()
1266            // Sort for deterministic testing.
1267            .sorted_by(|routine_a, routine_b| routine_a.id.name.cmp(&routine_b.id.name))
1268            .map(|routines| routines.clone().into())
1269            .collect();
1270
1271        assert_eq!(routines, vec![routine_first, routine_second]);
1272    }
1273
1274    #[test]
1275    fn remove_routine_not_existing() {
1276        let mut tree = FilteringResources::default();
1277
1278        let result = tree.remove(test_controller_a(), &test_routine_resource_a().id());
1279
1280        assert_eq!(result, None);
1281        assert_eq!(tree.routines(test_controller_a(), test_namespace_a().id), None);
1282    }
1283
1284    #[test]
1285    fn remove_routine_existing() {
1286        let mut tree = FilteringResources::default();
1287        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
1288        let _ = tree.add(test_controller_a(), test_routine_resource_a());
1289
1290        let result = tree.remove(test_controller_a(), &test_routine_resource_a().id());
1291
1292        assert_eq!(result, Some(test_routine_resource_a()));
1293        assert_eq!(tree.routines(test_controller_a(), test_namespace_a().id), Some(vec![]));
1294    }
1295
1296    #[test]
1297    #[should_panic(expected = "Rule's namespace does not exist.")]
1298    fn add_rule_with_namespace_not_existing_panics() {
1299        let mut tree = FilteringResources::default();
1300
1301        let _ = tree.add(test_controller_a(), test_rule_resource_a());
1302    }
1303
1304    #[test]
1305    #[should_panic(expected = "Rule's routine does not exist.")]
1306    fn add_rule_with_routine_not_existing_panics() {
1307        let mut tree = FilteringResources::default();
1308        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
1309
1310        let _ = tree.add(test_controller_a(), test_rule_resource_a());
1311    }
1312
1313    #[test]
1314    fn add_rule_with_namespace_and_routine_existing() {
1315        let mut tree = FilteringResources::default();
1316        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
1317        let _ = tree.add(test_controller_a(), test_routine_resource_a());
1318
1319        let result = tree.add(test_controller_a(), test_rule_resource_a());
1320
1321        assert_eq!(result, None);
1322        assert_eq!(
1323            tree.rules(test_controller_a(), test_namespace_a().id, test_routine_a().id),
1324            Some(vec![&test_rule_a()])
1325        );
1326    }
1327
1328    #[test]
1329    fn add_rule_with_routine_existing() {
1330        let mut tree = FilteringResources::default();
1331        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
1332        let _ = tree.add(test_controller_a(), test_routine_resource_a());
1333        let _ = tree.add(test_controller_a(), test_rule_resource_a());
1334
1335        let result = tree.add(test_controller_a(), test_rule_resource_a());
1336
1337        assert_eq!(result, Some(test_rule_resource_a()));
1338        assert_eq!(
1339            tree.rules(test_controller_a(), test_namespace_a().id, test_routine_a().id),
1340            Some(vec![&test_rule_a()])
1341        );
1342    }
1343
1344    #[test]
1345    fn add_rules_orders_by_index() {
1346        let mut routine = Routine::new(test_routine_a());
1347
1348        let rule_first = test_rule_a_with_index(12);
1349        let rule_second = test_rule_a_with_index(13);
1350        let rule_third = test_rule_a_with_index(14);
1351
1352        let _ = routine.add_rule(rule_third.clone());
1353        let _ = routine.add_rule(rule_first.clone());
1354        let _ = routine.add_rule(rule_second.clone());
1355
1356        let rules: Vec<&Rule> = routine.rules().collect_vec();
1357
1358        assert_eq!(rules, vec![&rule_first, &rule_second, &rule_third]);
1359    }
1360
1361    #[test]
1362    fn remove_rule_not_existing() {
1363        let mut tree = FilteringResources::default();
1364
1365        let result = tree.remove(test_controller_a(), &test_rule_resource_a().id());
1366
1367        assert_eq!(result, None);
1368        assert_eq!(
1369            tree.rules(test_controller_a(), test_namespace_a().id, test_routine_a().id),
1370            None
1371        );
1372    }
1373
1374    #[test]
1375    fn remove_rule_existing() {
1376        let mut tree = FilteringResources::default();
1377        let _ = tree.add(test_controller_a(), test_namespace_resource_a());
1378        let _ = tree.add(test_controller_a(), test_routine_resource_a());
1379        let _ = tree.add(test_controller_a(), test_rule_resource_a());
1380
1381        let result = tree.remove(test_controller_a(), &test_rule_resource_a().id());
1382
1383        assert_eq!(result, Some(test_rule_resource_a()));
1384        assert_eq!(
1385            tree.rules(test_controller_a(), test_namespace_a().id, test_routine_a().id),
1386            Some(vec![])
1387        );
1388    }
1389
1390    #[fuchsia_async::run_singlethreaded(test)]
1391    async fn test_do_filter() {
1392        const CONTROLLER_A: &str = "controller a";
1393        const CONTROLLER_B: &str = "controller b";
1394        const NAMESPACE_A: &str = "namespace a";
1395        const NAMESPACE_B: &str = "namespace b";
1396        const NAMESPACE_C: &str = "namespace c";
1397        const ROUTINE_A: &str = "routine a";
1398        const ROUTINE_B: &str = "routine b";
1399        const ROUTINE_C: &str = "routine c";
1400        const ROUTINE_D: &str = "routine d";
1401        const ROUTINE_E: &str = "routine e";
1402        const INDEX_FIRST: u32 = 11;
1403        const INDEX_SECOND: u32 = 12;
1404        const INDEX_THIRD: u32 = 13;
1405        const INDEX_FOURTH: u32 = 14;
1406
1407        let mut output = Vec::new();
1408        let (filter, mut requests) =
1409            fidl::endpoints::create_proxy_and_stream::<fnet_filter::StateMarker>();
1410
1411        let connector = TestConnector { filter: Some(filter), ..Default::default() };
1412        let op = do_filter(
1413            &mut output,
1414            opts::filter::FilterEnum::List(opts::filter::List {}),
1415            &connector,
1416        );
1417        let op_succeeds = async move {
1418            let req = requests
1419                .try_next()
1420                .await
1421                .expect("receiving request")
1422                .expect("request stream should not have ended");
1423            let (_options, server_end, _state_control_handle) =
1424                req.into_get_watcher().expect("request should be of type GetWatcher");
1425
1426            let mut watcher_request_stream = server_end.into_stream();
1427
1428            let events = [
1429                // controller a, namespace a
1430                fnet_filter_ext::Event::Existing(
1431                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1432                    fnet_filter_ext::Resource::Namespace(fnet_filter_ext::Namespace {
1433                        id: fnet_filter_ext::NamespaceId(NAMESPACE_A.to_string()),
1434                        domain: fnet_filter_ext::Domain::Ipv4,
1435                    }),
1436                )
1437                .into(),
1438                // controller a, namespace b
1439                fnet_filter_ext::Event::Existing(
1440                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1441                    fnet_filter_ext::Resource::Namespace(fnet_filter_ext::Namespace {
1442                        id: fnet_filter_ext::NamespaceId(NAMESPACE_B.to_string()),
1443                        domain: fnet_filter_ext::Domain::Ipv4,
1444                    }),
1445                )
1446                .into(),
1447                // controller b, namespace c
1448                fnet_filter_ext::Event::Existing(
1449                    fnet_filter_ext::ControllerId(CONTROLLER_B.to_string()),
1450                    fnet_filter_ext::Resource::Namespace(fnet_filter_ext::Namespace {
1451                        id: fnet_filter_ext::NamespaceId(NAMESPACE_C.to_string()),
1452                        domain: fnet_filter_ext::Domain::Ipv4,
1453                    }),
1454                )
1455                .into(),
1456                // controller a, namespace a, routine a (IP)
1457                fnet_filter_ext::Event::Existing(
1458                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1459                    fnet_filter_ext::Resource::Routine(fnet_filter_ext::Routine {
1460                        id: fnet_filter_ext::RoutineId {
1461                            namespace: fnet_filter_ext::NamespaceId(NAMESPACE_A.to_string()),
1462                            name: ROUTINE_A.to_string(),
1463                        },
1464                        routine_type: fnet_filter_ext::RoutineType::Ip(Some(InstalledIpRoutine {
1465                            priority: 2,
1466                            hook: fnet_filter_ext::IpHook::Egress,
1467                        })),
1468                    }),
1469                )
1470                .into(),
1471                // controller a, namespace a, routine b (IP)
1472                fnet_filter_ext::Event::Existing(
1473                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1474                    fnet_filter_ext::Resource::Routine(fnet_filter_ext::Routine {
1475                        id: fnet_filter_ext::RoutineId {
1476                            namespace: fnet_filter_ext::NamespaceId(NAMESPACE_A.to_string()),
1477                            name: ROUTINE_B.to_string(),
1478                        },
1479                        routine_type: fnet_filter_ext::RoutineType::Ip(Some(InstalledIpRoutine {
1480                            priority: 1,
1481                            hook: fnet_filter_ext::IpHook::Egress,
1482                        })),
1483                    }),
1484                )
1485                .into(),
1486                // controller a, namespace b, routine c (IP)
1487                fnet_filter_ext::Event::Existing(
1488                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1489                    fnet_filter_ext::Resource::Routine(fnet_filter_ext::Routine {
1490                        id: fnet_filter_ext::RoutineId {
1491                            namespace: fnet_filter_ext::NamespaceId(NAMESPACE_B.to_string()),
1492                            name: ROUTINE_C.to_string(),
1493                        },
1494                        routine_type: fnet_filter_ext::RoutineType::Ip(None),
1495                    }),
1496                )
1497                .into(),
1498                // controller a, namespace a, routine d (NAT)
1499                fnet_filter_ext::Event::Existing(
1500                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1501                    fnet_filter_ext::Resource::Routine(fnet_filter_ext::Routine {
1502                        id: fnet_filter_ext::RoutineId {
1503                            namespace: fnet_filter_ext::NamespaceId(NAMESPACE_A.to_string()),
1504                            name: ROUTINE_D.to_string(),
1505                        },
1506                        routine_type: fnet_filter_ext::RoutineType::Nat(Some(
1507                            InstalledNatRoutine {
1508                                priority: 1,
1509                                hook: fnet_filter_ext::NatHook::Egress,
1510                            },
1511                        )),
1512                    }),
1513                )
1514                .into(),
1515                // controller a, namespace b, routine e (NAT)
1516                fnet_filter_ext::Event::Existing(
1517                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1518                    fnet_filter_ext::Resource::Routine(fnet_filter_ext::Routine {
1519                        id: fnet_filter_ext::RoutineId {
1520                            namespace: fnet_filter_ext::NamespaceId(NAMESPACE_B.to_string()),
1521                            name: ROUTINE_E.to_string(),
1522                        },
1523                        routine_type: fnet_filter_ext::RoutineType::Nat(None),
1524                    }),
1525                )
1526                .into(),
1527                // controller a, namespace a, routine a, rule #1 (11)
1528                fnet_filter_ext::Event::Existing(
1529                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1530                    fnet_filter_ext::Resource::Rule(fnet_filter_ext::Rule {
1531                        id: fnet_filter_ext::RuleId {
1532                            routine: fnet_filter_ext::RoutineId {
1533                                namespace: fnet_filter_ext::NamespaceId(NAMESPACE_A.to_string()),
1534                                name: ROUTINE_A.to_string(),
1535                            },
1536                            index: INDEX_FIRST,
1537                        },
1538                        matchers: Default::default(),
1539                        action: fnet_filter_ext::Action::Accept,
1540                    }),
1541                )
1542                .into(),
1543                // controller a, namespace a, routine a, rule #2 (12)
1544                fnet_filter_ext::Event::Existing(
1545                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1546                    fnet_filter_ext::Resource::Rule(fnet_filter_ext::Rule {
1547                        id: fnet_filter_ext::RuleId {
1548                            routine: fnet_filter_ext::RoutineId {
1549                                namespace: fnet_filter_ext::NamespaceId(NAMESPACE_A.to_string()),
1550                                name: ROUTINE_A.to_string(),
1551                            },
1552                            index: INDEX_SECOND,
1553                        },
1554                        matchers: Default::default(),
1555                        action: fnet_filter_ext::Action::Accept,
1556                    }),
1557                )
1558                .into(),
1559                // controller a, namespace a, routine b, rule #3 (13)
1560                fnet_filter_ext::Event::Existing(
1561                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1562                    fnet_filter_ext::Resource::Rule(fnet_filter_ext::Rule {
1563                        id: fnet_filter_ext::RuleId {
1564                            routine: fnet_filter_ext::RoutineId {
1565                                namespace: fnet_filter_ext::NamespaceId(NAMESPACE_A.to_string()),
1566                                name: ROUTINE_B.to_string(),
1567                            },
1568                            index: INDEX_THIRD,
1569                        },
1570                        matchers: Default::default(),
1571                        action: fnet_filter_ext::Action::Accept,
1572                    }),
1573                )
1574                .into(),
1575                // controller a, namespace b, routine c, rule #1 (11)
1576                fnet_filter_ext::Event::Existing(
1577                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1578                    fnet_filter_ext::Resource::Rule(fnet_filter_ext::Rule {
1579                        id: fnet_filter_ext::RuleId {
1580                            routine: fnet_filter_ext::RoutineId {
1581                                namespace: fnet_filter_ext::NamespaceId(NAMESPACE_B.to_string()),
1582                                name: ROUTINE_C.to_string(),
1583                            },
1584                            index: INDEX_FIRST,
1585                        },
1586                        matchers: Default::default(),
1587                        action: fnet_filter_ext::Action::Accept,
1588                    }),
1589                )
1590                .into(),
1591                // Test Matcher output: InterfaceMatcher
1592                fnet_filter_ext::Event::Existing(
1593                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1594                    fnet_filter_ext::Resource::Rule(fnet_filter_ext::Rule {
1595                        id: fnet_filter_ext::RuleId {
1596                            routine: fnet_filter_ext::RoutineId {
1597                                namespace: fnet_filter_ext::NamespaceId(NAMESPACE_B.to_string()),
1598                                name: ROUTINE_E.to_string(),
1599                            },
1600                            index: INDEX_FIRST,
1601                        },
1602                        matchers: Matchers {
1603                            in_interface: Some(fnet_filter_ext::InterfaceMatcher::Id(
1604                                NonZeroU64::new(23).expect("Failed to create NonZeroU64"),
1605                            )),
1606                            out_interface: None,
1607                            src_addr: None,
1608                            dst_addr: None,
1609                            transport_protocol: None,
1610                        },
1611                        action: fnet_filter_ext::Action::Accept,
1612                    }),
1613                )
1614                .into(),
1615                // Test Matcher output: AddressMatcher
1616                fnet_filter_ext::Event::Existing(
1617                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1618                    fnet_filter_ext::Resource::Rule(fnet_filter_ext::Rule {
1619                        id: fnet_filter_ext::RuleId {
1620                            routine: fnet_filter_ext::RoutineId {
1621                                namespace: fnet_filter_ext::NamespaceId(NAMESPACE_B.to_string()),
1622                                name: ROUTINE_E.to_string(),
1623                            },
1624                            index: INDEX_SECOND,
1625                        },
1626                        matchers: Matchers {
1627                            in_interface: None,
1628                            out_interface: None,
1629                            src_addr: Some(fnet_filter_ext::AddressMatcher {
1630                                matcher: fnet_filter_ext::AddressMatcherType::Subnet(
1631                                    fidl_subnet!("192.168.0.1/32").try_into().unwrap(),
1632                                ),
1633                                invert: false,
1634                            }),
1635                            dst_addr: None,
1636                            transport_protocol: None,
1637                        },
1638                        action: fnet_filter_ext::Action::Accept,
1639                    }),
1640                )
1641                .into(),
1642                // Test Matcher output: TransportProtocolMatcher simple
1643                fnet_filter_ext::Event::Existing(
1644                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1645                    fnet_filter_ext::Resource::Rule(fnet_filter_ext::Rule {
1646                        id: fnet_filter_ext::RuleId {
1647                            routine: fnet_filter_ext::RoutineId {
1648                                namespace: fnet_filter_ext::NamespaceId(NAMESPACE_B.to_string()),
1649                                name: ROUTINE_E.to_string(),
1650                            },
1651                            index: INDEX_THIRD,
1652                        },
1653                        matchers: Matchers {
1654                            in_interface: None,
1655                            out_interface: None,
1656                            src_addr: None,
1657                            dst_addr: None,
1658                            transport_protocol: Some(
1659                                fnet_filter_ext::TransportProtocolMatcher::Icmpv6,
1660                            ),
1661                        },
1662                        action: fnet_filter_ext::Action::Accept,
1663                    }),
1664                )
1665                .into(),
1666                // Test Matcher output: TransportProtocolMatcher with src_port and dst_port
1667                fnet_filter_ext::Event::Existing(
1668                    fnet_filter_ext::ControllerId(CONTROLLER_A.to_string()),
1669                    fnet_filter_ext::Resource::Rule(fnet_filter_ext::Rule {
1670                        id: fnet_filter_ext::RuleId {
1671                            routine: fnet_filter_ext::RoutineId {
1672                                namespace: fnet_filter_ext::NamespaceId(NAMESPACE_B.to_string()),
1673                                name: ROUTINE_E.to_string(),
1674                            },
1675                            index: INDEX_FOURTH,
1676                        },
1677                        matchers: Matchers {
1678                            in_interface: None,
1679                            out_interface: None,
1680                            src_addr: None,
1681                            dst_addr: None,
1682                            transport_protocol: Some(
1683                                fnet_filter_ext::TransportProtocolMatcher::Tcp {
1684                                    src_port: None,
1685                                    dst_port: Some(
1686                                        fnet_filter_ext::PortMatcher::new(41, 42, true)
1687                                            .expect("Failed to create PortMatcher"),
1688                                    ),
1689                                },
1690                            ),
1691                        },
1692                        action: fnet_filter_ext::Action::Accept,
1693                    }),
1694                )
1695                .into(),
1696                fnet_filter_ext::Event::Idle.into(),
1697            ];
1698
1699            let () = watcher_request_stream
1700                .try_next()
1701                .await
1702                .expect("watcher watch FIDL error")
1703                .expect("watcher request stream should not have ended")
1704                .into_watch()
1705                .expect("request should be of type Watch")
1706                .send(&events)
1707                .expect("responder.send should succeed");
1708
1709            assert_matches!(
1710                watcher_request_stream.try_next().await.expect("watcher watch FIDL error"),
1711                None,
1712                "remaining watcher request stream should be empty because client should close \
1713                watcher channel after observing existing resources"
1714            );
1715
1716            Ok(())
1717        };
1718        let ((), ()) = futures::future::try_join(op, op_succeeds)
1719            .await
1720            .expect("filter server command should succeed");
1721
1722        const WANT_OUTPUT: &str = r#"controller: "controller a" {
1723    namespace: "namespace a" {
1724        domain: Ipv4
1725        installed IP routines {
1726            EGRESS {
1727                1. routine b {
1728                    13. Matchers -> Accept
1729                }
1730                2. routine a {
1731                    11. Matchers -> Accept
1732                    12. Matchers -> Accept
1733                }
1734            }
1735        }
1736        installed NAT routines {
1737            EGRESS {
1738                1. routine d {
1739                }
1740            }
1741        }
1742    }
1743    namespace: "namespace b" {
1744        domain: Ipv4
1745        uninstalled IP routines {
1746            routine c {
1747                11. Matchers -> Accept
1748            }
1749        }
1750        uninstalled NAT routines {
1751            routine e {
1752                11. Matchers { in_interface: Id(23) } -> Accept
1753                12. Matchers { src_addr: AddressMatcher { matcher: 192.168.0.1/32, invert: false } } -> Accept
1754                13. Matchers { transport_protocol: Icmpv6 } -> Accept
1755                14. Matchers { transport_protocol: Tcp { dst_port: PortMatcher { range: 41..=42, invert: true } } } -> Accept
1756            }
1757        }
1758    }
1759}
1760controller: "controller b" {
1761    namespace: "namespace c" {
1762        domain: Ipv4
1763    }
1764}
1765"#;
1766        let got_output = std::str::from_utf8(&output).unwrap();
1767        pretty_assertions::assert_eq!(got_output, WANT_OUTPUT,);
1768    }
1769}