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