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