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