1use 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#[derive(Debug, Default)]
29pub struct FilteringResources {
30 controllers: BTreeMap<ControllerId, BTreeMap<NamespaceId, Namespace>>,
31}
32
33impl FilteringResources {
34 pub fn controllers(&self) -> impl Iterator<Item = &ControllerId> {
36 self.controllers.keys()
37 }
38
39 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 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 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 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 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()); 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()]); let _ = tree.remove(test_controller_a(), &test_namespace_resource_b().id());
1011 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 .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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}