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