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