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