1use pest::Parser;
6use pest::iterators::Pair;
7
8use {
9 fidl_fuchsia_net as fnet, fidl_fuchsia_net_filter_ext as filter_ext,
10 fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext,
11 fidl_fuchsia_net_matchers_ext as fnet_matchers_ext,
12};
13
14use crate::grammar::{Error, FilterRuleParser, InvalidReason, Rule};
15use crate::util;
16
17fn parse_action(pair: Pair<'_, Rule>) -> filter_ext::Action {
18 assert_eq!(pair.as_rule(), Rule::action);
19 match pair.into_inner().next().unwrap().as_rule() {
20 Rule::pass => filter_ext::Action::Accept,
21 Rule::drop => filter_ext::Action::Drop,
22 Rule::dropreset => todo!("not yet supported in the filter2 API"),
26 _ => unreachable!("action must be one of (pass|drop|dropreset)"),
27 }
28}
29
30#[derive(Copy, Clone, Debug, PartialEq)]
33pub enum Direction {
34 LocalIngress,
35 LocalEgress,
36}
37
38fn parse_direction(pair: Pair<'_, Rule>) -> Direction {
39 assert_eq!(pair.as_rule(), Rule::direction);
40 match pair.into_inner().next().unwrap().as_rule() {
41 Rule::incoming => Direction::LocalIngress,
42 Rule::outgoing => Direction::LocalEgress,
43 _ => unreachable!("direction must be one of (in|out)"),
44 }
45}
46
47enum TransportProtocol {
50 Tcp,
51 Udp,
52 Icmp,
53}
54
55fn parse_proto(pair: Pair<'_, Rule>) -> Option<TransportProtocol> {
56 assert_eq!(pair.as_rule(), Rule::proto);
57 pair.into_inner().next().map(|pair| match pair.as_rule() {
58 Rule::tcp => TransportProtocol::Tcp,
59 Rule::udp => TransportProtocol::Udp,
60 Rule::icmp => TransportProtocol::Icmp,
61 _ => unreachable!("protocol must be one of (tcp|udp|icmp)"),
62 })
63}
64
65fn parse_devclass(pair: Pair<'_, Rule>) -> Option<fnet_interfaces_ext::PortClass> {
66 assert_eq!(pair.as_rule(), Rule::devclass);
67 pair.into_inner().next().map(|pair| match pair.as_rule() {
68 Rule::virt => fnet_interfaces_ext::PortClass::Virtual,
69 Rule::ethernet => fnet_interfaces_ext::PortClass::Ethernet,
70 Rule::wlan => fnet_interfaces_ext::PortClass::WlanClient,
71 Rule::ppp => fnet_interfaces_ext::PortClass::Ppp,
72 Rule::bridge => fnet_interfaces_ext::PortClass::Bridge,
73 Rule::ap => fnet_interfaces_ext::PortClass::WlanAp,
74 Rule::lowpan => fnet_interfaces_ext::PortClass::Lowpan,
75 _ => unreachable!("devclass must be one of (virt|ethernet|wlan|ppp|bridge|ap|lowpan)"),
76 })
77}
78
79fn parse_src(
80 pair: Pair<'_, Rule>,
81) -> Result<(Option<fnet_matchers_ext::Address>, Option<fnet_matchers_ext::Port>), Error> {
82 assert_eq!(pair.as_rule(), Rule::src);
83 parse_src_or_dst(pair)
84}
85
86fn parse_dst(
87 pair: Pair<'_, Rule>,
88) -> Result<(Option<fnet_matchers_ext::Address>, Option<fnet_matchers_ext::Port>), Error> {
89 assert_eq!(pair.as_rule(), Rule::dst);
90 parse_src_or_dst(pair)
91}
92
93fn parse_src_or_dst(
94 pair: Pair<'_, Rule>,
95) -> Result<(Option<fnet_matchers_ext::Address>, Option<fnet_matchers_ext::Port>), Error> {
96 let mut inner = pair.into_inner();
97 match inner.next() {
98 Some(pair) => match pair.as_rule() {
99 Rule::invertible_subnet => {
100 let (subnet, invert_match) = util::parse_invertible_subnet(pair)?;
101 let port = match inner.next() {
102 Some(pair) => Some(parse_port_range(pair)?),
103 None => None,
104 };
105 Ok((
106 Some(fnet_matchers_ext::Address {
107 matcher: fnet_matchers_ext::AddressMatcherType::Subnet(
108 fnet_matchers_ext::Subnet::try_from(subnet)
109 .map_err(|err| Error::Fidl(err.into()))?,
110 ),
111 invert: invert_match,
112 }),
113 port,
114 ))
115 }
116 Rule::port_range => Ok((None, Some(parse_port_range(pair)?))),
117 _ => unreachable!("src or dst must be either an invertible subnet or port range"),
118 },
119 None => Ok((None, None)),
120 }
121}
122
123fn parse_port_range(pair: Pair<'_, Rule>) -> Result<fnet_matchers_ext::Port, Error> {
124 assert_eq!(pair.as_rule(), Rule::port_range);
125 let mut inner = pair.into_inner();
126 let pair = inner.next().unwrap();
127 match pair.as_rule() {
128 Rule::port => {
129 let port_num = util::parse_port_num(inner.next().unwrap())?;
130 fnet_matchers_ext::Port::new(port_num, port_num, false).map_err(|err| match err {
131 fnet_matchers_ext::PortError::InvalidPortRange => {
132 Error::Invalid(InvalidReason::InvalidPortRange)
133 }
134 })
135 }
136 Rule::range => {
137 let port_start = util::parse_port_num(inner.next().unwrap())?;
138 let port_end = util::parse_port_num(inner.next().unwrap())?;
139 fnet_matchers_ext::Port::new(port_start, port_end, false).map_err(|err| match err {
140 fnet_matchers_ext::PortError::InvalidPortRange => {
141 Error::Invalid(InvalidReason::InvalidPortRange)
142 }
143 })
144 }
145 _ => unreachable!("port range must be either a single port, or a port range"),
146 }
147}
148
149fn parse_rule(
150 pair: Pair<'_, Rule>,
151 routines: &FilterRoutines,
152 index: usize,
153) -> Result<filter_ext::Rule, Error> {
154 assert_eq!(pair.as_rule(), Rule::rule);
155 let mut pairs = pair.into_inner();
156
157 let action = parse_action(pairs.next().unwrap());
158 let direction = parse_direction(pairs.next().unwrap());
159 let proto = parse_proto(pairs.next().unwrap());
160 let port_class = parse_devclass(pairs.next().unwrap());
161 let mut in_interface = None;
162 let mut out_interface = None;
163 let routine_id = match direction {
164 Direction::LocalIngress => {
165 let Some(ref routine_id) = routines.local_ingress else {
167 return Err(Error::RoutineNotProvided(direction));
168 };
169 in_interface = port_class.map(|class| fnet_matchers_ext::Interface::PortClass(class));
170 routine_id
171 }
172 Direction::LocalEgress => {
173 let Some(ref routine_id) = routines.local_egress else {
175 return Err(Error::RoutineNotProvided(direction));
176 };
177 out_interface = port_class.map(|class| fnet_matchers_ext::Interface::PortClass(class));
178 routine_id
179 }
180 };
181 let (src_addr, src_port) = parse_src(pairs.next().unwrap())?;
182 let (dst_addr, dst_port) = parse_dst(pairs.next().unwrap())?;
183 let transport_protocol = proto.map(|proto| match proto {
184 TransportProtocol::Tcp => fnet_matchers_ext::TransportProtocol::Tcp { src_port, dst_port },
185 TransportProtocol::Udp => fnet_matchers_ext::TransportProtocol::Udp { src_port, dst_port },
186 TransportProtocol::Icmp => fnet_matchers_ext::TransportProtocol::Icmp,
187 });
188
189 Ok(filter_ext::Rule {
190 id: filter_ext::RuleId { routine: routine_id.clone(), index: index as u32 },
191 matchers: filter_ext::Matchers {
192 in_interface,
193 out_interface,
194 src_addr,
195 dst_addr,
196 transport_protocol,
197 ..Default::default()
198 },
199 action,
200 })
201}
202
203#[derive(Debug, Default)]
207pub struct FilterRoutines {
208 pub local_ingress: Option<filter_ext::RoutineId>,
209 pub local_egress: Option<filter_ext::RoutineId>,
210}
211
212#[derive(Debug, Default)]
216pub struct NatRoutines {}
217
218fn validate_rule(rule: &filter_ext::Rule) -> Result<(), Error> {
219 if let (Some(src_subnet), Some(dst_subnet)) = (&rule.matchers.src_addr, &rule.matchers.dst_addr)
220 {
221 if let (
222 fnet_matchers_ext::AddressMatcherType::Subnet(src_subnet),
223 fnet_matchers_ext::AddressMatcherType::Subnet(dst_subnet),
224 ) = (&src_subnet.matcher, &dst_subnet.matcher)
225 {
226 if !util::ip_version_eq(
227 &fnet::Subnet::from(*src_subnet).addr,
228 &fnet::Subnet::from(*dst_subnet).addr,
229 ) {
230 return Err(Error::Invalid(InvalidReason::MixedIPVersions));
231 }
232 }
233 }
234
235 Ok(())
236}
237
238pub fn parse_str_to_rules(
239 line: &str,
240 routines: &FilterRoutines,
241) -> Result<Vec<filter_ext::Rule>, Error> {
242 let mut pairs =
243 FilterRuleParser::parse(Rule::rules, &line).map_err(|err| Error::Pest(Box::new(err)))?;
244 let mut rules = Vec::new();
245 for (index, filter_rule) in pairs.next().unwrap().into_inner().into_iter().enumerate() {
246 match filter_rule.as_rule() {
247 Rule::rule => {
248 let rule = parse_rule(filter_rule, &routines, index)?;
249 let () = validate_rule(&rule)?;
250 rules.push(rule);
251 }
252 Rule::EOI => (),
253 _ => unreachable!("rule must only have a rule case"),
254 }
255 }
256 Ok(rules)
257}
258
259pub fn parse_str_to_nat_rules(
260 _line: &str,
261 _routines: &NatRoutines,
262) -> Result<Vec<filter_ext::Rule>, Error> {
263 todo!("not yet supported in the filter2 API")
266}
267
268pub fn parse_str_to_rdr_rules(
269 _line: &str,
270 _routines: &NatRoutines,
271) -> Result<Vec<filter_ext::Rule>, Error> {
272 todo!("not yet supported in the filter2 API")
275}
276
277#[cfg(test)]
278mod test {
279 use super::*;
280
281 use net_declare::fidl_subnet;
282
283 fn test_filter_routines() -> FilterRoutines {
284 FilterRoutines {
285 local_ingress: Some(local_ingress_routine()),
286 local_egress: Some(local_egress_routine()),
287 }
288 }
289
290 fn local_ingress_routine() -> filter_ext::RoutineId {
291 test_routine_id("local_ingress")
292 }
293
294 fn local_egress_routine() -> filter_ext::RoutineId {
295 test_routine_id("local_egress")
296 }
297
298 fn test_routine_id(name: &str) -> filter_ext::RoutineId {
299 filter_ext::RoutineId {
300 namespace: filter_ext::NamespaceId(String::from("namespace")),
301 name: String::from(name),
302 }
303 }
304
305 #[test]
306 fn test_rule_with_proto_any() {
307 assert_eq!(
308 parse_str_to_rules("pass in;", &test_filter_routines()),
309 Ok(vec![filter_ext::Rule {
310 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
311 matchers: filter_ext::Matchers::default(),
312 action: filter_ext::Action::Accept,
313 }])
314 );
315 }
316
317 #[test]
318 fn test_rule_local_ingress_without_corresponding_routine() {
319 assert_eq!(
320 parse_str_to_rules("pass in;", &FilterRoutines::default()),
321 Err(Error::RoutineNotProvided(Direction::LocalIngress))
322 );
323 }
324
325 #[test]
326 fn test_rule_local_egress_without_corresponding_routine() {
327 assert_eq!(
328 parse_str_to_rules("pass out;", &FilterRoutines::default()),
329 Err(Error::RoutineNotProvided(Direction::LocalEgress))
330 );
331 }
332
333 #[test]
334 fn test_rule_with_proto_tcp() {
335 assert_eq!(
336 parse_str_to_rules("pass in proto tcp;", &test_filter_routines()),
337 Ok(vec![filter_ext::Rule {
338 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
339 matchers: filter_ext::Matchers {
340 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
341 src_port: None,
342 dst_port: None,
343 }),
344 ..Default::default()
345 },
346 action: filter_ext::Action::Accept,
347 }])
348 )
349 }
350
351 #[test]
352 fn test_multiple_rules() {
353 assert_eq!(
354 parse_str_to_rules("pass in proto tcp; drop out proto udp;", &test_filter_routines()),
355 Ok(vec![
356 filter_ext::Rule {
357 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
358 matchers: filter_ext::Matchers {
359 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
360 src_port: None,
361 dst_port: None,
362 }),
363 ..Default::default()
364 },
365 action: filter_ext::Action::Accept,
366 },
367 filter_ext::Rule {
368 id: filter_ext::RuleId { routine: local_egress_routine(), index: 1 },
369 matchers: filter_ext::Matchers {
370 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Udp {
371 src_port: None,
372 dst_port: None,
373 }),
374 ..Default::default()
375 },
376 action: filter_ext::Action::Drop,
377 }
378 ])
379 )
380 }
381
382 #[test]
383 fn test_rule_with_from_v4_address() {
384 assert_eq!(
385 parse_str_to_rules("pass in proto tcp from 1.2.3.0/24;", &test_filter_routines()),
386 Ok(vec![
387 filter_ext::Rule {
388 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
389 matchers: filter_ext::Matchers {
390 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
391 src_port: None,
392 dst_port: None,
393 }),
394 src_addr: Some(fnet_matchers_ext::Address {
395 matcher: fnet_matchers_ext::AddressMatcherType::Subnet(
396 fnet_matchers_ext::Subnet::try_from(fidl_subnet!("1.2.3.0/24"))
397 .unwrap()
398 ),
399 invert: false,
400 }),
401 ..Default::default()
402 },
403 action: filter_ext::Action::Accept,
404 }
405 .into()
406 ])
407 )
408 }
409
410 #[test]
411 fn test_rule_with_from_port() {
412 assert_eq!(
413 parse_str_to_rules("pass in proto tcp from port 10000;", &test_filter_routines()),
414 Ok(vec![
415 filter_ext::Rule {
416 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
417 matchers: filter_ext::Matchers {
418 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
419 src_port: Some(
420 fnet_matchers_ext::Port::new(10000, 10000, false).unwrap()
421 ),
422 dst_port: None,
423 }),
424 ..Default::default()
425 },
426 action: filter_ext::Action::Accept,
427 }
428 .into()
429 ])
430 )
431 }
432
433 #[test]
434 fn test_rule_with_from_range() {
435 assert_eq!(
436 parse_str_to_rules(
437 "pass in proto tcp from range 10000:10010;",
438 &test_filter_routines()
439 ),
440 Ok(vec![
441 filter_ext::Rule {
442 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
443 matchers: filter_ext::Matchers {
444 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
445 src_port: Some(
446 fnet_matchers_ext::Port::new(10000, 10010, false).unwrap()
447 ),
448 dst_port: None,
449 }),
450 ..Default::default()
451 },
452 action: filter_ext::Action::Accept,
453 }
454 .into()
455 ])
456 )
457 }
458
459 #[test]
460 fn test_rule_with_from_invalid_range() {
461 assert_eq!(
462 parse_str_to_rules(
463 "pass in proto tcp from range 10005:10000;",
464 &test_filter_routines()
465 ),
466 Err(Error::Invalid(InvalidReason::InvalidPortRange))
467 );
468 }
469
470 #[test]
471 fn test_rule_with_from_v4_address_port() {
472 assert_eq!(
473 parse_str_to_rules(
474 "pass in proto tcp from 1.2.3.0/24 port 10000;",
475 &test_filter_routines()
476 ),
477 Ok(vec![
478 filter_ext::Rule {
479 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
480 matchers: filter_ext::Matchers {
481 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
482 src_port: Some(
483 fnet_matchers_ext::Port::new(10000, 10000, false).unwrap()
484 ),
485 dst_port: None,
486 }),
487 src_addr: Some(fnet_matchers_ext::Address {
488 matcher: fnet_matchers_ext::AddressMatcherType::Subnet(
489 fnet_matchers_ext::Subnet::try_from(fidl_subnet!("1.2.3.0/24"))
490 .unwrap()
491 ),
492 invert: false,
493 }),
494 ..Default::default()
495 },
496 action: filter_ext::Action::Accept,
497 }
498 .into()
499 ])
500 )
501 }
502
503 #[test]
504 fn test_rule_with_from_not_v4_address_port() {
505 assert_eq!(
506 parse_str_to_rules(
507 "pass in proto tcp from !1.2.3.0/24 port 10000;",
508 &test_filter_routines()
509 ),
510 Ok(vec![
511 filter_ext::Rule {
512 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
513 matchers: filter_ext::Matchers {
514 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
515 src_port: Some(
516 fnet_matchers_ext::Port::new(10000, 10000, false).unwrap()
517 ),
518 dst_port: None,
519 }),
520 src_addr: Some(fnet_matchers_ext::Address {
521 matcher: fnet_matchers_ext::AddressMatcherType::Subnet(
522 fnet_matchers_ext::Subnet::try_from(fidl_subnet!("1.2.3.0/24"))
523 .unwrap()
524 ),
525 invert: true,
526 }),
527 ..Default::default()
528 },
529 action: filter_ext::Action::Accept,
530 }
531 .into()
532 ])
533 )
534 }
535
536 #[test]
537 fn test_rule_with_from_v6_address_port() {
538 assert_eq!(
539 parse_str_to_rules(
540 "pass in proto tcp from 1234:5678::/32 port 10000;",
541 &test_filter_routines()
542 ),
543 Ok(vec![
544 filter_ext::Rule {
545 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
546 matchers: filter_ext::Matchers {
547 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
548 src_port: Some(
549 fnet_matchers_ext::Port::new(10000, 10000, false).unwrap()
550 ),
551 dst_port: None,
552 }),
553 src_addr: Some(fnet_matchers_ext::Address {
554 matcher: fnet_matchers_ext::AddressMatcherType::Subnet(
555 fnet_matchers_ext::Subnet::try_from(fidl_subnet!("1234:5678::/32"))
556 .unwrap()
557 ),
558 invert: false,
559 }),
560 ..Default::default()
561 },
562 action: filter_ext::Action::Accept,
563 }
564 .into()
565 ])
566 )
567 }
568
569 #[test]
570 fn test_rule_with_to_v6_address_port() {
571 assert_eq!(
572 parse_str_to_rules(
573 "pass in proto tcp to 1234:5678::/32 port 10000;",
574 &test_filter_routines()
575 ),
576 Ok(vec![
577 filter_ext::Rule {
578 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
579 matchers: filter_ext::Matchers {
580 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
581 src_port: None,
582 dst_port: Some(
583 fnet_matchers_ext::Port::new(10000, 10000, false).unwrap()
584 ),
585 }),
586 dst_addr: Some(fnet_matchers_ext::Address {
587 matcher: fnet_matchers_ext::AddressMatcherType::Subnet(
588 fnet_matchers_ext::Subnet::try_from(fidl_subnet!("1234:5678::/32"))
589 .unwrap()
590 ),
591 invert: false,
592 }),
593 ..Default::default()
594 },
595 action: filter_ext::Action::Accept,
596 }
597 .into()
598 ])
599 )
600 }
601
602 #[test]
603 fn test_rule_with_from_v6_address_port_to_v4_address_port() {
604 assert_eq!(
605 parse_str_to_rules(
606 "pass in proto tcp from 1234:5678::/32 port 10000 to 1.2.3.0/24 port 1000;",
607 &test_filter_routines()
608 ),
609 Err(Error::Invalid(InvalidReason::MixedIPVersions))
610 );
611 }
612
613 #[test]
614 fn test_rule_with_from_v6_address_port_to_v6_address_port() {
615 assert_eq!(
616 parse_str_to_rules(
617 "pass in proto tcp from 1234:5678::/32 port 10000 to 2345:6789::/32 port 1000;",
618 &test_filter_routines()
619 ),
620 Ok(vec![
621 filter_ext::Rule {
622 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
623 matchers: filter_ext::Matchers {
624 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
625 src_port: Some(
626 fnet_matchers_ext::Port::new(10000, 10000, false).unwrap()
627 ),
628 dst_port: Some(
629 fnet_matchers_ext::Port::new(1000, 1000, false).unwrap()
630 ),
631 }),
632 src_addr: Some(fnet_matchers_ext::Address {
633 matcher: fnet_matchers_ext::AddressMatcherType::Subnet(
634 fnet_matchers_ext::Subnet::try_from(fidl_subnet!("1234:5678::/32"))
635 .unwrap()
636 ),
637 invert: false,
638 }),
639 dst_addr: Some(fnet_matchers_ext::Address {
640 matcher: fnet_matchers_ext::AddressMatcherType::Subnet(
641 fnet_matchers_ext::Subnet::try_from(fidl_subnet!("2345:6789::/32"))
642 .unwrap()
643 ),
644 invert: false,
645 }),
646 ..Default::default()
647 },
648 action: filter_ext::Action::Accept,
649 }
650 .into()
651 ])
652 )
653 }
654
655 #[test]
656 fn test_rule_with_port_class() {
657 assert_eq!(
658 parse_str_to_rules("pass in proto tcp devclass ap;", &test_filter_routines()),
659 Ok(vec![filter_ext::Rule {
660 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
661 matchers: filter_ext::Matchers {
662 in_interface: Some(fnet_matchers_ext::Interface::PortClass(
663 fnet_interfaces_ext::PortClass::WlanAp,
664 )),
665 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
666 src_port: None,
667 dst_port: None,
668 }),
669 ..Default::default()
670 },
671 action: filter_ext::Action::Accept,
672 }])
673 )
674 }
675
676 #[test]
677 fn test_rule_with_port_class_and_dst_range() {
678 assert_eq!(
679 parse_str_to_rules(
680 "pass in proto tcp devclass ap to range 1:2;",
681 &test_filter_routines()
682 ),
683 Ok(vec![
684 filter_ext::Rule {
685 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
686 matchers: filter_ext::Matchers {
687 in_interface: Some(fnet_matchers_ext::Interface::PortClass(
688 fnet_interfaces_ext::PortClass::WlanAp
689 )),
690 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
691 src_port: None,
692 dst_port: Some(fnet_matchers_ext::Port::new(1, 2, false).unwrap()),
693 }),
694 ..Default::default()
695 },
696 action: filter_ext::Action::Accept,
697 }
698 .into()
699 ])
700 )
701 }
702
703 #[test]
707 fn test_rule_with_unused_fields() {
708 assert_eq!(
709 parse_str_to_rules("pass in proto tcp log no state;", &test_filter_routines()),
710 Ok(vec![filter_ext::Rule {
711 id: filter_ext::RuleId { routine: local_ingress_routine(), index: 0 },
712 matchers: filter_ext::Matchers {
713 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
714 src_port: None,
715 dst_port: None,
716 }),
717 ..Default::default()
718 },
719 action: filter_ext::Action::Accept,
720 }])
721 )
722 }
723}