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