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