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::{AssignedAddrIpExt, InterfaceMatcher, MarkDomain, Marks, SegmentHeader};
831 use netstack3_hashmap::HashMap;
832 use test_case::test_case;
833
834 use super::*;
835 use crate::actions::MarkAction;
836 use crate::conntrack::{self, ConnectionDirection};
837 use crate::context::testutil::{FakeBindingsCtx, FakeCtx, FakeWeakAddressId};
838 use crate::logic::nat::NatConfig;
839 use crate::matchers::{
840 AddressMatcher, AddressMatcherType, PacketMatcher, PortMatcher, TransportProtocolMatcher,
841 };
842 use crate::packets::testutil::internal::{
843 ArbitraryValue, FakeIpPacket, FakeTcpSegment, FakeUdpPacket, TransportPacketExt,
844 };
845 use crate::state::{IpRoutines, NatRoutines, UninstalledRoutine};
846 use crate::testutil::TestIpExt;
847
848 impl<I: IpExt> Rule<I, FakeDeviceClass, ()> {
849 pub(crate) fn new(
850 matcher: PacketMatcher<I, FakeDeviceClass>,
851 action: Action<I, FakeDeviceClass, ()>,
852 ) -> Self {
853 Rule { matcher, action, validation_info: () }
854 }
855 }
856
857 #[test]
858 fn return_by_default_if_no_matching_rules_in_routine() {
859 assert_eq!(
860 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
861 &Routine { rules: Vec::new() },
862 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
863 &Interfaces { ingress: None, egress: None },
864 &mut NullMetadata {},
865 ),
866 RoutineResult::Return
867 );
868
869 let routine = Routine {
872 rules: vec![
873 Rule::new(
874 PacketMatcher::default(),
875 Action::Jump(UninstalledRoutine::new(Vec::new(), 0)),
876 ),
877 Rule::new(PacketMatcher::default(), Action::Drop),
878 ],
879 };
880 assert_eq!(
881 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
882 &routine,
883 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
884 &Interfaces { ingress: None, egress: None },
885 &mut NullMetadata {},
886 ),
887 RoutineResult::Drop
888 );
889 }
890
891 struct NullMetadata {}
892
893 impl<I: IpExt, A, BT: FilterBindingsTypes> FilterIpMetadata<I, A, BT> for NullMetadata {
894 fn take_connection_and_direction(
895 &mut self,
896 ) -> Option<(Connection<I, NatConfig<I, A>, BT>, ConnectionDirection)> {
897 None
898 }
899
900 fn replace_connection_and_direction(
901 &mut self,
902 _conn: Connection<I, NatConfig<I, A>, BT>,
903 _direction: ConnectionDirection,
904 ) -> Option<Connection<I, NatConfig<I, A>, BT>> {
905 None
906 }
907 }
908
909 impl FilterMarkMetadata for NullMetadata {
910 fn apply_mark_action(&mut self, _domain: MarkDomain, _action: MarkAction) {}
911 }
912
913 #[derive(Derivative)]
914 #[derivative(Default(bound = ""))]
915 struct PacketMetadata<I: IpExt + AssignedAddrIpExt, A, BT: FilterBindingsTypes> {
916 conn: Option<(Connection<I, NatConfig<I, A>, BT>, ConnectionDirection)>,
917 marks: Marks,
918 }
919
920 impl<I: TestIpExt, A, BT: FilterBindingsTypes> FilterIpMetadata<I, A, BT>
921 for PacketMetadata<I, A, BT>
922 {
923 fn take_connection_and_direction(
924 &mut self,
925 ) -> Option<(Connection<I, NatConfig<I, A>, BT>, ConnectionDirection)> {
926 let Self { conn, marks: _ } = self;
927 conn.take()
928 }
929
930 fn replace_connection_and_direction(
931 &mut self,
932 new_conn: Connection<I, NatConfig<I, A>, BT>,
933 direction: ConnectionDirection,
934 ) -> Option<Connection<I, NatConfig<I, A>, BT>> {
935 let Self { conn, marks: _ } = self;
936 conn.replace((new_conn, direction)).map(|(conn, _dir)| conn)
937 }
938 }
939
940 impl<I: TestIpExt, A, BT: FilterBindingsTypes> FilterMarkMetadata for PacketMetadata<I, A, BT> {
941 fn apply_mark_action(&mut self, domain: MarkDomain, action: MarkAction) {
942 action.apply(self.marks.get_mut(domain))
943 }
944 }
945
946 #[test]
947 fn accept_by_default_if_no_matching_rules_in_hook() {
948 assert_eq!(
949 check_routines_for_hook::<Ipv4, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
950 &Hook::default(),
951 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
952 Interfaces { ingress: None, egress: None },
953 &mut NullMetadata {},
954 ),
955 Verdict::Accept(())
956 );
957 }
958
959 #[test]
960 fn accept_by_default_if_return_from_routine() {
961 let hook = Hook {
962 routines: vec![Routine {
963 rules: vec![Rule::new(PacketMatcher::default(), Action::Return)],
964 }],
965 };
966
967 assert_eq!(
968 check_routines_for_hook::<Ipv4, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
969 &hook,
970 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
971 Interfaces { ingress: None, egress: None },
972 &mut NullMetadata {},
973 ),
974 Verdict::Accept(())
975 );
976 }
977
978 #[test]
979 fn accept_terminal_for_installed_routine() {
980 let routine = Routine {
981 rules: vec![
982 Rule::new(PacketMatcher::default(), Action::Accept),
984 Rule::new(PacketMatcher::default(), Action::Drop),
986 ],
987 };
988 assert_eq!(
989 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
990 &routine,
991 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
992 &Interfaces { ingress: None, egress: None },
993 &mut NullMetadata {},
994 ),
995 RoutineResult::Accept
996 );
997
998 let routine = Routine {
1000 rules: vec![
1001 Rule::new(
1003 PacketMatcher::default(),
1004 Action::Jump(UninstalledRoutine::new(
1005 vec![Rule::new(PacketMatcher::default(), Action::Accept)],
1006 0,
1007 )),
1008 ),
1009 Rule::new(PacketMatcher::default(), Action::Drop),
1011 ],
1012 };
1013 assert_eq!(
1014 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
1015 &routine,
1016 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1017 &Interfaces { ingress: None, egress: None },
1018 &mut NullMetadata {},
1019 ),
1020 RoutineResult::Accept
1021 );
1022
1023 let hook = Hook {
1028 routines: vec![
1029 routine,
1030 Routine {
1031 rules: vec![
1032 Rule::new(PacketMatcher::default(), Action::Drop),
1034 ],
1035 },
1036 ],
1037 };
1038
1039 assert_eq!(
1040 check_routines_for_hook::<Ipv4, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
1041 &hook,
1042 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1043 Interfaces { ingress: None, egress: None },
1044 &mut NullMetadata {},
1045 ),
1046 Verdict::Drop
1047 );
1048 }
1049
1050 #[test]
1051 fn drop_terminal_for_entire_hook() {
1052 let hook = Hook {
1053 routines: vec![
1054 Routine {
1055 rules: vec![
1056 Rule::new(PacketMatcher::default(), Action::Drop),
1058 ],
1059 },
1060 Routine {
1061 rules: vec![
1062 Rule::new(PacketMatcher::default(), Action::Accept),
1064 ],
1065 },
1066 ],
1067 };
1068
1069 assert_eq!(
1070 check_routines_for_hook::<Ipv4, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
1071 &hook,
1072 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1073 Interfaces { ingress: None, egress: None },
1074 &mut NullMetadata {},
1075 ),
1076 Verdict::Drop
1077 );
1078 }
1079
1080 #[test]
1081 fn transparent_proxy_terminal_for_entire_hook() {
1082 const TPROXY_PORT: NonZeroU16 = NonZeroU16::new(8080).unwrap();
1083
1084 let ingress = Hook {
1085 routines: vec![
1086 Routine {
1087 rules: vec![Rule::new(
1088 PacketMatcher::default(),
1089 Action::TransparentProxy(TransparentProxy::LocalPort(TPROXY_PORT)),
1090 )],
1091 },
1092 Routine {
1093 rules: vec![
1094 Rule::new(PacketMatcher::default(), Action::Accept),
1096 ],
1097 },
1098 ],
1099 };
1100
1101 assert_eq!(
1102 check_routines_for_ingress::<Ipv4, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
1103 &ingress,
1104 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1105 Interfaces { ingress: None, egress: None },
1106 &mut NullMetadata {},
1107 ),
1108 IngressVerdict::TransparentLocalDelivery {
1109 addr: <Ipv4 as crate::packets::testutil::internal::TestIpExt>::DST_IP,
1110 port: TPROXY_PORT
1111 }
1112 );
1113 }
1114
1115 #[test]
1116 fn jump_recursively_evaluates_target_routine() {
1117 let routine = Routine {
1120 rules: vec![Rule::new(
1121 PacketMatcher::default(),
1122 Action::Jump(UninstalledRoutine::new(
1123 vec![Rule::new(PacketMatcher::default(), Action::Drop)],
1124 0,
1125 )),
1126 )],
1127 };
1128 assert_eq!(
1129 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
1130 &routine,
1131 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1132 &Interfaces { ingress: None, egress: None },
1133 &mut NullMetadata {},
1134 ),
1135 RoutineResult::Drop
1136 );
1137
1138 let routine = Routine {
1141 rules: vec![
1142 Rule::new(
1143 PacketMatcher::default(),
1144 Action::Jump(UninstalledRoutine::new(
1145 vec![Rule::new(PacketMatcher::default(), Action::Accept)],
1146 0,
1147 )),
1148 ),
1149 Rule::new(PacketMatcher::default(), Action::Drop),
1150 ],
1151 };
1152 assert_eq!(
1153 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
1154 &routine,
1155 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1156 &Interfaces { ingress: None, egress: None },
1157 &mut NullMetadata {},
1158 ),
1159 RoutineResult::Accept
1160 );
1161
1162 let routine = Routine {
1165 rules: vec![
1166 Rule::new(
1167 PacketMatcher::default(),
1168 Action::Jump(UninstalledRoutine::new(
1169 vec![Rule::new(PacketMatcher::default(), Action::Return)],
1170 0,
1171 )),
1172 ),
1173 Rule::new(PacketMatcher::default(), Action::Drop),
1174 ],
1175 };
1176 assert_eq!(
1177 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
1178 &routine,
1179 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1180 &Interfaces { ingress: None, egress: None },
1181 &mut NullMetadata {},
1182 ),
1183 RoutineResult::Drop
1184 );
1185 }
1186
1187 #[test]
1188 fn return_terminal_for_single_routine() {
1189 let routine = Routine {
1190 rules: vec![
1191 Rule::new(PacketMatcher::default(), Action::Return),
1192 Rule::new(PacketMatcher::default(), Action::Drop),
1194 ],
1195 };
1196
1197 assert_eq!(
1198 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
1199 &routine,
1200 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1201 &Interfaces { ingress: None, egress: None },
1202 &mut NullMetadata {},
1203 ),
1204 RoutineResult::Return
1205 );
1206 }
1207
1208 #[ip_test(I)]
1209 fn filter_handler_implements_ip_hooks_correctly<I: TestIpExt>() {
1210 fn drop_all_traffic<I: TestIpExt>(
1211 matcher: PacketMatcher<I, FakeDeviceClass>,
1212 ) -> Hook<I, FakeDeviceClass, ()> {
1213 Hook { routines: vec![Routine { rules: vec![Rule::new(matcher, Action::Drop)] }] }
1214 }
1215
1216 let mut bindings_ctx = FakeBindingsCtx::new();
1217
1218 let mut ctx = FakeCtx::with_ip_routines(
1221 &mut bindings_ctx,
1222 IpRoutines {
1223 ingress: drop_all_traffic(PacketMatcher {
1224 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1225 ..Default::default()
1226 }),
1227 ..Default::default()
1228 },
1229 );
1230 assert_eq!(
1231 FilterImpl(&mut ctx).ingress_hook(
1232 &mut bindings_ctx,
1233 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1234 &FakeMatcherDeviceId::wlan_interface(),
1235 &mut NullMetadata {},
1236 ),
1237 Verdict::Drop.into()
1238 );
1239
1240 let mut ctx = FakeCtx::with_ip_routines(
1243 &mut bindings_ctx,
1244 IpRoutines {
1245 local_ingress: drop_all_traffic(PacketMatcher {
1246 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1247 ..Default::default()
1248 }),
1249 ..Default::default()
1250 },
1251 );
1252 assert_eq!(
1253 FilterImpl(&mut ctx).local_ingress_hook(
1254 &mut bindings_ctx,
1255 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1256 &FakeMatcherDeviceId::wlan_interface(),
1257 &mut NullMetadata {},
1258 ),
1259 Verdict::Drop
1260 );
1261
1262 let mut ctx = FakeCtx::with_ip_routines(
1265 &mut bindings_ctx,
1266 IpRoutines {
1267 forwarding: drop_all_traffic(PacketMatcher {
1268 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1269 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Ethernet)),
1270 ..Default::default()
1271 }),
1272 ..Default::default()
1273 },
1274 );
1275 assert_eq!(
1276 FilterImpl(&mut ctx).forwarding_hook(
1277 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1278 &FakeMatcherDeviceId::wlan_interface(),
1279 &FakeMatcherDeviceId::ethernet_interface(),
1280 &mut NullMetadata {},
1281 ),
1282 Verdict::Drop
1283 );
1284
1285 let mut ctx = FakeCtx::with_ip_routines(
1288 &mut bindings_ctx,
1289 IpRoutines {
1290 local_egress: drop_all_traffic(PacketMatcher {
1291 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1292 ..Default::default()
1293 }),
1294 ..Default::default()
1295 },
1296 );
1297 assert_eq!(
1298 FilterImpl(&mut ctx).local_egress_hook(
1299 &mut bindings_ctx,
1300 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1301 &FakeMatcherDeviceId::wlan_interface(),
1302 &mut NullMetadata {},
1303 ),
1304 Verdict::Drop
1305 );
1306
1307 let mut ctx = FakeCtx::with_ip_routines(
1310 &mut bindings_ctx,
1311 IpRoutines {
1312 egress: drop_all_traffic(PacketMatcher {
1313 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1314 ..Default::default()
1315 }),
1316 ..Default::default()
1317 },
1318 );
1319 assert_eq!(
1320 FilterImpl(&mut ctx)
1321 .egress_hook(
1322 &mut bindings_ctx,
1323 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1324 &FakeMatcherDeviceId::wlan_interface(),
1325 &mut NullMetadata {},
1326 )
1327 .0,
1328 Verdict::Drop
1329 );
1330 }
1331
1332 #[ip_test(I)]
1333 #[test_case(22 => Verdict::Accept(()); "port 22 allowed for SSH")]
1334 #[test_case(80 => Verdict::Accept(()); "port 80 allowed for HTTP")]
1335 #[test_case(1024 => Verdict::Accept(()); "ephemeral port 1024 allowed")]
1336 #[test_case(65535 => Verdict::Accept(()); "ephemeral port 65535 allowed")]
1337 #[test_case(1023 => Verdict::Drop; "privileged port 1023 blocked")]
1338 #[test_case(53 => Verdict::Drop; "privileged port 53 blocked")]
1339 fn block_privileged_ports_except_ssh_http<I: TestIpExt>(port: u16) -> Verdict {
1340 fn tcp_port_rule<I: FilterIpExt>(
1341 src_port: Option<PortMatcher>,
1342 dst_port: Option<PortMatcher>,
1343 action: Action<I, FakeDeviceClass, ()>,
1344 ) -> Rule<I, FakeDeviceClass, ()> {
1345 Rule::new(
1346 PacketMatcher {
1347 transport_protocol: Some(TransportProtocolMatcher {
1348 proto: <&FakeTcpSegment as TransportPacketExt<I>>::proto().unwrap(),
1349 src_port,
1350 dst_port,
1351 }),
1352 ..Default::default()
1353 },
1354 action,
1355 )
1356 }
1357
1358 fn default_filter_rules<I: FilterIpExt>() -> Routine<I, FakeDeviceClass, ()> {
1359 Routine {
1360 rules: vec![
1361 tcp_port_rule(
1363 None,
1364 Some(PortMatcher { range: 22..=22, invert: false }),
1365 Action::Accept,
1366 ),
1367 tcp_port_rule(
1369 None,
1370 Some(PortMatcher { range: 80..=80, invert: false }),
1371 Action::Accept,
1372 ),
1373 tcp_port_rule(
1375 None,
1376 Some(PortMatcher { range: 1024..=65535, invert: false }),
1377 Action::Accept,
1378 ),
1379 tcp_port_rule(
1381 None,
1382 Some(PortMatcher { range: 1..=65535, invert: false }),
1383 Action::Drop,
1384 ),
1385 ],
1386 }
1387 }
1388
1389 let mut bindings_ctx = FakeBindingsCtx::new();
1390
1391 let mut ctx = FakeCtx::with_ip_routines(
1392 &mut bindings_ctx,
1393 IpRoutines {
1394 local_ingress: Hook { routines: vec![default_filter_rules()] },
1395 ..Default::default()
1396 },
1397 );
1398
1399 FilterImpl(&mut ctx).local_ingress_hook(
1400 &mut bindings_ctx,
1401 &mut FakeIpPacket::<I, _> {
1402 body: FakeTcpSegment {
1403 dst_port: port,
1404 src_port: 11111,
1405 segment: SegmentHeader::arbitrary_value(),
1406 payload_len: 8888,
1407 },
1408 ..ArbitraryValue::arbitrary_value()
1409 },
1410 &FakeMatcherDeviceId::wlan_interface(),
1411 &mut NullMetadata {},
1412 )
1413 }
1414
1415 #[ip_test(I)]
1416 #[test_case(
1417 FakeMatcherDeviceId::ethernet_interface() => Verdict::Accept(());
1418 "allow incoming traffic on ethernet interface"
1419 )]
1420 #[test_case(
1421 FakeMatcherDeviceId::wlan_interface() => Verdict::Drop;
1422 "drop incoming traffic on wlan interface"
1423 )]
1424 fn filter_on_wlan_only<I: TestIpExt>(interface: FakeMatcherDeviceId) -> Verdict {
1425 fn drop_wlan_traffic<I: IpExt>() -> Routine<I, FakeDeviceClass, ()> {
1426 Routine {
1427 rules: vec![Rule::new(
1428 PacketMatcher {
1429 in_interface: Some(InterfaceMatcher::Id(
1430 FakeMatcherDeviceId::wlan_interface().id,
1431 )),
1432 ..Default::default()
1433 },
1434 Action::Drop,
1435 )],
1436 }
1437 }
1438
1439 let mut bindings_ctx = FakeBindingsCtx::new();
1440
1441 let mut ctx = FakeCtx::with_ip_routines(
1442 &mut bindings_ctx,
1443 IpRoutines {
1444 local_ingress: Hook { routines: vec![drop_wlan_traffic()] },
1445 ..Default::default()
1446 },
1447 );
1448
1449 FilterImpl(&mut ctx).local_ingress_hook(
1450 &mut bindings_ctx,
1451 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1452 &interface,
1453 &mut NullMetadata {},
1454 )
1455 }
1456
1457 #[test]
1458 fn ingress_reuses_cached_connection_when_available() {
1459 let mut bindings_ctx = FakeBindingsCtx::new();
1460 let mut core_ctx = FakeCtx::new(&mut bindings_ctx);
1461
1462 let mut packet = FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value();
1465 let mut metadata = PacketMetadata::default();
1466 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1467 &mut bindings_ctx,
1468 &mut packet,
1469 &FakeMatcherDeviceId::ethernet_interface(),
1470 &mut metadata,
1471 );
1472 assert_eq!(verdict, Verdict::Accept(()));
1473
1474 let (stashed, _dir) =
1476 metadata.take_connection_and_direction().expect("metadata should include connection");
1477 let tuple = packet.conntrack_packet().expect("packet should be trackable").tuple();
1478 let table = core_ctx
1479 .conntrack()
1480 .get_connection(&tuple)
1481 .expect("packet should be inserted in table");
1482 assert_matches!(
1483 (table, stashed),
1484 (Connection::Shared(table), Connection::Shared(stashed)) => {
1485 assert!(Arc::ptr_eq(&table, &stashed));
1486 }
1487 );
1488
1489 let verdict = FilterImpl(&mut core_ctx).ingress_hook(
1492 &mut bindings_ctx,
1493 &mut packet,
1494 &FakeMatcherDeviceId::ethernet_interface(),
1495 &mut metadata,
1496 );
1497 assert_eq!(verdict, Verdict::Accept(()).into());
1498
1499 let (after_ingress, _dir) =
1502 metadata.take_connection_and_direction().expect("metadata should include connection");
1503 let table = core_ctx
1504 .conntrack()
1505 .get_connection(&tuple)
1506 .expect("packet should be inserted in table");
1507 assert_matches!(
1508 (table, after_ingress),
1509 (Connection::Shared(before), Connection::Shared(after)) => {
1510 assert!(Arc::ptr_eq(&before, &after));
1511 }
1512 );
1513 }
1514
1515 #[ip_test(I)]
1516 fn drop_packet_on_finalize_connection_failure<I: TestIpExt>() {
1517 let mut bindings_ctx = FakeBindingsCtx::new();
1518 let mut ctx = FakeCtx::new(&mut bindings_ctx);
1519
1520 for i in 0..u32::try_from(conntrack::MAXIMUM_ENTRIES / 2).unwrap() {
1521 let (mut packet, mut reply_packet) = conntrack::testutils::make_test_udp_packets(i);
1522 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1523 &mut bindings_ctx,
1524 &mut packet,
1525 &FakeMatcherDeviceId::ethernet_interface(),
1526 &mut NullMetadata {},
1527 );
1528 assert_eq!(verdict, Verdict::Accept(()));
1529
1530 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1531 &mut bindings_ctx,
1532 &mut reply_packet,
1533 &FakeMatcherDeviceId::ethernet_interface(),
1534 &mut NullMetadata {},
1535 );
1536 assert_eq!(verdict, Verdict::Accept(()));
1537
1538 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1539 &mut bindings_ctx,
1540 &mut packet,
1541 &FakeMatcherDeviceId::ethernet_interface(),
1542 &mut NullMetadata {},
1543 );
1544 assert_eq!(verdict, Verdict::Accept(()));
1545 }
1546
1547 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1551 &mut bindings_ctx,
1552 &mut FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value(),
1553 &FakeMatcherDeviceId::ethernet_interface(),
1554 &mut NullMetadata {},
1555 );
1556 assert_eq!(verdict, Verdict::Drop);
1557 }
1558
1559 #[ip_test(I)]
1560 fn implicit_snat_to_prevent_tuple_clash<I: TestIpExt>() {
1561 let mut bindings_ctx = FakeBindingsCtx::new();
1562 let mut ctx = FakeCtx::with_nat_routines_and_device_addrs(
1563 &mut bindings_ctx,
1564 NatRoutines {
1565 egress: Hook {
1566 routines: vec![Routine {
1567 rules: vec![Rule::new(
1568 PacketMatcher {
1569 src_address: Some(AddressMatcher {
1570 matcher: AddressMatcherType::Range(I::SRC_IP_2..=I::SRC_IP_2),
1571 invert: false,
1572 }),
1573 ..Default::default()
1574 },
1575 Action::Masquerade { src_port: None },
1576 )],
1577 }],
1578 },
1579 ..Default::default()
1580 },
1581 HashMap::from([(
1582 FakeMatcherDeviceId::ethernet_interface(),
1583 AddrSubnet::new(I::SRC_IP, I::SUBNET.prefix()).unwrap(),
1584 )]),
1585 );
1586
1587 let mut packet = FakeIpPacket {
1590 src_ip: I::SRC_IP_2,
1591 dst_ip: I::DST_IP,
1592 body: FakeUdpPacket::arbitrary_value(),
1593 };
1594 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1595 &mut bindings_ctx,
1596 &mut packet,
1597 &FakeMatcherDeviceId::ethernet_interface(),
1598 &mut NullMetadata {},
1599 );
1600 assert_eq!(verdict, Verdict::Accept(()));
1601 assert_eq!(packet.src_ip, I::SRC_IP);
1602
1603 let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1610 let src_port = packet.body.src_port;
1611 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1612 &mut bindings_ctx,
1613 &mut packet,
1614 &FakeMatcherDeviceId::ethernet_interface(),
1615 &mut NullMetadata {},
1616 );
1617 assert_eq!(verdict, Verdict::Accept(()));
1618 assert_ne!(packet.body.src_port, src_port);
1619 }
1620
1621 #[ip_test(I)]
1622 fn packet_adopts_tracked_connection_in_table_if_identical<I: TestIpExt>() {
1623 let mut bindings_ctx = FakeBindingsCtx::new();
1624 let mut core_ctx = FakeCtx::new(&mut bindings_ctx);
1625
1626 let mut first_packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1629 let mut first_metadata = PacketMetadata::default();
1630 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1631 &mut bindings_ctx,
1632 &mut first_packet,
1633 &FakeMatcherDeviceId::ethernet_interface(),
1634 &mut first_metadata,
1635 );
1636 assert_eq!(verdict, Verdict::Accept(()));
1637
1638 let mut second_packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1639 let mut second_metadata = PacketMetadata::default();
1640 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1641 &mut bindings_ctx,
1642 &mut second_packet,
1643 &FakeMatcherDeviceId::ethernet_interface(),
1644 &mut second_metadata,
1645 );
1646 assert_eq!(verdict, Verdict::Accept(()));
1647
1648 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1650 &mut bindings_ctx,
1651 &mut first_packet,
1652 &FakeMatcherDeviceId::ethernet_interface(),
1653 &mut first_metadata,
1654 );
1655 assert_eq!(verdict, Verdict::Accept(()));
1656
1657 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1660 &mut bindings_ctx,
1661 &mut second_packet,
1662 &FakeMatcherDeviceId::ethernet_interface(),
1663 &mut second_metadata,
1664 );
1665 assert_eq!(second_packet.body.src_port, first_packet.body.src_port);
1666 assert_eq!(verdict, Verdict::Accept(()));
1667
1668 let (first_conn, _dir) = first_metadata.take_connection_and_direction().unwrap();
1669 let (second_conn, _dir) = second_metadata.take_connection_and_direction().unwrap();
1670 assert_matches!(
1671 (first_conn, second_conn),
1672 (Connection::Shared(first), Connection::Shared(second)) => {
1673 assert!(Arc::ptr_eq(&first, &second));
1674 }
1675 );
1676 }
1677
1678 #[ip_test(I)]
1679 fn both_source_and_destination_nat_configured<I: TestIpExt>() {
1680 let mut bindings_ctx = FakeBindingsCtx::new();
1681 let mut core_ctx = FakeCtx::with_nat_routines_and_device_addrs(
1684 &mut bindings_ctx,
1685 NatRoutines {
1686 local_egress: Hook {
1687 routines: vec![Routine {
1688 rules: vec![Rule::new(
1689 PacketMatcher::default(),
1690 Action::Redirect { dst_port: None },
1691 )],
1692 }],
1693 },
1694 egress: Hook {
1695 routines: vec![Routine {
1696 rules: vec![Rule::new(
1697 PacketMatcher::default(),
1698 Action::Masquerade { src_port: None },
1699 )],
1700 }],
1701 },
1702 ..Default::default()
1703 },
1704 HashMap::from([(
1705 FakeMatcherDeviceId::ethernet_interface(),
1706 AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap(),
1707 )]),
1708 );
1709
1710 let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1713 let mut metadata = PacketMetadata::default();
1714 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1715 &mut bindings_ctx,
1716 &mut packet,
1717 &FakeMatcherDeviceId::ethernet_interface(),
1718 &mut metadata,
1719 );
1720 assert_eq!(verdict, Verdict::Accept(()));
1721 assert_eq!(packet.dst_ip, *I::LOOPBACK_ADDRESS);
1722
1723 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1726 &mut bindings_ctx,
1727 &mut packet,
1728 &FakeMatcherDeviceId::ethernet_interface(),
1729 &mut metadata,
1730 );
1731 assert_eq!(verdict, Verdict::Accept(()));
1732 assert_eq!(packet.src_ip, I::SRC_IP_2);
1733 }
1734
1735 #[ip_test(I)]
1736 #[test_case(
1737 Hook {
1738 routines: vec![
1739 Routine {
1740 rules: vec![
1741 Rule::new(
1742 PacketMatcher::default(),
1743 Action::Mark {
1744 domain: MarkDomain::Mark1,
1745 action: MarkAction::SetMark { clearing_mask: 0, mark: 1 },
1746 },
1747 ),
1748 Rule::new(PacketMatcher::default(), Action::Drop),
1749 ],
1750 },
1751 ],
1752 }; "non terminal for routine"
1753 )]
1754 #[test_case(
1755 Hook {
1756 routines: vec![
1757 Routine {
1758 rules: vec![Rule::new(
1759 PacketMatcher::default(),
1760 Action::Mark {
1761 domain: MarkDomain::Mark1,
1762 action: MarkAction::SetMark { clearing_mask: 0, mark: 1 },
1763 },
1764 )],
1765 },
1766 Routine {
1767 rules: vec![
1768 Rule::new(PacketMatcher::default(), Action::Drop),
1769 ],
1770 },
1771 ],
1772 }; "non terminal for hook"
1773 )]
1774 fn mark_action<I: TestIpExt>(ingress: Hook<I, FakeDeviceClass, ()>) {
1775 let mut metadata = PacketMetadata::<I, FakeWeakAddressId<I>, FakeBindingsCtx<I>>::default();
1776 assert_eq!(
1777 check_routines_for_ingress::<I, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
1778 &ingress,
1779 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1780 Interfaces { ingress: None, egress: None },
1781 &mut metadata,
1782 ),
1783 IngressVerdict::Verdict(Verdict::Drop),
1784 );
1785 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1)]));
1786 }
1787
1788 #[ip_test(I)]
1789 fn mark_action_applied_in_succession<I: TestIpExt>() {
1790 fn hook_with_single_mark_action<I: TestIpExt>(
1791 domain: MarkDomain,
1792 action: MarkAction,
1793 ) -> Hook<I, FakeDeviceClass, ()> {
1794 Hook {
1795 routines: vec![Routine {
1796 rules: vec![Rule::new(
1797 PacketMatcher::default(),
1798 Action::Mark { domain, action },
1799 )],
1800 }],
1801 }
1802 }
1803 let mut metadata = PacketMetadata::<I, FakeWeakAddressId<I>, FakeBindingsCtx<I>>::default();
1804 assert_eq!(
1805 check_routines_for_ingress::<I, _, FakeMatcherDeviceId, FakeDeviceClass, _>(
1806 &hook_with_single_mark_action(
1807 MarkDomain::Mark1,
1808 MarkAction::SetMark { clearing_mask: 0, mark: 1 }
1809 ),
1810 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1811 Interfaces { ingress: None, egress: None },
1812 &mut metadata,
1813 ),
1814 IngressVerdict::Verdict(Verdict::Accept(())),
1815 );
1816 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1)]));
1817
1818 assert_eq!(
1819 check_routines_for_hook(
1820 &hook_with_single_mark_action::<I>(
1821 MarkDomain::Mark2,
1822 MarkAction::SetMark { clearing_mask: 0, mark: 1 }
1823 ),
1824 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1825 Interfaces::<FakeMatcherDeviceId> { ingress: None, egress: None },
1826 &mut metadata,
1827 ),
1828 Verdict::Accept(())
1829 );
1830 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1), (MarkDomain::Mark2, 1)]));
1831
1832 assert_eq!(
1833 check_routines_for_hook(
1834 &hook_with_single_mark_action::<I>(
1835 MarkDomain::Mark1,
1836 MarkAction::SetMark { clearing_mask: 1, mark: 2 }
1837 ),
1838 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1839 Interfaces::<FakeMatcherDeviceId> { ingress: None, egress: None },
1840 &mut metadata,
1841 ),
1842 Verdict::Accept(())
1843 );
1844 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 2), (MarkDomain::Mark2, 1)]));
1845 }
1846}