1pub(crate) mod nat;
6
7use core::num::NonZeroU16;
8use core::ops::RangeInclusive;
9
10use log::error;
11use net_types::ip::{GenericOverIp, Ip, IpVersionMarker};
12use netstack3_base::{AnyDevice, DeviceIdContext, HandleableTimer, IpDeviceAddressIdContext};
13use packet_formats::ip::IpExt;
14
15use crate::conntrack::{Connection, FinalizeConnectionError, GetConnectionError};
16use crate::context::{FilterBindingsContext, FilterBindingsTypes, FilterIpContext};
17use crate::matchers::InterfaceProperties;
18use crate::packets::{IpPacket, MaybeTransportPacket};
19use crate::state::{
20 Action, FilterIpMetadata, FilterMarkMetadata, Hook, Routine, Rule, TransparentProxy,
21};
22
23#[derive(Debug, Clone, Copy, PartialEq)]
25pub enum Verdict<R = ()> {
26 Accept(R),
28 Drop,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq)]
34pub enum IngressVerdict<I: IpExt, R = ()> {
35 Verdict(Verdict<R>),
37 TransparentLocalDelivery {
40 addr: I::Addr,
42 port: NonZeroU16,
44 },
45}
46
47impl<I: IpExt, R> From<Verdict<R>> for IngressVerdict<I, R> {
48 fn from(verdict: Verdict<R>) -> Self {
49 IngressVerdict::Verdict(verdict)
50 }
51}
52
53#[derive(Debug)]
55pub struct ProofOfEgressCheck {
56 _private_field_to_prevent_construction_outside_of_module: (),
57}
58
59impl ProofOfEgressCheck {
60 pub fn clone_for_fragmentation(&self) -> Self {
65 Self { _private_field_to_prevent_construction_outside_of_module: () }
66 }
67}
68
69#[derive(Clone)]
70pub(crate) struct Interfaces<'a, D> {
71 pub ingress: Option<&'a D>,
72 pub egress: Option<&'a D>,
73}
74
75#[derive(Debug)]
77#[cfg_attr(test, derive(PartialEq, Eq))]
78pub(crate) enum RoutineResult<I: IpExt> {
79 Accept,
82 Return,
84 Drop,
86 TransparentLocalDelivery {
89 addr: I::Addr,
91 port: NonZeroU16,
93 },
94 Redirect {
97 dst_port: Option<RangeInclusive<NonZeroU16>>,
101 },
102 Masquerade {
105 src_port: Option<RangeInclusive<NonZeroU16>>,
109 },
110}
111
112impl<I: IpExt> RoutineResult<I> {
113 fn is_terminal(&self) -> bool {
114 match self {
115 RoutineResult::Accept
116 | RoutineResult::Drop
117 | RoutineResult::TransparentLocalDelivery { .. }
118 | RoutineResult::Redirect { .. }
119 | RoutineResult::Masquerade { .. } => true,
120 RoutineResult::Return => false,
121 }
122 }
123}
124
125fn apply_transparent_proxy<I: IpExt, P: MaybeTransportPacket>(
126 proxy: &TransparentProxy<I>,
127 dst_addr: I::Addr,
128 maybe_transport_packet: P,
129) -> RoutineResult<I> {
130 let (addr, port) = match proxy {
131 TransparentProxy::LocalPort(port) => (dst_addr, *port),
132 TransparentProxy::LocalAddr(addr) => {
133 let Some(transport_packet_data) = maybe_transport_packet.transport_packet_data() else {
134 error!(
140 "transparent proxy action is only valid on a rule that matches \
141 on transport protocol, but this packet has no transport header",
142 );
143 return RoutineResult::Drop;
144 };
145 let port = NonZeroU16::new(transport_packet_data.dst_port())
146 .expect("TCP and UDP destination port is always non-zero");
147 (*addr, port)
148 }
149 TransparentProxy::LocalAddrAndPort(addr, port) => (*addr, *port),
150 };
151 RoutineResult::TransparentLocalDelivery { addr, port }
152}
153
154fn check_routine<I, P, D, DeviceClass, M>(
155 Routine { rules }: &Routine<I, DeviceClass, ()>,
156 packet: &P,
157 interfaces: &Interfaces<'_, D>,
158 metadata: &mut M,
159) -> RoutineResult<I>
160where
161 I: IpExt,
162 P: IpPacket<I>,
163 D: InterfaceProperties<DeviceClass>,
164 M: FilterMarkMetadata,
165{
166 for Rule { matcher, action, validation_info: () } in rules {
167 if matcher.matches(packet, &interfaces) {
168 match action {
169 Action::Accept => return RoutineResult::Accept,
170 Action::Return => return RoutineResult::Return,
171 Action::Drop => return RoutineResult::Drop,
172 Action::Jump(target) => {
175 let result = check_routine(target.get(), packet, interfaces, metadata);
176 if result.is_terminal() {
177 return result;
178 }
179 continue;
180 }
181 Action::TransparentProxy(proxy) => {
182 return apply_transparent_proxy(
183 proxy,
184 packet.dst_addr(),
185 packet.maybe_transport_packet(),
186 );
187 }
188 Action::Redirect { dst_port } => {
189 return RoutineResult::Redirect { dst_port: dst_port.clone() }
190 }
191 Action::Masquerade { src_port } => {
192 return RoutineResult::Masquerade { src_port: src_port.clone() }
193 }
194 Action::Mark { domain, action } => {
195 metadata.apply_mark_action(*domain, *action);
198 }
199 }
200 }
201 }
202 RoutineResult::Return
203}
204
205fn check_routines_for_hook<I, P, D, DeviceClass, M>(
206 hook: &Hook<I, DeviceClass, ()>,
207 packet: &P,
208 interfaces: Interfaces<'_, D>,
209 metadata: &mut M,
210) -> Verdict
211where
212 I: IpExt,
213 P: IpPacket<I>,
214 D: InterfaceProperties<DeviceClass>,
215 M: FilterMarkMetadata,
216{
217 let Hook { routines } = hook;
218 for routine in routines {
219 match check_routine(&routine, packet, &interfaces, metadata) {
220 RoutineResult::Accept | RoutineResult::Return => {}
221 RoutineResult::Drop => return Verdict::Drop,
222 result @ RoutineResult::TransparentLocalDelivery { .. } => {
223 unreachable!(
224 "transparent local delivery is only valid in INGRESS hook; got {result:?}"
225 )
226 }
227 result @ (RoutineResult::Redirect { .. } | RoutineResult::Masquerade { .. }) => {
228 unreachable!("NAT actions are only valid in NAT routines; got {result:?}")
229 }
230 }
231 }
232 Verdict::Accept(())
233}
234
235fn check_routines_for_ingress<I, P, D, DeviceClass, M>(
236 hook: &Hook<I, DeviceClass, ()>,
237 packet: &P,
238 interfaces: Interfaces<'_, D>,
239 metadata: &mut M,
240) -> IngressVerdict<I>
241where
242 I: IpExt,
243 P: IpPacket<I>,
244 D: InterfaceProperties<DeviceClass>,
245 M: FilterMarkMetadata,
246{
247 let Hook { routines } = hook;
248 for routine in routines {
249 match check_routine(&routine, packet, &interfaces, metadata) {
250 RoutineResult::Accept | RoutineResult::Return => {}
251 RoutineResult::Drop => return Verdict::Drop.into(),
252 RoutineResult::TransparentLocalDelivery { addr, port } => {
253 return IngressVerdict::TransparentLocalDelivery { addr, port };
254 }
255 result @ (RoutineResult::Redirect { .. } | RoutineResult::Masquerade { .. }) => {
256 unreachable!("NAT actions are only valid in NAT routines; got {result:?}")
257 }
258 }
259 }
260 Verdict::Accept(()).into()
261}
262
263pub trait FilterHandler<I: IpExt, BC: FilterBindingsTypes>:
266 IpDeviceAddressIdContext<I, DeviceId: InterfaceProperties<BC::DeviceClass>>
267{
268 fn ingress_hook<P, M>(
271 &mut self,
272 bindings_ctx: &mut BC,
273 packet: &mut P,
274 interface: &Self::DeviceId,
275 metadata: &mut M,
276 ) -> IngressVerdict<I>
277 where
278 P: IpPacket<I>,
279 M: FilterIpMetadata<I, Self::WeakAddressId, BC>;
280
281 fn local_ingress_hook<P, M>(
284 &mut self,
285 bindings_ctx: &mut BC,
286 packet: &mut P,
287 interface: &Self::DeviceId,
288 metadata: &mut M,
289 ) -> Verdict
290 where
291 P: IpPacket<I>,
292 M: FilterIpMetadata<I, Self::WeakAddressId, BC>;
293
294 fn forwarding_hook<P, M>(
297 &mut self,
298 packet: &mut P,
299 in_interface: &Self::DeviceId,
300 out_interface: &Self::DeviceId,
301 metadata: &mut M,
302 ) -> Verdict
303 where
304 P: IpPacket<I>,
305 M: FilterIpMetadata<I, Self::WeakAddressId, BC>;
306
307 fn local_egress_hook<P, M>(
310 &mut self,
311 bindings_ctx: &mut BC,
312 packet: &mut P,
313 interface: &Self::DeviceId,
314 metadata: &mut M,
315 ) -> Verdict
316 where
317 P: IpPacket<I>,
318 M: FilterIpMetadata<I, Self::WeakAddressId, BC>;
319
320 fn egress_hook<P, M>(
323 &mut self,
324 bindings_ctx: &mut BC,
325 packet: &mut P,
326 interface: &Self::DeviceId,
327 metadata: &mut M,
328 ) -> (Verdict, ProofOfEgressCheck)
329 where
330 P: IpPacket<I>,
331 M: FilterIpMetadata<I, Self::WeakAddressId, BC>;
332}
333
334pub struct FilterImpl<'a, CC>(pub &'a mut CC);
339
340impl<CC: DeviceIdContext<AnyDevice>> DeviceIdContext<AnyDevice> for FilterImpl<'_, CC> {
341 type DeviceId = CC::DeviceId;
342 type WeakDeviceId = CC::WeakDeviceId;
343}
344
345impl<I, CC> IpDeviceAddressIdContext<I> for FilterImpl<'_, CC>
346where
347 I: IpExt,
348 CC: IpDeviceAddressIdContext<I>,
349{
350 type AddressId = CC::AddressId;
351 type WeakAddressId = CC::WeakAddressId;
352}
353
354impl<I, BC, CC> FilterHandler<I, BC> for FilterImpl<'_, CC>
355where
356 I: IpExt,
357 BC: FilterBindingsContext,
358 CC: FilterIpContext<I, BC>,
359{
360 fn ingress_hook<P, M>(
361 &mut self,
362 bindings_ctx: &mut BC,
363 packet: &mut P,
364 interface: &Self::DeviceId,
365 metadata: &mut M,
366 ) -> IngressVerdict<I>
367 where
368 P: IpPacket<I>,
369 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
370 {
371 let Self(this) = self;
372 this.with_filter_state_and_nat_ctx(|state, core_ctx| {
373 let conn = match metadata.take_connection_and_direction() {
377 Some((c, d)) => Some((c, d)),
378 None => {
379 match state.conntrack.get_connection_for_packet_and_update(bindings_ctx, packet)
380 {
381 Ok(result) => result,
382 Err(GetConnectionError::InvalidPacket(c, d)) => Some((c, d)),
385 }
386 }
387 };
388
389 let mut verdict = match check_routines_for_ingress(
390 &state.installed_routines.get().ip.ingress,
391 packet,
392 Interfaces { ingress: Some(interface), egress: None },
393 metadata,
394 ) {
395 v @ IngressVerdict::Verdict(Verdict::Drop) => return v,
396 v @ IngressVerdict::Verdict(Verdict::Accept(()))
397 | v @ IngressVerdict::TransparentLocalDelivery { .. } => v,
398 };
399
400 if let Some((mut conn, direction)) = conn {
401 match nat::perform_nat::<nat::IngressHook, _, _, _, _>(
405 core_ctx,
406 bindings_ctx,
407 state.nat_installed.get(),
408 &state.conntrack,
409 &mut conn,
410 direction,
411 &state.installed_routines.get().nat.ingress,
412 packet,
413 Interfaces { ingress: Some(interface), egress: None },
414 ) {
415 v @ IngressVerdict::Verdict(Verdict::Drop) => return v,
419 IngressVerdict::Verdict(Verdict::Accept(())) => {}
420 v @ IngressVerdict::TransparentLocalDelivery { .. } => {
421 verdict = v;
422 }
423 }
424
425 let res = metadata.replace_connection_and_direction(conn, direction);
426 debug_assert!(res.is_none());
427 }
428
429 verdict
430 })
431 }
432
433 fn local_ingress_hook<P, M>(
434 &mut self,
435 bindings_ctx: &mut BC,
436 packet: &mut P,
437 interface: &Self::DeviceId,
438 metadata: &mut M,
439 ) -> Verdict
440 where
441 P: IpPacket<I>,
442 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
443 {
444 let Self(this) = self;
445 this.with_filter_state_and_nat_ctx(|state, core_ctx| {
446 let conn = match metadata.take_connection_and_direction() {
447 Some((c, d)) => Some((c, d)),
448 None => {
452 match state.conntrack.get_connection_for_packet_and_update(bindings_ctx, packet)
453 {
454 Ok(result) => result,
455 Err(GetConnectionError::InvalidPacket(c, d)) => Some((c, d)),
458 }
459 }
460 };
461
462 let verdict = match check_routines_for_hook(
463 &state.installed_routines.get().ip.local_ingress,
464 packet,
465 Interfaces { ingress: Some(interface), egress: None },
466 metadata,
467 ) {
468 Verdict::Drop => return Verdict::Drop,
469 Verdict::Accept(()) => Verdict::Accept(()),
470 };
471
472 if let Some((mut conn, direction)) = conn {
473 match nat::perform_nat::<nat::LocalIngressHook, _, _, _, _>(
477 core_ctx,
478 bindings_ctx,
479 state.nat_installed.get(),
480 &state.conntrack,
481 &mut conn,
482 direction,
483 &state.installed_routines.get().nat.local_ingress,
484 packet,
485 Interfaces { ingress: Some(interface), egress: None },
486 ) {
487 Verdict::Drop => return Verdict::Drop,
488 Verdict::Accept(()) => {}
489 }
490
491 match state.conntrack.finalize_connection(bindings_ctx, conn) {
492 Ok((_inserted, _weak_conn)) => {}
493 Err(FinalizeConnectionError::Conflict | FinalizeConnectionError::TableFull) => {
496 return Verdict::Drop;
497 }
498 }
499 }
500
501 verdict
502 })
503 }
504
505 fn forwarding_hook<P, M>(
506 &mut self,
507 packet: &mut P,
508 in_interface: &Self::DeviceId,
509 out_interface: &Self::DeviceId,
510 metadata: &mut M,
511 ) -> Verdict
512 where
513 P: IpPacket<I>,
514 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
515 {
516 let Self(this) = self;
517 this.with_filter_state(|state| {
518 check_routines_for_hook(
519 &state.installed_routines.get().ip.forwarding,
520 packet,
521 Interfaces { ingress: Some(in_interface), egress: Some(out_interface) },
522 metadata,
523 )
524 })
525 }
526
527 fn local_egress_hook<P, M>(
528 &mut self,
529 bindings_ctx: &mut BC,
530 packet: &mut P,
531 interface: &Self::DeviceId,
532 metadata: &mut M,
533 ) -> Verdict
534 where
535 P: IpPacket<I>,
536 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
537 {
538 let Self(this) = self;
539 this.with_filter_state_and_nat_ctx(|state, core_ctx| {
540 let conn =
543 match state.conntrack.get_connection_for_packet_and_update(bindings_ctx, packet) {
544 Ok(result) => result,
545 Err(GetConnectionError::InvalidPacket(c, d)) => Some((c, d)),
548 };
549
550 let verdict = match check_routines_for_hook(
551 &state.installed_routines.get().ip.local_egress,
552 packet,
553 Interfaces { ingress: None, egress: Some(interface) },
554 metadata,
555 ) {
556 Verdict::Drop => return Verdict::Drop,
557 Verdict::Accept(()) => Verdict::Accept(()),
558 };
559
560 if let Some((mut conn, direction)) = conn {
561 match nat::perform_nat::<nat::LocalEgressHook, _, _, _, _>(
565 core_ctx,
566 bindings_ctx,
567 state.nat_installed.get(),
568 &state.conntrack,
569 &mut conn,
570 direction,
571 &state.installed_routines.get().nat.local_egress,
572 packet,
573 Interfaces { ingress: None, egress: Some(interface) },
574 ) {
575 Verdict::Drop => return Verdict::Drop,
576 Verdict::Accept(()) => {}
577 }
578
579 let res = metadata.replace_connection_and_direction(conn, direction);
580 debug_assert!(res.is_none());
581 }
582
583 verdict
584 })
585 }
586
587 fn egress_hook<P, M>(
588 &mut self,
589 bindings_ctx: &mut BC,
590 packet: &mut P,
591 interface: &Self::DeviceId,
592 metadata: &mut M,
593 ) -> (Verdict, ProofOfEgressCheck)
594 where
595 P: IpPacket<I>,
596 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
597 {
598 let Self(this) = self;
599 let verdict = this.with_filter_state_and_nat_ctx(|state, core_ctx| {
600 let conn = match metadata.take_connection_and_direction() {
601 Some((c, d)) => Some((c, d)),
602 None => {
606 match state.conntrack.get_connection_for_packet_and_update(bindings_ctx, packet)
607 {
608 Ok(result) => result,
609 Err(GetConnectionError::InvalidPacket(c, d)) => Some((c, d)),
612 }
613 }
614 };
615
616 let verdict = match check_routines_for_hook(
617 &state.installed_routines.get().ip.egress,
618 packet,
619 Interfaces { ingress: None, egress: Some(interface) },
620 metadata,
621 ) {
622 Verdict::Drop => return Verdict::Drop,
623 Verdict::Accept(()) => Verdict::Accept(()),
624 };
625
626 if let Some((mut conn, direction)) = conn {
627 match nat::perform_nat::<nat::EgressHook, _, _, _, _>(
631 core_ctx,
632 bindings_ctx,
633 state.nat_installed.get(),
634 &state.conntrack,
635 &mut conn,
636 direction,
637 &state.installed_routines.get().nat.egress,
638 packet,
639 Interfaces { ingress: None, egress: Some(interface) },
640 ) {
641 Verdict::Drop => return Verdict::Drop,
642 Verdict::Accept(()) => {}
643 }
644
645 match state.conntrack.finalize_connection(bindings_ctx, conn) {
646 Ok((_inserted, conn)) => {
647 if let Some(conn) = conn {
648 let res = metadata.replace_connection_and_direction(
649 Connection::Shared(conn),
650 direction,
651 );
652 debug_assert!(res.is_none());
653 }
654 }
655 Err(FinalizeConnectionError::Conflict | FinalizeConnectionError::TableFull) => {
658 return Verdict::Drop;
659 }
660 }
661 }
662
663 verdict
664 });
665 (
666 verdict,
667 ProofOfEgressCheck { _private_field_to_prevent_construction_outside_of_module: () },
668 )
669 }
670}
671
672#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, GenericOverIp, Hash)]
674#[generic_over_ip(I, Ip)]
675pub enum FilterTimerId<I: Ip> {
676 ConntrackGc(IpVersionMarker<I>),
678}
679
680impl<I: IpExt, BC: FilterBindingsContext, CC: FilterIpContext<I, BC>> HandleableTimer<CC, BC>
681 for FilterTimerId<I>
682{
683 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
684 match self {
685 FilterTimerId::ConntrackGc(_) => core_ctx.with_filter_state(|state| {
686 state.conntrack.perform_gc(bindings_ctx);
687 }),
688 }
689 }
690}
691
692#[cfg(any(test, feature = "testutils"))]
693pub mod testutil {
694 use core::marker::PhantomData;
695
696 use net_types::ip::AddrSubnet;
697 use netstack3_base::testutil::{FakeStrongDeviceId, FakeWeakAddressId, FakeWeakDeviceId};
698 use netstack3_base::AssignedAddrIpExt;
699
700 use super::*;
701
702 pub struct NoopImpl<DeviceId>(PhantomData<DeviceId>);
709
710 impl<DeviceId> Default for NoopImpl<DeviceId> {
711 fn default() -> Self {
712 Self(PhantomData)
713 }
714 }
715
716 impl<DeviceId: FakeStrongDeviceId> DeviceIdContext<AnyDevice> for NoopImpl<DeviceId> {
717 type DeviceId = DeviceId;
718 type WeakDeviceId = FakeWeakDeviceId<DeviceId>;
719 }
720
721 impl<I: AssignedAddrIpExt, DeviceId: FakeStrongDeviceId> IpDeviceAddressIdContext<I>
722 for NoopImpl<DeviceId>
723 {
724 type AddressId = AddrSubnet<I::Addr, I::AssignedWitness>;
725 type WeakAddressId = FakeWeakAddressId<Self::AddressId>;
726 }
727
728 impl<I, BC, DeviceId> FilterHandler<I, BC> for NoopImpl<DeviceId>
729 where
730 I: IpExt + AssignedAddrIpExt,
731 BC: FilterBindingsContext,
732 DeviceId: FakeStrongDeviceId + InterfaceProperties<BC::DeviceClass>,
733 {
734 fn ingress_hook<P, M>(
735 &mut self,
736 _: &mut BC,
737 _: &mut P,
738 _: &Self::DeviceId,
739 _: &mut M,
740 ) -> IngressVerdict<I>
741 where
742 P: IpPacket<I>,
743 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
744 {
745 Verdict::Accept(()).into()
746 }
747
748 fn local_ingress_hook<P, M>(
749 &mut self,
750 _: &mut BC,
751 _: &mut P,
752 _: &Self::DeviceId,
753 _: &mut M,
754 ) -> Verdict
755 where
756 P: IpPacket<I>,
757 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
758 {
759 Verdict::Accept(())
760 }
761
762 fn forwarding_hook<P, M>(
763 &mut self,
764 _: &mut P,
765 _: &Self::DeviceId,
766 _: &Self::DeviceId,
767 _: &mut M,
768 ) -> Verdict
769 where
770 P: IpPacket<I>,
771 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
772 {
773 Verdict::Accept(())
774 }
775
776 fn local_egress_hook<P, M>(
777 &mut self,
778 _: &mut BC,
779 _: &mut P,
780 _: &Self::DeviceId,
781 _: &mut M,
782 ) -> Verdict
783 where
784 P: IpPacket<I>,
785 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
786 {
787 Verdict::Accept(())
788 }
789
790 fn egress_hook<P, M>(
791 &mut self,
792 _: &mut BC,
793 _: &mut P,
794 _: &Self::DeviceId,
795 _: &mut M,
796 ) -> (Verdict, ProofOfEgressCheck)
797 where
798 P: IpPacket<I>,
799 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
800 {
801 (Verdict::Accept(()), ProofOfEgressCheck::forge_proof_for_test())
802 }
803 }
804
805 impl ProofOfEgressCheck {
806 pub(crate) fn forge_proof_for_test() -> Self {
808 ProofOfEgressCheck { _private_field_to_prevent_construction_outside_of_module: () }
809 }
810 }
811}
812
813#[cfg(test)]
814mod tests {
815 use alloc::collections::HashMap;
816 use alloc::sync::Arc;
817 use alloc::vec;
818 use alloc::vec::Vec;
819
820 use assert_matches::assert_matches;
821 use derivative::Derivative;
822 use ip_test_macro::ip_test;
823 use net_types::ip::{AddrSubnet, Ipv4};
824 use netstack3_base::{AssignedAddrIpExt, MarkDomain, Marks, SegmentHeader};
825 use test_case::test_case;
826
827 use super::*;
828 use crate::actions::MarkAction;
829 use crate::conntrack::{self, ConnectionDirection, Tuple};
830 use crate::context::testutil::{FakeBindingsCtx, FakeCtx, FakeDeviceClass, FakeWeakAddressId};
831 use crate::logic::nat::NatConfig;
832 use crate::matchers::testutil::{ethernet_interface, wlan_interface, FakeDeviceId};
833 use crate::matchers::{
834 AddressMatcher, AddressMatcherType, InterfaceMatcher, PacketMatcher, PortMatcher,
835 TransportProtocolMatcher,
836 };
837 use crate::packets::testutil::internal::{
838 ArbitraryValue, FakeIpPacket, FakeTcpSegment, FakeUdpPacket, TransportPacketExt,
839 };
840 use crate::state::{IpRoutines, NatRoutines, UninstalledRoutine};
841 use crate::testutil::TestIpExt;
842
843 impl<I: IpExt> Rule<I, FakeDeviceClass, ()> {
844 pub(crate) fn new(
845 matcher: PacketMatcher<I, FakeDeviceClass>,
846 action: Action<I, FakeDeviceClass, ()>,
847 ) -> Self {
848 Rule { matcher, action, validation_info: () }
849 }
850 }
851
852 #[test]
853 fn return_by_default_if_no_matching_rules_in_routine() {
854 assert_eq!(
855 check_routine::<Ipv4, _, FakeDeviceId, FakeDeviceClass, _>(
856 &Routine { rules: Vec::new() },
857 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
858 &Interfaces { ingress: None, egress: None },
859 &mut NullMetadata {},
860 ),
861 RoutineResult::Return
862 );
863
864 let routine = Routine {
867 rules: vec![
868 Rule::new(
869 PacketMatcher::default(),
870 Action::Jump(UninstalledRoutine::new(Vec::new(), 0)),
871 ),
872 Rule::new(PacketMatcher::default(), Action::Drop),
873 ],
874 };
875 assert_eq!(
876 check_routine::<Ipv4, _, FakeDeviceId, FakeDeviceClass, _>(
877 &routine,
878 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
879 &Interfaces { ingress: None, egress: None },
880 &mut NullMetadata {},
881 ),
882 RoutineResult::Drop
883 );
884 }
885
886 struct NullMetadata {}
887
888 impl<I: IpExt, A, BT: FilterBindingsTypes> FilterIpMetadata<I, A, BT> for NullMetadata {
889 fn take_connection_and_direction(
890 &mut self,
891 ) -> Option<(Connection<I, NatConfig<I, A>, BT>, ConnectionDirection)> {
892 None
893 }
894
895 fn replace_connection_and_direction(
896 &mut self,
897 _conn: Connection<I, NatConfig<I, A>, BT>,
898 _direction: ConnectionDirection,
899 ) -> Option<Connection<I, NatConfig<I, A>, BT>> {
900 None
901 }
902 }
903
904 impl FilterMarkMetadata for NullMetadata {
905 fn apply_mark_action(&mut self, _domain: MarkDomain, _action: MarkAction) {}
906 }
907
908 #[derive(Derivative)]
909 #[derivative(Default(bound = ""))]
910 struct PacketMetadata<I: IpExt + AssignedAddrIpExt, A, BT: FilterBindingsTypes> {
911 conn: Option<(Connection<I, NatConfig<I, A>, BT>, ConnectionDirection)>,
912 marks: Marks,
913 }
914
915 impl<I: TestIpExt, A, BT: FilterBindingsTypes> FilterIpMetadata<I, A, BT>
916 for PacketMetadata<I, A, BT>
917 {
918 fn take_connection_and_direction(
919 &mut self,
920 ) -> Option<(Connection<I, NatConfig<I, A>, BT>, ConnectionDirection)> {
921 let Self { conn, marks: _ } = self;
922 conn.take()
923 }
924
925 fn replace_connection_and_direction(
926 &mut self,
927 new_conn: Connection<I, NatConfig<I, A>, BT>,
928 direction: ConnectionDirection,
929 ) -> Option<Connection<I, NatConfig<I, A>, BT>> {
930 let Self { conn, marks: _ } = self;
931 conn.replace((new_conn, direction)).map(|(conn, _dir)| conn)
932 }
933 }
934
935 impl<I: TestIpExt, A, BT: FilterBindingsTypes> FilterMarkMetadata for PacketMetadata<I, A, BT> {
936 fn apply_mark_action(&mut self, domain: MarkDomain, action: MarkAction) {
937 action.apply(self.marks.get_mut(domain))
938 }
939 }
940
941 #[test]
942 fn accept_by_default_if_no_matching_rules_in_hook() {
943 assert_eq!(
944 check_routines_for_hook::<Ipv4, _, FakeDeviceId, FakeDeviceClass, _>(
945 &Hook::default(),
946 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
947 Interfaces { ingress: None, egress: None },
948 &mut NullMetadata {},
949 ),
950 Verdict::Accept(())
951 );
952 }
953
954 #[test]
955 fn accept_by_default_if_return_from_routine() {
956 let hook = Hook {
957 routines: vec![Routine {
958 rules: vec![Rule::new(PacketMatcher::default(), Action::Return)],
959 }],
960 };
961
962 assert_eq!(
963 check_routines_for_hook::<Ipv4, _, FakeDeviceId, FakeDeviceClass, _>(
964 &hook,
965 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
966 Interfaces { ingress: None, egress: None },
967 &mut NullMetadata {},
968 ),
969 Verdict::Accept(())
970 );
971 }
972
973 #[test]
974 fn accept_terminal_for_installed_routine() {
975 let routine = Routine {
976 rules: vec![
977 Rule::new(PacketMatcher::default(), Action::Accept),
979 Rule::new(PacketMatcher::default(), Action::Drop),
981 ],
982 };
983 assert_eq!(
984 check_routine::<Ipv4, _, FakeDeviceId, FakeDeviceClass, _>(
985 &routine,
986 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
987 &Interfaces { ingress: None, egress: None },
988 &mut NullMetadata {},
989 ),
990 RoutineResult::Accept
991 );
992
993 let routine = Routine {
995 rules: vec![
996 Rule::new(
998 PacketMatcher::default(),
999 Action::Jump(UninstalledRoutine::new(
1000 vec![Rule::new(PacketMatcher::default(), Action::Accept)],
1001 0,
1002 )),
1003 ),
1004 Rule::new(PacketMatcher::default(), Action::Drop),
1006 ],
1007 };
1008 assert_eq!(
1009 check_routine::<Ipv4, _, FakeDeviceId, FakeDeviceClass, _>(
1010 &routine,
1011 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1012 &Interfaces { ingress: None, egress: None },
1013 &mut NullMetadata {},
1014 ),
1015 RoutineResult::Accept
1016 );
1017
1018 let hook = Hook {
1023 routines: vec![
1024 routine,
1025 Routine {
1026 rules: vec![
1027 Rule::new(PacketMatcher::default(), Action::Drop),
1029 ],
1030 },
1031 ],
1032 };
1033
1034 assert_eq!(
1035 check_routines_for_hook::<Ipv4, _, FakeDeviceId, FakeDeviceClass, _>(
1036 &hook,
1037 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1038 Interfaces { ingress: None, egress: None },
1039 &mut NullMetadata {},
1040 ),
1041 Verdict::Drop
1042 );
1043 }
1044
1045 #[test]
1046 fn drop_terminal_for_entire_hook() {
1047 let hook = Hook {
1048 routines: vec![
1049 Routine {
1050 rules: vec![
1051 Rule::new(PacketMatcher::default(), Action::Drop),
1053 ],
1054 },
1055 Routine {
1056 rules: vec![
1057 Rule::new(PacketMatcher::default(), Action::Accept),
1059 ],
1060 },
1061 ],
1062 };
1063
1064 assert_eq!(
1065 check_routines_for_hook::<Ipv4, _, FakeDeviceId, FakeDeviceClass, _>(
1066 &hook,
1067 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1068 Interfaces { ingress: None, egress: None },
1069 &mut NullMetadata {},
1070 ),
1071 Verdict::Drop
1072 );
1073 }
1074
1075 #[test]
1076 fn transparent_proxy_terminal_for_entire_hook() {
1077 const TPROXY_PORT: NonZeroU16 = NonZeroU16::new(8080).unwrap();
1078
1079 let ingress = Hook {
1080 routines: vec![
1081 Routine {
1082 rules: vec![Rule::new(
1083 PacketMatcher::default(),
1084 Action::TransparentProxy(TransparentProxy::LocalPort(TPROXY_PORT)),
1085 )],
1086 },
1087 Routine {
1088 rules: vec![
1089 Rule::new(PacketMatcher::default(), Action::Accept),
1091 ],
1092 },
1093 ],
1094 };
1095
1096 assert_eq!(
1097 check_routines_for_ingress::<Ipv4, _, FakeDeviceId, FakeDeviceClass, _>(
1098 &ingress,
1099 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1100 Interfaces { ingress: None, egress: None },
1101 &mut NullMetadata {},
1102 ),
1103 IngressVerdict::TransparentLocalDelivery {
1104 addr: <Ipv4 as crate::packets::testutil::internal::TestIpExt>::DST_IP,
1105 port: TPROXY_PORT
1106 }
1107 );
1108 }
1109
1110 #[test]
1111 fn jump_recursively_evaluates_target_routine() {
1112 let routine = Routine {
1115 rules: vec![Rule::new(
1116 PacketMatcher::default(),
1117 Action::Jump(UninstalledRoutine::new(
1118 vec![Rule::new(PacketMatcher::default(), Action::Drop)],
1119 0,
1120 )),
1121 )],
1122 };
1123 assert_eq!(
1124 check_routine::<Ipv4, _, FakeDeviceId, FakeDeviceClass, _>(
1125 &routine,
1126 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1127 &Interfaces { ingress: None, egress: None },
1128 &mut NullMetadata {},
1129 ),
1130 RoutineResult::Drop
1131 );
1132
1133 let routine = Routine {
1136 rules: vec![
1137 Rule::new(
1138 PacketMatcher::default(),
1139 Action::Jump(UninstalledRoutine::new(
1140 vec![Rule::new(PacketMatcher::default(), Action::Accept)],
1141 0,
1142 )),
1143 ),
1144 Rule::new(PacketMatcher::default(), Action::Drop),
1145 ],
1146 };
1147 assert_eq!(
1148 check_routine::<Ipv4, _, FakeDeviceId, FakeDeviceClass, _>(
1149 &routine,
1150 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1151 &Interfaces { ingress: None, egress: None },
1152 &mut NullMetadata {},
1153 ),
1154 RoutineResult::Accept
1155 );
1156
1157 let routine = Routine {
1160 rules: vec![
1161 Rule::new(
1162 PacketMatcher::default(),
1163 Action::Jump(UninstalledRoutine::new(
1164 vec![Rule::new(PacketMatcher::default(), Action::Return)],
1165 0,
1166 )),
1167 ),
1168 Rule::new(PacketMatcher::default(), Action::Drop),
1169 ],
1170 };
1171 assert_eq!(
1172 check_routine::<Ipv4, _, FakeDeviceId, FakeDeviceClass, _>(
1173 &routine,
1174 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1175 &Interfaces { ingress: None, egress: None },
1176 &mut NullMetadata {},
1177 ),
1178 RoutineResult::Drop
1179 );
1180 }
1181
1182 #[test]
1183 fn return_terminal_for_single_routine() {
1184 let routine = Routine {
1185 rules: vec![
1186 Rule::new(PacketMatcher::default(), Action::Return),
1187 Rule::new(PacketMatcher::default(), Action::Drop),
1189 ],
1190 };
1191
1192 assert_eq!(
1193 check_routine::<Ipv4, _, FakeDeviceId, FakeDeviceClass, _>(
1194 &routine,
1195 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1196 &Interfaces { ingress: None, egress: None },
1197 &mut NullMetadata {},
1198 ),
1199 RoutineResult::Return
1200 );
1201 }
1202
1203 #[ip_test(I)]
1204 fn filter_handler_implements_ip_hooks_correctly<I: TestIpExt>() {
1205 fn drop_all_traffic<I: TestIpExt>(
1206 matcher: PacketMatcher<I, FakeDeviceClass>,
1207 ) -> Hook<I, FakeDeviceClass, ()> {
1208 Hook { routines: vec![Routine { rules: vec![Rule::new(matcher, Action::Drop)] }] }
1209 }
1210
1211 let mut bindings_ctx = FakeBindingsCtx::new();
1212
1213 let mut ctx = FakeCtx::with_ip_routines(
1216 &mut bindings_ctx,
1217 IpRoutines {
1218 ingress: drop_all_traffic(PacketMatcher {
1219 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1220 ..Default::default()
1221 }),
1222 ..Default::default()
1223 },
1224 );
1225 assert_eq!(
1226 FilterImpl(&mut ctx).ingress_hook(
1227 &mut bindings_ctx,
1228 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1229 &wlan_interface(),
1230 &mut NullMetadata {},
1231 ),
1232 Verdict::Drop.into()
1233 );
1234
1235 let mut ctx = FakeCtx::with_ip_routines(
1238 &mut bindings_ctx,
1239 IpRoutines {
1240 local_ingress: drop_all_traffic(PacketMatcher {
1241 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1242 ..Default::default()
1243 }),
1244 ..Default::default()
1245 },
1246 );
1247 assert_eq!(
1248 FilterImpl(&mut ctx).local_ingress_hook(
1249 &mut bindings_ctx,
1250 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1251 &wlan_interface(),
1252 &mut NullMetadata {},
1253 ),
1254 Verdict::Drop
1255 );
1256
1257 let mut ctx = FakeCtx::with_ip_routines(
1260 &mut bindings_ctx,
1261 IpRoutines {
1262 forwarding: drop_all_traffic(PacketMatcher {
1263 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1264 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Ethernet)),
1265 ..Default::default()
1266 }),
1267 ..Default::default()
1268 },
1269 );
1270 assert_eq!(
1271 FilterImpl(&mut ctx).forwarding_hook(
1272 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1273 &wlan_interface(),
1274 ðernet_interface(),
1275 &mut NullMetadata {},
1276 ),
1277 Verdict::Drop
1278 );
1279
1280 let mut ctx = FakeCtx::with_ip_routines(
1283 &mut bindings_ctx,
1284 IpRoutines {
1285 local_egress: drop_all_traffic(PacketMatcher {
1286 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1287 ..Default::default()
1288 }),
1289 ..Default::default()
1290 },
1291 );
1292 assert_eq!(
1293 FilterImpl(&mut ctx).local_egress_hook(
1294 &mut bindings_ctx,
1295 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1296 &wlan_interface(),
1297 &mut NullMetadata {},
1298 ),
1299 Verdict::Drop
1300 );
1301
1302 let mut ctx = FakeCtx::with_ip_routines(
1305 &mut bindings_ctx,
1306 IpRoutines {
1307 egress: drop_all_traffic(PacketMatcher {
1308 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1309 ..Default::default()
1310 }),
1311 ..Default::default()
1312 },
1313 );
1314 assert_eq!(
1315 FilterImpl(&mut ctx)
1316 .egress_hook(
1317 &mut bindings_ctx,
1318 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1319 &wlan_interface(),
1320 &mut NullMetadata {},
1321 )
1322 .0,
1323 Verdict::Drop
1324 );
1325 }
1326
1327 #[ip_test(I)]
1328 #[test_case(22 => Verdict::Accept(()); "port 22 allowed for SSH")]
1329 #[test_case(80 => Verdict::Accept(()); "port 80 allowed for HTTP")]
1330 #[test_case(1024 => Verdict::Accept(()); "ephemeral port 1024 allowed")]
1331 #[test_case(65535 => Verdict::Accept(()); "ephemeral port 65535 allowed")]
1332 #[test_case(1023 => Verdict::Drop; "privileged port 1023 blocked")]
1333 #[test_case(53 => Verdict::Drop; "privileged port 53 blocked")]
1334 fn block_privileged_ports_except_ssh_http<I: TestIpExt>(port: u16) -> Verdict {
1335 fn tcp_port_rule<I: IpExt>(
1336 src_port: Option<PortMatcher>,
1337 dst_port: Option<PortMatcher>,
1338 action: Action<I, FakeDeviceClass, ()>,
1339 ) -> Rule<I, FakeDeviceClass, ()> {
1340 Rule::new(
1341 PacketMatcher {
1342 transport_protocol: Some(TransportProtocolMatcher {
1343 proto: <&FakeTcpSegment as TransportPacketExt<I>>::proto(),
1344 src_port,
1345 dst_port,
1346 }),
1347 ..Default::default()
1348 },
1349 action,
1350 )
1351 }
1352
1353 fn default_filter_rules<I: IpExt>() -> Routine<I, FakeDeviceClass, ()> {
1354 Routine {
1355 rules: vec![
1356 tcp_port_rule(
1358 None,
1359 Some(PortMatcher { range: 22..=22, invert: false }),
1360 Action::Accept,
1361 ),
1362 tcp_port_rule(
1364 None,
1365 Some(PortMatcher { range: 80..=80, invert: false }),
1366 Action::Accept,
1367 ),
1368 tcp_port_rule(
1370 None,
1371 Some(PortMatcher { range: 1024..=65535, invert: false }),
1372 Action::Accept,
1373 ),
1374 tcp_port_rule(
1376 None,
1377 Some(PortMatcher { range: 1..=65535, invert: false }),
1378 Action::Drop,
1379 ),
1380 ],
1381 }
1382 }
1383
1384 let mut bindings_ctx = FakeBindingsCtx::new();
1385
1386 let mut ctx = FakeCtx::with_ip_routines(
1387 &mut bindings_ctx,
1388 IpRoutines {
1389 local_ingress: Hook { routines: vec![default_filter_rules()] },
1390 ..Default::default()
1391 },
1392 );
1393
1394 FilterImpl(&mut ctx).local_ingress_hook(
1395 &mut bindings_ctx,
1396 &mut FakeIpPacket::<I, _> {
1397 body: FakeTcpSegment {
1398 dst_port: port,
1399 src_port: 11111,
1400 segment: SegmentHeader::arbitrary_value(),
1401 payload_len: 8888,
1402 },
1403 ..ArbitraryValue::arbitrary_value()
1404 },
1405 &wlan_interface(),
1406 &mut NullMetadata {},
1407 )
1408 }
1409
1410 #[ip_test(I)]
1411 #[test_case(
1412 ethernet_interface() => Verdict::Accept(());
1413 "allow incoming traffic on ethernet interface"
1414 )]
1415 #[test_case(wlan_interface() => Verdict::Drop; "drop incoming traffic on wlan interface")]
1416 fn filter_on_wlan_only<I: TestIpExt>(interface: FakeDeviceId) -> Verdict {
1417 fn drop_wlan_traffic<I: IpExt>() -> Routine<I, FakeDeviceClass, ()> {
1418 Routine {
1419 rules: vec![Rule::new(
1420 PacketMatcher {
1421 in_interface: Some(InterfaceMatcher::Id(wlan_interface().id)),
1422 ..Default::default()
1423 },
1424 Action::Drop,
1425 )],
1426 }
1427 }
1428
1429 let mut bindings_ctx = FakeBindingsCtx::new();
1430
1431 let mut ctx = FakeCtx::with_ip_routines(
1432 &mut bindings_ctx,
1433 IpRoutines {
1434 local_ingress: Hook { routines: vec![drop_wlan_traffic()] },
1435 ..Default::default()
1436 },
1437 );
1438
1439 FilterImpl(&mut ctx).local_ingress_hook(
1440 &mut bindings_ctx,
1441 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1442 &interface,
1443 &mut NullMetadata {},
1444 )
1445 }
1446
1447 #[test]
1448 fn ingress_reuses_cached_connection_when_available() {
1449 let mut bindings_ctx = FakeBindingsCtx::new();
1450 let mut core_ctx = FakeCtx::new(&mut bindings_ctx);
1451
1452 let mut packet = FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value();
1455 let mut metadata = PacketMetadata::default();
1456 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1457 &mut bindings_ctx,
1458 &mut packet,
1459 ðernet_interface(),
1460 &mut metadata,
1461 );
1462 assert_eq!(verdict, Verdict::Accept(()));
1463
1464 let (stashed, _dir) =
1466 metadata.take_connection_and_direction().expect("metadata should include connection");
1467 let tuple = Tuple::from_packet(&packet).expect("packet should be trackable");
1468 let table = core_ctx
1469 .conntrack()
1470 .get_connection(&tuple)
1471 .expect("packet should be inserted in table");
1472 assert_matches!(
1473 (table, stashed),
1474 (Connection::Shared(table), Connection::Shared(stashed)) => {
1475 assert!(Arc::ptr_eq(&table, &stashed));
1476 }
1477 );
1478
1479 let verdict = FilterImpl(&mut core_ctx).ingress_hook(
1482 &mut bindings_ctx,
1483 &mut packet,
1484 ðernet_interface(),
1485 &mut metadata,
1486 );
1487 assert_eq!(verdict, Verdict::Accept(()).into());
1488
1489 let (after_ingress, _dir) =
1492 metadata.take_connection_and_direction().expect("metadata should include connection");
1493 let table = core_ctx
1494 .conntrack()
1495 .get_connection(&tuple)
1496 .expect("packet should be inserted in table");
1497 assert_matches!(
1498 (table, after_ingress),
1499 (Connection::Shared(before), Connection::Shared(after)) => {
1500 assert!(Arc::ptr_eq(&before, &after));
1501 }
1502 );
1503 }
1504
1505 #[ip_test(I)]
1506 fn drop_packet_on_finalize_connection_failure<I: TestIpExt>() {
1507 let mut bindings_ctx = FakeBindingsCtx::new();
1508 let mut ctx = FakeCtx::new(&mut bindings_ctx);
1509
1510 for i in 0..u32::try_from(conntrack::MAXIMUM_ENTRIES / 2).unwrap() {
1511 let (mut packet, mut reply_packet) = conntrack::testutils::make_test_udp_packets(i);
1512 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1513 &mut bindings_ctx,
1514 &mut packet,
1515 ðernet_interface(),
1516 &mut NullMetadata {},
1517 );
1518 assert_eq!(verdict, Verdict::Accept(()));
1519
1520 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1521 &mut bindings_ctx,
1522 &mut reply_packet,
1523 ðernet_interface(),
1524 &mut NullMetadata {},
1525 );
1526 assert_eq!(verdict, Verdict::Accept(()));
1527
1528 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1529 &mut bindings_ctx,
1530 &mut packet,
1531 ðernet_interface(),
1532 &mut NullMetadata {},
1533 );
1534 assert_eq!(verdict, Verdict::Accept(()));
1535 }
1536
1537 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1541 &mut bindings_ctx,
1542 &mut FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value(),
1543 ðernet_interface(),
1544 &mut NullMetadata {},
1545 );
1546 assert_eq!(verdict, Verdict::Drop);
1547 }
1548
1549 #[ip_test(I)]
1550 fn implicit_snat_to_prevent_tuple_clash<I: TestIpExt>() {
1551 let mut bindings_ctx = FakeBindingsCtx::new();
1552 let mut ctx = FakeCtx::with_nat_routines_and_device_addrs(
1553 &mut bindings_ctx,
1554 NatRoutines {
1555 egress: Hook {
1556 routines: vec![Routine {
1557 rules: vec![Rule::new(
1558 PacketMatcher {
1559 src_address: Some(AddressMatcher {
1560 matcher: AddressMatcherType::Range(I::SRC_IP_2..=I::SRC_IP_2),
1561 invert: false,
1562 }),
1563 ..Default::default()
1564 },
1565 Action::Masquerade { src_port: None },
1566 )],
1567 }],
1568 },
1569 ..Default::default()
1570 },
1571 HashMap::from([(
1572 ethernet_interface(),
1573 AddrSubnet::new(I::SRC_IP, I::SUBNET.prefix()).unwrap(),
1574 )]),
1575 );
1576
1577 let mut packet = FakeIpPacket {
1580 src_ip: I::SRC_IP_2,
1581 dst_ip: I::DST_IP,
1582 body: FakeUdpPacket::arbitrary_value(),
1583 };
1584 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1585 &mut bindings_ctx,
1586 &mut packet,
1587 ðernet_interface(),
1588 &mut NullMetadata {},
1589 );
1590 assert_eq!(verdict, Verdict::Accept(()));
1591 assert_eq!(packet.src_ip, I::SRC_IP);
1592
1593 let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1600 let src_port = packet.body.src_port;
1601 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1602 &mut bindings_ctx,
1603 &mut packet,
1604 ðernet_interface(),
1605 &mut NullMetadata {},
1606 );
1607 assert_eq!(verdict, Verdict::Accept(()));
1608 assert_ne!(packet.body.src_port, src_port);
1609 }
1610
1611 #[ip_test(I)]
1612 fn packet_adopts_tracked_connection_in_table_if_identical<I: TestIpExt>() {
1613 let mut bindings_ctx = FakeBindingsCtx::new();
1614 let mut core_ctx = FakeCtx::new(&mut bindings_ctx);
1615
1616 let mut first_packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1619 let mut first_metadata = PacketMetadata::default();
1620 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1621 &mut bindings_ctx,
1622 &mut first_packet,
1623 ðernet_interface(),
1624 &mut first_metadata,
1625 );
1626 assert_eq!(verdict, Verdict::Accept(()));
1627
1628 let mut second_packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1629 let mut second_metadata = PacketMetadata::default();
1630 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1631 &mut bindings_ctx,
1632 &mut second_packet,
1633 ðernet_interface(),
1634 &mut second_metadata,
1635 );
1636 assert_eq!(verdict, Verdict::Accept(()));
1637
1638 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1640 &mut bindings_ctx,
1641 &mut first_packet,
1642 ðernet_interface(),
1643 &mut first_metadata,
1644 );
1645 assert_eq!(verdict, Verdict::Accept(()));
1646
1647 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1650 &mut bindings_ctx,
1651 &mut second_packet,
1652 ðernet_interface(),
1653 &mut second_metadata,
1654 );
1655 assert_eq!(second_packet.body.src_port, first_packet.body.src_port);
1656 assert_eq!(verdict, Verdict::Accept(()));
1657
1658 let (first_conn, _dir) = first_metadata.take_connection_and_direction().unwrap();
1659 let (second_conn, _dir) = second_metadata.take_connection_and_direction().unwrap();
1660 assert_matches!(
1661 (first_conn, second_conn),
1662 (Connection::Shared(first), Connection::Shared(second)) => {
1663 assert!(Arc::ptr_eq(&first, &second));
1664 }
1665 );
1666 }
1667
1668 #[ip_test(I)]
1669 fn both_source_and_destination_nat_configured<I: TestIpExt>() {
1670 let mut bindings_ctx = FakeBindingsCtx::new();
1671 let mut core_ctx = FakeCtx::with_nat_routines_and_device_addrs(
1674 &mut bindings_ctx,
1675 NatRoutines {
1676 local_egress: Hook {
1677 routines: vec![Routine {
1678 rules: vec![Rule::new(
1679 PacketMatcher::default(),
1680 Action::Redirect { dst_port: None },
1681 )],
1682 }],
1683 },
1684 egress: Hook {
1685 routines: vec![Routine {
1686 rules: vec![Rule::new(
1687 PacketMatcher::default(),
1688 Action::Masquerade { src_port: None },
1689 )],
1690 }],
1691 },
1692 ..Default::default()
1693 },
1694 HashMap::from([(
1695 ethernet_interface(),
1696 AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap(),
1697 )]),
1698 );
1699
1700 let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1703 let mut metadata = PacketMetadata::default();
1704 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1705 &mut bindings_ctx,
1706 &mut packet,
1707 ðernet_interface(),
1708 &mut metadata,
1709 );
1710 assert_eq!(verdict, Verdict::Accept(()));
1711 assert_eq!(packet.dst_ip, *I::LOOPBACK_ADDRESS);
1712
1713 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1716 &mut bindings_ctx,
1717 &mut packet,
1718 ðernet_interface(),
1719 &mut metadata,
1720 );
1721 assert_eq!(verdict, Verdict::Accept(()));
1722 assert_eq!(packet.src_ip, I::SRC_IP_2);
1723 }
1724
1725 #[ip_test(I)]
1726 #[test_case(
1727 Hook {
1728 routines: vec![
1729 Routine {
1730 rules: vec![
1731 Rule::new(
1732 PacketMatcher::default(),
1733 Action::Mark {
1734 domain: MarkDomain::Mark1,
1735 action: MarkAction::SetMark { clearing_mask: 0, mark: 1 },
1736 },
1737 ),
1738 Rule::new(PacketMatcher::default(), Action::Drop),
1739 ],
1740 },
1741 ],
1742 }; "non terminal for routine"
1743 )]
1744 #[test_case(
1745 Hook {
1746 routines: vec![
1747 Routine {
1748 rules: vec![Rule::new(
1749 PacketMatcher::default(),
1750 Action::Mark {
1751 domain: MarkDomain::Mark1,
1752 action: MarkAction::SetMark { clearing_mask: 0, mark: 1 },
1753 },
1754 )],
1755 },
1756 Routine {
1757 rules: vec![
1758 Rule::new(PacketMatcher::default(), Action::Drop),
1759 ],
1760 },
1761 ],
1762 }; "non terminal for hook"
1763 )]
1764 fn mark_action<I: TestIpExt>(ingress: Hook<I, FakeDeviceClass, ()>) {
1765 let mut metadata = PacketMetadata::<I, FakeWeakAddressId<I>, FakeBindingsCtx<I>>::default();
1766 assert_eq!(
1767 check_routines_for_ingress::<I, _, FakeDeviceId, FakeDeviceClass, _>(
1768 &ingress,
1769 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1770 Interfaces { ingress: None, egress: None },
1771 &mut metadata,
1772 ),
1773 IngressVerdict::Verdict(Verdict::Drop),
1774 );
1775 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1)]));
1776 }
1777
1778 #[ip_test(I)]
1779 fn mark_action_applied_in_succession<I: TestIpExt>() {
1780 fn hook_with_single_mark_action<I: TestIpExt>(
1781 domain: MarkDomain,
1782 action: MarkAction,
1783 ) -> Hook<I, FakeDeviceClass, ()> {
1784 Hook {
1785 routines: vec![Routine {
1786 rules: vec![Rule::new(
1787 PacketMatcher::default(),
1788 Action::Mark { domain, action },
1789 )],
1790 }],
1791 }
1792 }
1793 let mut metadata = PacketMetadata::<I, FakeWeakAddressId<I>, FakeBindingsCtx<I>>::default();
1794 assert_eq!(
1795 check_routines_for_ingress::<I, _, FakeDeviceId, FakeDeviceClass, _>(
1796 &hook_with_single_mark_action(
1797 MarkDomain::Mark1,
1798 MarkAction::SetMark { clearing_mask: 0, mark: 1 }
1799 ),
1800 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1801 Interfaces { ingress: None, egress: None },
1802 &mut metadata,
1803 ),
1804 IngressVerdict::Verdict(Verdict::Accept(())),
1805 );
1806 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1)]));
1807
1808 assert_eq!(
1809 check_routines_for_hook(
1810 &hook_with_single_mark_action::<I>(
1811 MarkDomain::Mark2,
1812 MarkAction::SetMark { clearing_mask: 0, mark: 1 }
1813 ),
1814 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1815 Interfaces::<FakeDeviceId> { ingress: None, egress: None },
1816 &mut metadata,
1817 ),
1818 Verdict::Accept(())
1819 );
1820 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1), (MarkDomain::Mark2, 1)]));
1821
1822 assert_eq!(
1823 check_routines_for_hook(
1824 &hook_with_single_mark_action::<I>(
1825 MarkDomain::Mark1,
1826 MarkAction::SetMark { clearing_mask: 1, mark: 2 }
1827 ),
1828 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1829 Interfaces::<FakeDeviceId> { ingress: None, egress: None },
1830 &mut metadata,
1831 ),
1832 Verdict::Accept(())
1833 );
1834 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 2), (MarkDomain::Mark2, 1)]));
1835 }
1836}