1use alloc::sync::Arc;
6use alloc::vec::Vec;
7use core::fmt::Debug;
8
9use assert_matches::assert_matches;
10use derivative::Derivative;
11use net_types::ip::{GenericOverIp, Ip};
12use netstack3_base::MatcherBindingsTypes;
13use netstack3_hashmap::hash_map::{Entry, HashMap};
14use packet_formats::ip::{IpExt, IpProto, Ipv4Proto, Ipv6Proto};
15
16use crate::{
17 Action, Hook, IpRoutines, NatRoutines, PacketMatcher, RejectType, Routine, Routines, Rule,
18 TransportProtocolMatcher, UninstalledRoutine,
19};
20
21#[derive(Derivative, Debug, GenericOverIp)]
23#[generic_over_ip()]
24#[cfg_attr(test, derivative(PartialEq(bound = "RuleInfo: PartialEq")))]
25pub enum ValidationError<RuleInfo> {
26 RuleWithInvalidMatcher(RuleInfo),
30 RuleWithInvalidAction(RuleInfo),
34 TransparentProxyWithInvalidMatcher(RuleInfo),
38 RedirectWithInvalidMatcher(RuleInfo),
42 MasqueradeWithInvalidMatcher(RuleInfo),
46 RejectWithInvalidMatcher(RuleInfo),
50}
51
52#[derive(Derivative)]
54#[derivative(Default(bound = ""))]
55pub struct ValidRoutines<I: IpExt, BT: MatcherBindingsTypes>(Routines<I, BT, ()>);
56
57impl<I: IpExt, BT: MatcherBindingsTypes> ValidRoutines<I, BT> {
58 pub fn get(&self) -> &Routines<I, BT, ()> {
60 let Self(state) = self;
61 &state
62 }
63}
64
65impl<I: IpExt, BT: MatcherBindingsTypes> ValidRoutines<I, BT> {
66 pub fn new<RuleInfo: Clone>(
78 routines: Routines<I, BT, RuleInfo>,
79 ) -> Result<(Self, Vec<UninstalledRoutine<I, BT, ()>>), ValidationError<RuleInfo>> {
80 let Routines { ip: ip_routines, nat: nat_routines } = &routines;
81
82 let IpRoutines { ingress, local_ingress, egress, local_egress, forwarding } = ip_routines;
85 validate_hook(
86 &ingress,
87 &[UnavailableMatcher::OutInterface],
88 &[
89 UnavailableAction::Redirect,
90 UnavailableAction::Masquerade,
91 UnavailableAction::Reject,
92 ],
93 )?;
94 validate_hook(
95 &local_ingress,
96 &[UnavailableMatcher::OutInterface],
97 &[
98 UnavailableAction::TransparentProxy,
99 UnavailableAction::Redirect,
100 UnavailableAction::Masquerade,
101 ],
102 )?;
103 validate_hook(
104 &forwarding,
105 &[],
106 &[
107 UnavailableAction::TransparentProxy,
108 UnavailableAction::Redirect,
109 UnavailableAction::Masquerade,
110 ],
111 )?;
112 validate_hook(
113 &egress,
114 &[UnavailableMatcher::InInterface],
115 &[
116 UnavailableAction::TransparentProxy,
117 UnavailableAction::Redirect,
118 UnavailableAction::Masquerade,
119 UnavailableAction::Reject,
120 ],
121 )?;
122 validate_hook(
123 &local_egress,
124 &[UnavailableMatcher::InInterface],
125 &[
126 UnavailableAction::TransparentProxy,
127 UnavailableAction::Redirect,
128 UnavailableAction::Masquerade,
129 ],
130 )?;
131
132 let NatRoutines { ingress, local_ingress, egress, local_egress } = nat_routines;
133 validate_hook(
134 &ingress,
135 &[UnavailableMatcher::OutInterface],
136 &[
137 UnavailableAction::TransparentProxy,
138 UnavailableAction::Masquerade,
139 UnavailableAction::Mark,
140 UnavailableAction::Reject,
141 ],
142 )?;
143 validate_hook(
144 &local_ingress,
145 &[UnavailableMatcher::OutInterface],
146 &[
147 UnavailableAction::TransparentProxy,
148 UnavailableAction::Redirect,
149 UnavailableAction::Masquerade,
150 UnavailableAction::Mark,
151 UnavailableAction::Reject,
152 ],
153 )?;
154 validate_hook(
155 &egress,
156 &[UnavailableMatcher::InInterface],
157 &[
158 UnavailableAction::TransparentProxy,
159 UnavailableAction::Redirect,
160 UnavailableAction::Mark,
161 UnavailableAction::Reject,
162 ],
163 )?;
164 validate_hook(
165 &local_egress,
166 &[UnavailableMatcher::InInterface],
167 &[
168 UnavailableAction::TransparentProxy,
169 UnavailableAction::Masquerade,
170 UnavailableAction::Mark,
171 UnavailableAction::Reject,
172 ],
173 )?;
174
175 let mut index = UninstalledRoutineIndex::default();
176 let routines = routines.strip_debug_info(&mut index);
177 Ok((Self(routines), index.into_values()))
178 }
179}
180
181#[derive(Clone, Copy)]
182enum UnavailableMatcher {
183 InInterface,
184 OutInterface,
185}
186
187impl UnavailableMatcher {
188 fn validate<I: IpExt, BT: MatcherBindingsTypes, RuleInfo: Clone>(
189 &self,
190 matcher: &PacketMatcher<I, BT>,
191 rule: &RuleInfo,
192 ) -> Result<(), ValidationError<RuleInfo>> {
193 let unavailable_matcher = match self {
194 UnavailableMatcher::InInterface => matcher.in_interface.as_ref(),
195 UnavailableMatcher::OutInterface => matcher.out_interface.as_ref(),
196 };
197 if unavailable_matcher.is_some() {
198 Err(ValidationError::RuleWithInvalidMatcher(rule.clone()))
199 } else {
200 Ok(())
201 }
202 }
203}
204
205#[derive(Clone, Copy)]
206enum UnavailableAction {
207 TransparentProxy,
208 Redirect,
209 Masquerade,
210 Mark,
211 Reject,
212}
213
214impl UnavailableAction {
215 fn validate<I: IpExt, BT: MatcherBindingsTypes, RuleInfo: Clone>(
216 &self,
217 action: &Action<I, BT, RuleInfo>,
218 rule: &RuleInfo,
219 ) -> Result<(), ValidationError<RuleInfo>> {
220 match (self, action) {
221 (UnavailableAction::TransparentProxy, Action::TransparentProxy(_))
222 | (UnavailableAction::Redirect, Action::Redirect { .. })
223 | (UnavailableAction::Masquerade, Action::Masquerade { .. })
224 | (UnavailableAction::Mark, Action::Mark { .. })
225 | (UnavailableAction::Reject, Action::Reject(_)) => {
226 Err(ValidationError::RuleWithInvalidAction(rule.clone()))
227 }
228 _ => Ok(()),
229 }
230 }
231}
232
233fn validate_hook<I: IpExt, BT: MatcherBindingsTypes, RuleInfo: Clone>(
236 Hook { routines }: &Hook<I, BT, RuleInfo>,
237 unavailable_matchers: &[UnavailableMatcher],
238 unavailable_actions: &[UnavailableAction],
239) -> Result<(), ValidationError<RuleInfo>> {
240 for routine in routines {
241 validate_routine(routine, unavailable_matchers, unavailable_actions)?;
242 }
243
244 Ok(())
245}
246
247fn validate_routine<I: IpExt, BT: MatcherBindingsTypes, RuleInfo: Clone>(
256 Routine { rules }: &Routine<I, BT, RuleInfo>,
257 unavailable_matchers: &[UnavailableMatcher],
258 unavailable_actions: &[UnavailableAction],
259) -> Result<(), ValidationError<RuleInfo>> {
260 for Rule { matcher, action, validation_info } in rules {
261 for unavailable in unavailable_matchers {
262 unavailable.validate(matcher, validation_info)?;
263 }
264 for unavailable in unavailable_actions {
265 unavailable.validate(action, validation_info)?;
266 }
267
268 let get_proto_matcher = |matcher: &PacketMatcher<_, _>| {
269 let Some(TransportProtocolMatcher { proto, .. }) = matcher.transport_protocol else {
270 return None;
271 };
272 I::map_ip_in(
273 proto,
274 |proto| match proto {
275 Ipv4Proto::Proto(proto) => Some(proto),
276 _ => None,
277 },
278 |proto| match proto {
279 Ipv6Proto::Proto(proto) => Some(proto),
280 _ => None,
281 },
282 )
283 };
284
285 let has_tcp_or_udp_matcher = |matcher: &PacketMatcher<_, _>| {
286 matches!(get_proto_matcher(matcher), Some(IpProto::Tcp | IpProto::Udp))
287 };
288
289 match action {
290 Action::Accept | Action::Drop | Action::Return | Action::Mark { .. } | Action::None => {
291 }
292 Action::TransparentProxy(_) => {
293 if !has_tcp_or_udp_matcher(matcher) {
296 return Err(ValidationError::TransparentProxyWithInvalidMatcher(
297 validation_info.clone(),
298 ));
299 }
300 }
301 Action::Redirect { dst_port } => {
302 if dst_port.is_some() {
303 if !has_tcp_or_udp_matcher(matcher) {
306 return Err(ValidationError::RedirectWithInvalidMatcher(
307 validation_info.clone(),
308 ));
309 };
310 }
311 }
312 Action::Masquerade { src_port } => {
313 if src_port.is_some() {
314 if !has_tcp_or_udp_matcher(matcher) {
317 return Err(ValidationError::MasqueradeWithInvalidMatcher(
318 validation_info.clone(),
319 ));
320 };
321 }
322 }
323 Action::Jump(target) => {
324 let UninstalledRoutine { routine, id: _ } = target;
325 validate_routine(&*routine, unavailable_matchers, unavailable_actions)?;
326 }
327 Action::Reject(reject_type) => match reject_type {
328 RejectType::TcpReset => {
329 if get_proto_matcher(matcher) != Some(IpProto::Tcp) {
330 return Err(ValidationError::RejectWithInvalidMatcher(
331 validation_info.clone(),
332 ));
333 }
334 }
335 RejectType::NetUnreachable
336 | RejectType::HostUnreachable
337 | RejectType::ProtoUnreachable
338 | RejectType::PortUnreachable
339 | RejectType::RoutePolicyFail
340 | RejectType::RejectRoute
341 | RejectType::AdminProhibited => {}
342 },
343 }
344 }
345
346 Ok(())
347}
348
349#[derive(Derivative)]
350#[derivative(PartialEq(bound = ""), Debug(bound = ""))]
351enum ConvertedRoutine<I: IpExt, BT: MatcherBindingsTypes> {
352 InProgress,
353 Done(UninstalledRoutine<I, BT, ()>),
354}
355
356#[derive(Derivative)]
357#[derivative(Default(bound = ""))]
358struct UninstalledRoutineIndex<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> {
359 index: HashMap<UninstalledRoutine<I, BT, RuleInfo>, ConvertedRoutine<I, BT>>,
360}
361
362impl<I: IpExt, BT: MatcherBindingsTypes, RuleInfo: Clone> UninstalledRoutineIndex<I, BT, RuleInfo> {
363 fn get_or_insert_with(
364 &mut self,
365 target: UninstalledRoutine<I, BT, RuleInfo>,
366 convert: impl FnOnce(
367 &mut UninstalledRoutineIndex<I, BT, RuleInfo>,
368 ) -> UninstalledRoutine<I, BT, ()>,
369 ) -> UninstalledRoutine<I, BT, ()> {
370 match self.index.entry(target.clone()) {
371 Entry::Occupied(entry) => match entry.get() {
372 ConvertedRoutine::InProgress => panic!("cycle in routine graph"),
373 ConvertedRoutine::Done(routine) => return routine.clone(),
374 },
375 Entry::Vacant(entry) => {
376 let _ = entry.insert(ConvertedRoutine::InProgress);
377 }
378 }
379 let converted = convert(self);
382 let previous = self.index.insert(target, ConvertedRoutine::Done(converted.clone()));
383 assert_eq!(previous, Some(ConvertedRoutine::InProgress));
384 converted
385 }
386
387 fn into_values(self) -> Vec<UninstalledRoutine<I, BT, ()>> {
388 self.index
389 .into_values()
390 .map(|routine| assert_matches!(routine, ConvertedRoutine::Done(routine) => routine))
391 .collect()
392 }
393}
394
395impl<I: IpExt, BT: MatcherBindingsTypes, RuleInfo: Clone> Routines<I, BT, RuleInfo> {
396 fn strip_debug_info(
397 self,
398 index: &mut UninstalledRoutineIndex<I, BT, RuleInfo>,
399 ) -> Routines<I, BT, ()> {
400 let Self { ip: ip_routines, nat: nat_routines } = self;
401 Routines {
402 ip: ip_routines.strip_debug_info(index),
403 nat: nat_routines.strip_debug_info(index),
404 }
405 }
406}
407
408impl<I: IpExt, BT: MatcherBindingsTypes, RuleInfo: Clone> IpRoutines<I, BT, RuleInfo> {
409 fn strip_debug_info(
410 self,
411 index: &mut UninstalledRoutineIndex<I, BT, RuleInfo>,
412 ) -> IpRoutines<I, BT, ()> {
413 let Self { ingress, local_ingress, egress, local_egress, forwarding } = self;
414 IpRoutines {
415 ingress: ingress.strip_debug_info(index),
416 local_ingress: local_ingress.strip_debug_info(index),
417 forwarding: forwarding.strip_debug_info(index),
418 egress: egress.strip_debug_info(index),
419 local_egress: local_egress.strip_debug_info(index),
420 }
421 }
422}
423
424impl<I: IpExt, BT: MatcherBindingsTypes, RuleInfo: Clone> NatRoutines<I, BT, RuleInfo> {
425 fn strip_debug_info(
426 self,
427 index: &mut UninstalledRoutineIndex<I, BT, RuleInfo>,
428 ) -> NatRoutines<I, BT, ()> {
429 let Self { ingress, local_ingress, egress, local_egress } = self;
430 NatRoutines {
431 ingress: ingress.strip_debug_info(index),
432 local_ingress: local_ingress.strip_debug_info(index),
433 egress: egress.strip_debug_info(index),
434 local_egress: local_egress.strip_debug_info(index),
435 }
436 }
437}
438
439impl<I: IpExt, BT: MatcherBindingsTypes, RuleInfo: Clone> Hook<I, BT, RuleInfo> {
440 fn strip_debug_info(
441 self,
442 index: &mut UninstalledRoutineIndex<I, BT, RuleInfo>,
443 ) -> Hook<I, BT, ()> {
444 let Self { routines } = self;
445 Hook {
446 routines: routines.into_iter().map(|routine| routine.strip_debug_info(index)).collect(),
447 }
448 }
449}
450
451impl<I: IpExt, BT: MatcherBindingsTypes, RuleInfo: Clone> Routine<I, BT, RuleInfo> {
452 fn strip_debug_info(
453 self,
454 index: &mut UninstalledRoutineIndex<I, BT, RuleInfo>,
455 ) -> Routine<I, BT, ()> {
456 let Self { rules } = self;
457 Routine {
458 rules: rules
459 .into_iter()
460 .map(|Rule { matcher, action, validation_info: _ }| Rule {
461 matcher,
462 action: action.strip_debug_info(index),
463 validation_info: (),
464 })
465 .collect(),
466 }
467 }
468}
469
470impl<I: IpExt, BT: MatcherBindingsTypes, RuleInfo: Clone> Action<I, BT, RuleInfo> {
471 fn strip_debug_info(
472 self,
473 index: &mut UninstalledRoutineIndex<I, BT, RuleInfo>,
474 ) -> Action<I, BT, ()> {
475 match self {
476 Self::Accept => Action::Accept,
477 Self::Drop => Action::Drop,
478 Self::Return => Action::Return,
479 Self::TransparentProxy(proxy) => Action::TransparentProxy(proxy),
480 Self::Redirect { dst_port } => Action::Redirect { dst_port },
481 Self::Masquerade { src_port } => Action::Masquerade { src_port },
482 Self::Mark { domain, action } => Action::Mark { domain, action },
483 Self::Jump(target) => {
484 let converted = index.get_or_insert_with(target.clone(), |index| {
485 let UninstalledRoutine { ref routine, id } = target;
487 UninstalledRoutine {
488 routine: Arc::new(Routine::clone(&*routine).strip_debug_info(index)),
489 id,
490 }
491 });
492 Action::Jump(converted)
493 }
494 Self::None => Action::None,
495 Self::Reject(reject_type) => Action::Reject(reject_type),
496 }
497 }
498}
499
500#[cfg(test)]
501mod tests {
502 use alloc::vec;
503 use core::num::NonZeroU16;
504
505 use assert_matches::assert_matches;
506 use ip_test_macro::ip_test;
507 use net_types::ip::Ipv4;
508 use netstack3_base::InterfaceMatcher;
509 use netstack3_base::testutil::FakeDeviceClass;
510 use test_case::test_case;
511
512 use super::*;
513 use crate::context::testutil::FakeBindingsCtx;
514 use crate::{PacketMatcher, TransparentProxy};
515
516 #[derive(Debug, Clone, PartialEq)]
517 enum RuleId {
518 Valid,
519 Invalid,
520 }
521
522 fn rule<I: IpExt>(
523 matcher: PacketMatcher<I, FakeBindingsCtx<I>>,
524 validation_info: RuleId,
525 ) -> Rule<I, FakeBindingsCtx<I>, RuleId> {
526 Rule { matcher, action: Action::Drop, validation_info }
527 }
528
529 fn hook_with_rules<I: IpExt>(
530 rules: Vec<Rule<I, FakeBindingsCtx<I>, RuleId>>,
531 ) -> Hook<I, FakeBindingsCtx<I>, RuleId> {
532 Hook { routines: vec![Routine { rules }] }
533 }
534
535 #[ip_test(I)]
536 #[test_case(
537 hook_with_rules(vec![rule(
538 PacketMatcher {
539 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Ethernet)),
540 ..Default::default()
541 },
542 RuleId::Valid,
543 )]),
544 UnavailableMatcher::OutInterface =>
545 Ok(());
546 "match on input interface in root routine when available"
547 )]
548 #[test_case(
549 hook_with_rules(vec![rule(
550 PacketMatcher {
551 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Ethernet)),
552 ..Default::default()
553 },
554 RuleId::Valid,
555 )]),
556 UnavailableMatcher::InInterface =>
557 Ok(());
558 "match on output interface in root routine when available"
559 )]
560 #[test_case(
561 hook_with_rules(vec![
562 rule(PacketMatcher::default(), RuleId::Valid),
563 rule(
564 PacketMatcher {
565 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Ethernet)),
566 ..Default::default()
567 },
568 RuleId::Invalid,
569 ),
570 ]),
571 UnavailableMatcher::InInterface =>
572 Err(ValidationError::RuleWithInvalidMatcher(RuleId::Invalid));
573 "match on input interface in root routine when unavailable"
574 )]
575 #[test_case(
576 hook_with_rules(vec![
577 rule(PacketMatcher::default(), RuleId::Valid),
578 rule(
579 PacketMatcher {
580 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Ethernet)),
581 ..Default::default()
582 },
583 RuleId::Invalid,
584 ),
585 ]),
586 UnavailableMatcher::OutInterface =>
587 Err(ValidationError::RuleWithInvalidMatcher(RuleId::Invalid));
588 "match on output interface in root routine when unavailable"
589 )]
590 #[test_case(
591 Hook {
592 routines: vec![Routine {
593 rules: vec![Rule {
594 matcher: PacketMatcher::default(),
595 action: Action::Jump(UninstalledRoutine::new(
596 vec![rule(
597 PacketMatcher {
598 in_interface: Some(InterfaceMatcher::DeviceClass(
599 FakeDeviceClass::Ethernet,
600 )),
601 ..Default::default()
602 },
603 RuleId::Invalid,
604 )],
605 0,
606 )),
607 validation_info: RuleId::Valid,
608 }],
609 }],
610 },
611 UnavailableMatcher::InInterface =>
612 Err(ValidationError::RuleWithInvalidMatcher(RuleId::Invalid));
613 "match on input interface in target routine when unavailable"
614 )]
615 #[test_case(
616 Hook {
617 routines: vec![Routine {
618 rules: vec![Rule {
619 matcher: PacketMatcher::default(),
620 action: Action::Jump(UninstalledRoutine::new(
621 vec![rule(
622 PacketMatcher {
623 out_interface: Some(InterfaceMatcher::DeviceClass(
624 FakeDeviceClass::Ethernet,
625 )),
626 ..Default::default()
627 },
628 RuleId::Invalid,
629 )],
630 0,
631 )),
632 validation_info: RuleId::Valid,
633 }],
634 }],
635 },
636 UnavailableMatcher::OutInterface =>
637 Err(ValidationError::RuleWithInvalidMatcher(RuleId::Invalid));
638 "match on output interface in target routine when unavailable"
639 )]
640 fn validate_interface_matcher_available<I: IpExt>(
641 hook: Hook<I, FakeBindingsCtx<I>, RuleId>,
642 unavailable_matcher: UnavailableMatcher,
643 ) -> Result<(), ValidationError<RuleId>> {
644 validate_hook(&hook, &[unavailable_matcher], &[])
645 }
646
647 fn hook_with_rule<I: IpExt>(
648 rule: Rule<I, FakeBindingsCtx<I>, RuleId>,
649 ) -> Hook<I, FakeBindingsCtx<I>, RuleId> {
650 Hook { routines: vec![Routine { rules: vec![rule] }] }
651 }
652
653 fn transport_matcher<I: IpExt>(proto: I::Proto) -> PacketMatcher<I, FakeBindingsCtx<I>> {
654 PacketMatcher {
655 transport_protocol: Some(TransportProtocolMatcher {
656 proto,
657 src_port: None,
658 dst_port: None,
659 }),
660 ..Default::default()
661 }
662 }
663
664 fn udp_matcher<I: IpExt>() -> PacketMatcher<I, FakeBindingsCtx<I>> {
665 transport_matcher(I::map_ip(
666 (),
667 |()| Ipv4Proto::Proto(IpProto::Udp),
668 |()| Ipv6Proto::Proto(IpProto::Udp),
669 ))
670 }
671
672 fn tcp_matcher<I: IpExt>() -> PacketMatcher<I, FakeBindingsCtx<I>> {
673 transport_matcher(I::map_ip(
674 (),
675 |()| Ipv4Proto::Proto(IpProto::Tcp),
676 |()| Ipv6Proto::Proto(IpProto::Tcp),
677 ))
678 }
679
680 fn icmp_matcher<I: IpExt>() -> PacketMatcher<I, FakeBindingsCtx<I>> {
681 transport_matcher(I::map_ip((), |()| Ipv4Proto::Icmp, |()| Ipv6Proto::Icmpv6))
682 }
683
684 const LOCAL_PORT: NonZeroU16 = NonZeroU16::new(8080).unwrap();
685
686 #[ip_test(I)]
687 #[test_case(
688 Routines {
689 ip: IpRoutines {
690 ingress: hook_with_rule(Rule {
691 matcher: udp_matcher(),
692 action: Action::TransparentProxy(TransparentProxy::LocalPort(LOCAL_PORT)),
693 validation_info: RuleId::Valid,
694 }),
695 ..Default::default()
696 },
697 ..Default::default()
698 } =>
699 Ok(());
700 "transparent proxy available in IP INGRESS routines"
701 )]
702 #[test_case(
703 Routines {
704 ip: IpRoutines {
705 ingress: hook_with_rule(Rule {
706 matcher: PacketMatcher::default(),
707 action: Action::Jump(UninstalledRoutine::new(
708 vec![Rule {
709 matcher: udp_matcher(),
710 action: Action::TransparentProxy(
711 TransparentProxy::LocalPort(LOCAL_PORT)
712 ),
713 validation_info: RuleId::Valid,
714 }],
715 0,
716 )),
717 validation_info: RuleId::Valid,
718 }),
719 ..Default::default()
720 },
721 ..Default::default()
722 } =>
723 Ok(());
724 "transparent proxy available in target routine reachable from INGRESS"
725 )]
726 #[test_case(
727 Routines {
728 ip: IpRoutines {
729 egress: hook_with_rule(Rule {
730 matcher: udp_matcher(),
731 action: Action::TransparentProxy(TransparentProxy::LocalPort(LOCAL_PORT)),
732 validation_info: RuleId::Invalid,
733 }),
734 ..Default::default()
735 },
736 ..Default::default()
737 } =>
738 Err(ValidationError::RuleWithInvalidAction(RuleId::Invalid));
739 "transparent proxy unavailable in IP EGRESS routine"
740 )]
741 #[test_case(
742 Routines {
743 ip: IpRoutines {
744 egress: hook_with_rule(Rule {
745 matcher: PacketMatcher::default(),
746 action: Action::Jump(UninstalledRoutine::new(
747 vec![Rule {
748 matcher: udp_matcher(),
749 action: Action::TransparentProxy(
750 TransparentProxy::LocalPort(LOCAL_PORT)
751 ),
752 validation_info: RuleId::Invalid,
753 }],
754 0,
755 )),
756 validation_info: RuleId::Valid,
757 }),
758 ..Default::default()
759 },
760 ..Default::default()
761 } =>
762 Err(ValidationError::RuleWithInvalidAction(RuleId::Invalid));
763 "transparent proxy unavailable in target routine reachable from EGRESS"
764 )]
765 #[test_case(
766 Routines {
767 nat: NatRoutines {
768 ingress: hook_with_rule(Rule {
769 matcher: PacketMatcher::default(),
770 action: Action::Redirect { dst_port: None },
771 validation_info: RuleId::Valid,
772 }),
773 local_egress: hook_with_rule(Rule {
774 matcher: PacketMatcher::default(),
775 action: Action::Redirect { dst_port: None },
776 validation_info: RuleId::Valid,
777 }),
778 ..Default::default()
779 },
780 ..Default::default()
781 } =>
782 Ok(());
783 "redirect available in NAT INGRESS and LOCAL_EGRESS routines"
784 )]
785 #[test_case(
786 Routines {
787 nat: NatRoutines {
788 egress: hook_with_rule(Rule {
789 matcher: PacketMatcher::default(),
790 action: Action::Redirect { dst_port: None },
791 validation_info: RuleId::Invalid,
792 }),
793 ..Default::default()
794 },
795 ..Default::default()
796 } =>
797 Err(ValidationError::RuleWithInvalidAction(RuleId::Invalid));
798 "redirect unavailable in NAT EGRESS"
799 )]
800 #[test_case(
801 Routines {
802 ip: IpRoutines {
803 ingress: hook_with_rule(Rule {
804 matcher: PacketMatcher::default(),
805 action: Action::Redirect { dst_port: None },
806 validation_info: RuleId::Invalid,
807 }),
808 ..Default::default()
809 },
810 ..Default::default()
811 } =>
812 Err(ValidationError::RuleWithInvalidAction(RuleId::Invalid));
813 "redirect unavailable in IP routines"
814 )]
815 #[test_case(
816 Routines {
817 nat: NatRoutines {
818 egress: hook_with_rule(Rule {
819 matcher: PacketMatcher::default(),
820 action: Action::Masquerade { src_port: None },
821 validation_info: RuleId::Valid,
822 }),
823 ..Default::default()
824 },
825 ..Default::default()
826 } =>
827 Ok(());
828 "masquerade available in NAT EGRESS"
829 )]
830 #[test_case(
831 Routines {
832 nat: NatRoutines {
833 local_ingress: hook_with_rule(Rule {
834 matcher: PacketMatcher::default(),
835 action: Action::Masquerade { src_port: None },
836 validation_info: RuleId::Invalid,
837 }),
838 ..Default::default()
839 },
840 ..Default::default()
841 } =>
842 Err(ValidationError::RuleWithInvalidAction(RuleId::Invalid));
843 "masquerade unavailable in NAT LOCAL_INGRESS"
844 )]
845 #[test_case(
846 Routines {
847 ip: IpRoutines {
848 egress: hook_with_rule(Rule {
849 matcher: PacketMatcher::default(),
850 action: Action::Masquerade { src_port: None },
851 validation_info: RuleId::Invalid,
852 }),
853 ..Default::default()
854 },
855 ..Default::default()
856 } =>
857 Err(ValidationError::RuleWithInvalidAction(RuleId::Invalid));
858 "masquerade unavailable in IP routines"
859 )]
860 fn validate_action_available<I: IpExt>(
861 routines: Routines<I, FakeBindingsCtx<I>, RuleId>,
862 ) -> Result<(), ValidationError<RuleId>> {
863 ValidRoutines::new(routines).map(|_| ())
864 }
865
866 #[ip_test(I)]
867 #[test_case(
868 Routine {
869 rules: vec![Rule {
870 matcher: tcp_matcher(),
871 action: Action::TransparentProxy(TransparentProxy::LocalPort(LOCAL_PORT)),
872 validation_info: RuleId::Valid,
873 }],
874 } =>
875 Ok(());
876 "transparent proxy valid with TCP matcher"
877 )]
878 #[test_case(
879 Routine {
880 rules: vec![Rule {
881 matcher: udp_matcher(),
882 action: Action::TransparentProxy(TransparentProxy::LocalPort(LOCAL_PORT)),
883 validation_info: RuleId::Valid,
884 }],
885 } =>
886 Ok(());
887 "transparent proxy valid with UDP matcher"
888 )]
889 #[test_case(
890 Routine {
891 rules: vec![Rule {
892 matcher: icmp_matcher(),
893 action: Action::TransparentProxy(TransparentProxy::LocalPort(LOCAL_PORT)),
894 validation_info: RuleId::Invalid,
895 }],
896 } =>
897 Err(ValidationError::TransparentProxyWithInvalidMatcher(RuleId::Invalid));
898 "transparent proxy invalid with ICMP matcher"
899 )]
900 #[test_case(
901 Routine {
902 rules: vec![Rule {
903 matcher: PacketMatcher::default(),
904 action: Action::TransparentProxy(TransparentProxy::LocalPort(LOCAL_PORT)),
905 validation_info: RuleId::Invalid,
906 }],
907 } =>
908 Err(ValidationError::TransparentProxyWithInvalidMatcher(RuleId::Invalid));
909 "transparent proxy invalid with no transport protocol matcher"
910 )]
911 fn validate_transparent_proxy_matcher<I: IpExt>(
912 routine: Routine<I, FakeBindingsCtx<I>, RuleId>,
913 ) -> Result<(), ValidationError<RuleId>> {
914 validate_routine(&routine, &[], &[])
915 }
916
917 #[ip_test(I)]
918 #[test_case(
919 Routine {
920 rules: vec![Rule {
921 matcher: PacketMatcher::default(),
922 action: Action::Redirect { dst_port: None },
923 validation_info: RuleId::Valid,
924 }],
925 } =>
926 Ok(());
927 "redirect valid with no matcher if dst port unspecified"
928 )]
929 #[test_case(
930 Routine {
931 rules: vec![Rule {
932 matcher: tcp_matcher(),
933 action: Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
934 validation_info: RuleId::Valid,
935 }],
936 } =>
937 Ok(());
938 "redirect valid with TCP matcher when dst port specified"
939 )]
940 #[test_case(
941 Routine {
942 rules: vec![Rule {
943 matcher: udp_matcher(),
944 action: Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
945 validation_info: RuleId::Valid,
946 }],
947 } =>
948 Ok(());
949 "redirect valid with UDP matcher when dst port specified"
950 )]
951 #[test_case(
952 Routine {
953 rules: vec![Rule {
954 matcher: icmp_matcher(),
955 action: Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
956 validation_info: RuleId::Invalid,
957 }],
958 } =>
959 Err(ValidationError::RedirectWithInvalidMatcher(RuleId::Invalid));
960 "redirect invalid with ICMP matcher when dst port specified"
961 )]
962 #[test_case(
963 Routine {
964 rules: vec![Rule {
965 matcher: PacketMatcher::default(),
966 action: Action::Redirect { dst_port: Some(LOCAL_PORT..=LOCAL_PORT) },
967 validation_info: RuleId::Invalid,
968 }],
969 } =>
970 Err(ValidationError::RedirectWithInvalidMatcher(RuleId::Invalid));
971 "redirect invalid with no transport protocol matcher when dst port specified"
972 )]
973 fn validate_redirect_matcher<I: IpExt>(
974 routine: Routine<I, FakeBindingsCtx<I>, RuleId>,
975 ) -> Result<(), ValidationError<RuleId>> {
976 validate_routine(&routine, &[], &[])
977 }
978
979 #[ip_test(I)]
980 #[test_case(
981 Routine {
982 rules: vec![Rule {
983 matcher: PacketMatcher::default(),
984 action: Action::Masquerade { src_port: None },
985 validation_info: RuleId::Valid,
986 }],
987 } =>
988 Ok(());
989 "masquerade valid with no matcher if src port unspecified"
990 )]
991 #[test_case(
992 Routine {
993 rules: vec![Rule {
994 matcher: tcp_matcher(),
995 action: Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
996 validation_info: RuleId::Valid,
997 }],
998 } =>
999 Ok(());
1000 "masquerade valid with TCP matcher when src port specified"
1001 )]
1002 #[test_case(
1003 Routine {
1004 rules: vec![Rule {
1005 matcher: udp_matcher(),
1006 action: Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
1007 validation_info: RuleId::Valid,
1008 }],
1009 } =>
1010 Ok(());
1011 "masquerade valid with UDP matcher when src port specified"
1012 )]
1013 #[test_case(
1014 Routine {
1015 rules: vec![Rule {
1016 matcher: icmp_matcher(),
1017 action: Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
1018 validation_info: RuleId::Invalid,
1019 }],
1020 } =>
1021 Err(ValidationError::MasqueradeWithInvalidMatcher(RuleId::Invalid));
1022 "masquerade invalid with ICMP matcher when src port specified"
1023 )]
1024 #[test_case(
1025 Routine {
1026 rules: vec![Rule {
1027 matcher: PacketMatcher::default(),
1028 action: Action::Masquerade { src_port: Some(LOCAL_PORT..=LOCAL_PORT) },
1029 validation_info: RuleId::Invalid,
1030 }],
1031 } =>
1032 Err(ValidationError::MasqueradeWithInvalidMatcher(RuleId::Invalid));
1033 "masquerade invalid with no transport protocol matcher when src port specified"
1034 )]
1035 fn validate_masquerade_matcher<I: IpExt>(
1036 routine: Routine<I, FakeBindingsCtx<I>, RuleId>,
1037 ) -> Result<(), ValidationError<RuleId>> {
1038 validate_routine(&routine, &[], &[])
1039 }
1040
1041 #[test]
1042 fn strip_debug_info_reuses_uninstalled_routines() {
1043 let uninstalled_routine =
1045 UninstalledRoutine::<Ipv4, FakeBindingsCtx<Ipv4>, _>::new(Vec::new(), 0);
1046 let hook = Hook {
1047 routines: vec![
1048 Routine {
1049 rules: vec![Rule {
1050 matcher: PacketMatcher::default(),
1051 action: Action::Jump(uninstalled_routine.clone()),
1052 validation_info: "rule-1",
1053 }],
1054 },
1055 Routine {
1056 rules: vec![Rule {
1057 matcher: PacketMatcher::default(),
1058 action: Action::Jump(uninstalled_routine),
1059 validation_info: "rule-2",
1060 }],
1061 },
1062 ],
1063 };
1064
1065 let Hook { routines } = hook.strip_debug_info(&mut UninstalledRoutineIndex::default());
1071 let (first, second) = assert_matches!(
1072 &routines[..],
1073 [Routine { rules: first }, Routine { rules: second }] => (first, second)
1074 );
1075 let first = assert_matches!(
1076 &first[..],
1077 [Rule { action: Action::Jump(target), .. }] => target
1078 );
1079 let second = assert_matches!(
1080 &second[..],
1081 [Rule { action: Action::Jump(target), .. }] => target
1082 );
1083 assert_eq!(first, second);
1084 }
1085}