1pub(crate) mod nat;
6
7use core::fmt::Debug;
8use core::num::NonZeroU16;
9use core::ops::RangeInclusive;
10
11use derivative::Derivative;
12use log::{debug, error};
13use net_types::ip::{GenericOverIp, Ip, IpVersionMarker};
14use netstack3_base::{
15 AnyDevice, DeviceIdContext, HandleableTimer, InterfaceProperties, IpDeviceAddressIdContext,
16};
17use packet_formats::ip::IpExt;
18
19use crate::conntrack::{Connection, FinalizeConnectionError, GetConnectionError};
20use crate::context::{FilterBindingsContext, FilterBindingsTypes, FilterIpContext};
21use crate::packets::{FilterIpExt, FilterIpPacket, MaybeTransportPacket};
22use crate::state::{
23 Action, FilterIpMetadata, FilterPacketMetadata, Hook, RejectType, Routine, Rule,
24 TransparentProxy,
25};
26
27#[derive(Debug, Clone, Copy, PartialEq)]
35pub enum Verdict<S, P = Accept> {
36 Proceed(P),
38 Stop(S),
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct Accept;
46
47impl<S, P> Verdict<S, P> {
48 fn is_stop(&self) -> bool {
49 matches!(self, Verdict::Stop(_))
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq)]
55pub struct DropPacket;
56
57#[derive(Debug, Clone, Copy, PartialEq)]
59pub enum IngressStopReason<I: IpExt> {
60 Drop,
62 TransparentLocalDelivery {
64 addr: I::Addr,
66 port: NonZeroU16,
68 },
69}
70
71#[derive(Debug, Clone, Copy, PartialEq)]
73pub enum DropOrReject {
74 Drop,
76 Reject(RejectType),
78}
79
80pub type IngressVerdict<I> = Verdict<IngressStopReason<I>>;
82
83impl<I: IpExt> From<RoutineResult<I>> for IngressVerdict<I> {
84 fn from(verdict: RoutineResult<I>) -> Self {
85 match verdict {
86 RoutineResult::Accept | RoutineResult::Return => Verdict::Proceed(Accept),
87 RoutineResult::Drop => Verdict::Stop(IngressStopReason::Drop),
88 RoutineResult::TransparentLocalDelivery { addr, port } => {
89 Verdict::Stop(IngressStopReason::TransparentLocalDelivery { addr, port })
90 }
91 result @ (RoutineResult::Redirect { .. } | RoutineResult::Masquerade { .. }) => {
92 unreachable!("NAT actions are only valid in NAT routines; got {result:?}")
93 }
94 RoutineResult::Reject { .. } => {
95 unreachable!("Reject actions are not allowed in ingress routines")
96 }
97 }
98 }
99}
100
101pub type LocalIngressVerdict = Verdict<DropOrReject>;
102pub type ForwardVerdict = Verdict<DropOrReject>;
103pub type EgressVerdict = Verdict<DropPacket>;
104pub type LocalEgressVerdict = Verdict<DropOrReject>;
105
106impl<I: IpExt> From<RoutineResult<I>> for Verdict<DropPacket> {
107 fn from(result: RoutineResult<I>) -> Self {
108 match result {
109 RoutineResult::Accept | RoutineResult::Return => Verdict::Proceed(Accept),
110 RoutineResult::Drop => Verdict::Stop(DropPacket),
111 result @ RoutineResult::TransparentLocalDelivery { .. } => {
112 unreachable!(
113 "transparent local delivery is only valid in INGRESS hook; got {result:?}"
114 )
115 }
116 result @ (RoutineResult::Redirect { .. } | RoutineResult::Masquerade { .. }) => {
117 unreachable!("NAT actions are only valid in NAT routines; got {result:?}")
118 }
119 RoutineResult::Reject(_reject_type) => {
120 unreachable!(
121 "Reject action is allowed only in FORWARD, LOCAL_INGRESS and LOCAL_EGRESS hooks"
122 )
123 }
124 }
125 }
126}
127
128impl<I: IpExt> From<RoutineResult<I>> for Verdict<DropOrReject> {
129 fn from(result: RoutineResult<I>) -> Self {
130 match result {
131 RoutineResult::Accept | RoutineResult::Return => Verdict::Proceed(Accept),
132 RoutineResult::Drop => Verdict::Stop(DropOrReject::Drop),
133 RoutineResult::TransparentLocalDelivery { .. } => {
134 unreachable!(
135 "transparent local delivery is only valid in INGRESS hook; got {result:?}"
136 )
137 }
138 result @ (RoutineResult::Redirect { .. } | RoutineResult::Masquerade { .. }) => {
139 unreachable!("NAT actions are only valid in NAT routines; got {result:?}")
140 }
141 RoutineResult::Reject(reject_type) => Verdict::Stop(DropOrReject::Reject(reject_type)),
142 }
143 }
144}
145
146#[derive(Debug)]
148pub struct ProofOfEgressCheck {
149 _private_field_to_prevent_construction_outside_of_module: (),
150}
151
152impl ProofOfEgressCheck {
153 pub fn clone_for_multiple_frames(&self) -> Self {
159 Self { _private_field_to_prevent_construction_outside_of_module: () }
160 }
161}
162
163#[derive(Debug, Derivative)]
164#[derivative(Clone(bound = ""), Copy(bound = ""))]
165pub struct Interfaces<'a, D> {
167 pub ingress: Option<&'a D>,
170 pub egress: Option<&'a D>,
173}
174
175#[derive(Debug)]
177#[cfg_attr(test, derive(PartialEq, Eq))]
178pub(crate) enum RoutineResult<I: IpExt> {
179 Accept,
182 Return,
184 Drop,
186 TransparentLocalDelivery {
189 addr: I::Addr,
191 port: NonZeroU16,
193 },
194 Redirect {
197 dst_port: Option<RangeInclusive<NonZeroU16>>,
201 },
202 Masquerade {
205 src_port: Option<RangeInclusive<NonZeroU16>>,
209 },
210 Reject(RejectType),
211}
212
213impl<I: IpExt> RoutineResult<I> {
214 fn is_terminal(&self) -> bool {
215 match self {
216 RoutineResult::Accept
217 | RoutineResult::Drop
218 | RoutineResult::TransparentLocalDelivery { .. }
219 | RoutineResult::Redirect { .. }
220 | RoutineResult::Masquerade { .. }
221 | RoutineResult::Reject(_) => true,
222 RoutineResult::Return => false,
223 }
224 }
225}
226
227fn apply_transparent_proxy<I: IpExt, P: MaybeTransportPacket>(
228 proxy: &TransparentProxy<I>,
229 dst_addr: I::Addr,
230 maybe_transport_packet: P,
231) -> RoutineResult<I> {
232 let (addr, port) = match proxy {
233 TransparentProxy::LocalPort(port) => (dst_addr, *port),
234 TransparentProxy::LocalAddr(addr) => {
235 let Some(transport_packet_data) = maybe_transport_packet.transport_packet_data() else {
236 error!(
242 "transparent proxy action is only valid on a rule that matches \
243 on transport protocol, but this packet has no transport header",
244 );
245 return RoutineResult::Drop;
246 };
247 let Some(port) = NonZeroU16::new(transport_packet_data.dst_port()) else {
253 debug!("attempted to TPROXY packet to port 0");
256 return RoutineResult::Drop;
257 };
258 (*addr, port)
259 }
260 TransparentProxy::LocalAddrAndPort(addr, port) => (*addr, *port),
261 };
262 RoutineResult::TransparentLocalDelivery { addr, port }
263}
264
265fn check_routine<I, P, D, BC, M>(
266 Routine { rules }: &Routine<I, BC, ()>,
267 packet: &P,
268 interfaces: Interfaces<'_, D>,
269 metadata: &mut M,
270) -> RoutineResult<I>
271where
272 I: FilterIpExt,
273 P: FilterIpPacket<I>,
274 D: InterfaceProperties<BC::DeviceClass>,
275 BC: FilterBindingsContext<D>,
276 M: FilterPacketMetadata,
277{
278 for Rule { matcher, action, validation_info: () } in rules {
279 if matcher.matches(packet, interfaces, metadata) {
280 match action {
281 Action::Accept => return RoutineResult::Accept,
282 Action::Return => return RoutineResult::Return,
283 Action::Drop => return RoutineResult::Drop,
284 Action::Jump(target) => {
287 let result = check_routine(target.get(), packet, interfaces, metadata);
288 if result.is_terminal() {
289 return result;
290 }
291 continue;
292 }
293 Action::TransparentProxy(proxy) => {
294 return apply_transparent_proxy(
295 proxy,
296 packet.dst_addr(),
297 packet.maybe_transport_packet(),
298 );
299 }
300 Action::Redirect { dst_port } => {
301 return RoutineResult::Redirect { dst_port: dst_port.clone() };
302 }
303 Action::Masquerade { src_port } => {
304 return RoutineResult::Masquerade { src_port: src_port.clone() };
305 }
306 Action::Mark { domain, action } => {
307 metadata.apply_mark_action(*domain, *action);
310 }
311 Action::None => {
312 continue;
313 }
314 Action::Reject(reject_type) => {
315 return RoutineResult::Reject(*reject_type);
316 }
317 }
318 }
319 }
320 RoutineResult::Return
321}
322
323fn check_routines_for_hook<I, P, D, BC, M, SR>(
324 hook: &Hook<I, BC, ()>,
325 packet: &P,
326 interfaces: Interfaces<'_, D>,
327 metadata: &mut M,
328) -> Verdict<SR>
329where
330 I: FilterIpExt,
331 P: FilterIpPacket<I>,
332 D: InterfaceProperties<BC::DeviceClass>,
333 BC: FilterBindingsContext<D>,
334 M: FilterPacketMetadata,
335 Verdict<SR>: From<RoutineResult<I>>,
336{
337 let Hook { routines } = hook;
338 for routine in routines {
339 let verdict: Verdict<SR> = check_routine(&routine, packet, interfaces, metadata).into();
340 match verdict {
341 Verdict::Proceed(Accept) => (),
342 Verdict::Stop(stop_reason) => return Verdict::Stop(stop_reason),
343 }
344 }
345 Verdict::Proceed(Accept)
346}
347
348pub trait FilterHandler<I: FilterIpExt, BC: FilterBindingsTypes>:
351 IpDeviceAddressIdContext<I, DeviceId: InterfaceProperties<BC::DeviceClass>>
352{
353 fn ingress_hook<P, M>(
356 &mut self,
357 bindings_ctx: &mut BC,
358 packet: &mut P,
359 interface: &Self::DeviceId,
360 metadata: &mut M,
361 ) -> IngressVerdict<I>
362 where
363 P: FilterIpPacket<I>,
364 M: FilterIpMetadata<I, Self::WeakAddressId, BC>;
365
366 fn local_ingress_hook<P, M>(
369 &mut self,
370 bindings_ctx: &mut BC,
371 packet: &mut P,
372 interface: &Self::DeviceId,
373 metadata: &mut M,
374 ) -> LocalIngressVerdict
375 where
376 P: FilterIpPacket<I>,
377 M: FilterIpMetadata<I, Self::WeakAddressId, BC>;
378
379 fn forwarding_hook<P, M>(
382 &mut self,
383 packet: &mut P,
384 in_interface: &Self::DeviceId,
385 out_interface: &Self::DeviceId,
386 metadata: &mut M,
387 ) -> ForwardVerdict
388 where
389 P: FilterIpPacket<I>,
390 M: FilterIpMetadata<I, Self::WeakAddressId, BC>;
391
392 fn local_egress_hook<P, M>(
395 &mut self,
396 bindings_ctx: &mut BC,
397 packet: &mut P,
398 interface: &Self::DeviceId,
399 metadata: &mut M,
400 ) -> LocalEgressVerdict
401 where
402 P: FilterIpPacket<I>,
403 M: FilterIpMetadata<I, Self::WeakAddressId, BC>;
404
405 fn egress_hook<P, M>(
408 &mut self,
409 bindings_ctx: &mut BC,
410 packet: &mut P,
411 interface: &Self::DeviceId,
412 metadata: &mut M,
413 ) -> (EgressVerdict, ProofOfEgressCheck)
414 where
415 P: FilterIpPacket<I>,
416 M: FilterIpMetadata<I, Self::WeakAddressId, BC>;
417}
418
419pub struct FilterImpl<'a, CC>(pub &'a mut CC);
424
425impl<CC: DeviceIdContext<AnyDevice>> DeviceIdContext<AnyDevice> for FilterImpl<'_, CC> {
426 type DeviceId = CC::DeviceId;
427 type WeakDeviceId = CC::WeakDeviceId;
428}
429
430impl<I, CC> IpDeviceAddressIdContext<I> for FilterImpl<'_, CC>
431where
432 I: FilterIpExt,
433 CC: IpDeviceAddressIdContext<I>,
434{
435 type AddressId = CC::AddressId;
436 type WeakAddressId = CC::WeakAddressId;
437}
438
439impl<I, BC, CC> FilterHandler<I, BC> for FilterImpl<'_, CC>
440where
441 I: FilterIpExt,
442 BC: FilterBindingsContext<CC::DeviceId>,
443 CC: FilterIpContext<I, BC>,
444{
445 fn ingress_hook<P, M>(
446 &mut self,
447 bindings_ctx: &mut BC,
448 packet: &mut P,
449 interface: &Self::DeviceId,
450 metadata: &mut M,
451 ) -> IngressVerdict<I>
452 where
453 P: FilterIpPacket<I>,
454 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
455 {
456 let Self(this) = self;
457 this.with_filter_state_and_nat_ctx(|state, core_ctx| {
458 let conn = match metadata.take_connection_and_direction() {
462 Some((c, d)) => Some((c, d)),
463 None => {
464 packet.conntrack_packet().and_then(|packet| {
465 match state
466 .conntrack
467 .get_connection_for_packet_and_update(bindings_ctx, packet)
468 {
469 Ok(result) => result,
470 Err(GetConnectionError::InvalidPacket(c, d)) => Some((c, d)),
473 }
474 })
475 }
476 };
477
478 let verdict = check_routines_for_hook(
479 &state.installed_routines.get().ip.ingress,
480 packet,
481 Interfaces { ingress: Some(interface), egress: None },
482 metadata,
483 );
484
485 if verdict.is_stop() {
486 return verdict;
487 }
488
489 if let Some((mut conn, direction)) = conn {
490 match nat::perform_nat::<nat::IngressHook, _, _, _, _, _>(
494 core_ctx,
495 bindings_ctx,
496 state.nat_installed.get(),
497 &state.conntrack,
498 &mut conn,
499 direction,
500 &state.installed_routines.get().nat.ingress,
501 packet,
502 Interfaces { ingress: Some(interface), egress: None },
503 metadata,
504 ) {
505 Verdict::Stop(DropPacket) => return Verdict::Stop(IngressStopReason::Drop),
506 Verdict::Proceed(Accept) => (),
507 }
508
509 let res = metadata.replace_connection_and_direction(conn, direction);
510 debug_assert!(res.is_none());
511 }
512
513 verdict
514 })
515 }
516
517 fn local_ingress_hook<P, M>(
518 &mut self,
519 bindings_ctx: &mut BC,
520 packet: &mut P,
521 interface: &Self::DeviceId,
522 metadata: &mut M,
523 ) -> LocalIngressVerdict
524 where
525 P: FilterIpPacket<I>,
526 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
527 {
528 let Self(this) = self;
529 this.with_filter_state_and_nat_ctx(|state, core_ctx| {
530 let conn = match metadata.take_connection_and_direction() {
531 Some((c, d)) => Some((c, d)),
532 None => packet.conntrack_packet().and_then(|packet| {
536 match state.conntrack.get_connection_for_packet_and_update(bindings_ctx, packet)
537 {
538 Ok(result) => result,
539 Err(GetConnectionError::InvalidPacket(c, d)) => Some((c, d)),
542 }
543 }),
544 };
545
546 let verdict = check_routines_for_hook(
547 &state.installed_routines.get().ip.local_ingress,
548 packet,
549 Interfaces { ingress: Some(interface), egress: None },
550 metadata,
551 );
552
553 if verdict.is_stop() {
554 return verdict;
555 }
556
557 if let Some((mut conn, direction)) = conn {
558 match nat::perform_nat::<nat::LocalIngressHook, _, _, _, _, _>(
562 core_ctx,
563 bindings_ctx,
564 state.nat_installed.get(),
565 &state.conntrack,
566 &mut conn,
567 direction,
568 &state.installed_routines.get().nat.local_ingress,
569 packet,
570 Interfaces { ingress: Some(interface), egress: None },
571 metadata,
572 ) {
573 Verdict::Stop(DropPacket) => return Verdict::Stop(DropOrReject::Drop),
574 Verdict::Proceed(Accept) => (),
575 }
576
577 match state.conntrack.finalize_connection(bindings_ctx, conn) {
578 Ok((_inserted, _weak_conn)) => {}
579 Err(FinalizeConnectionError::Conflict | FinalizeConnectionError::TableFull) => {
582 return Verdict::Stop(DropOrReject::Drop);
583 }
584 }
585 }
586
587 verdict
588 })
589 }
590
591 fn forwarding_hook<P, M>(
592 &mut self,
593 packet: &mut P,
594 in_interface: &Self::DeviceId,
595 out_interface: &Self::DeviceId,
596 metadata: &mut M,
597 ) -> ForwardVerdict
598 where
599 P: FilterIpPacket<I>,
600 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
601 {
602 let Self(this) = self;
603 this.with_filter_state(|state| {
604 check_routines_for_hook(
605 &state.installed_routines.get().ip.forwarding,
606 packet,
607 Interfaces { ingress: Some(in_interface), egress: Some(out_interface) },
608 metadata,
609 )
610 })
611 }
612
613 fn local_egress_hook<P, M>(
614 &mut self,
615 bindings_ctx: &mut BC,
616 packet: &mut P,
617 interface: &Self::DeviceId,
618 metadata: &mut M,
619 ) -> LocalEgressVerdict
620 where
621 P: FilterIpPacket<I>,
622 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
623 {
624 let Self(this) = self;
625 this.with_filter_state_and_nat_ctx(|state, core_ctx| {
626 let conn = packet.conntrack_packet().and_then(|packet| {
629 match state.conntrack.get_connection_for_packet_and_update(bindings_ctx, packet) {
630 Ok(result) => result,
631 Err(GetConnectionError::InvalidPacket(c, d)) => Some((c, d)),
634 }
635 });
636
637 let verdict = check_routines_for_hook(
638 &state.installed_routines.get().ip.local_egress,
639 packet,
640 Interfaces { ingress: None, egress: Some(interface) },
641 metadata,
642 );
643
644 if verdict.is_stop() {
645 return verdict;
646 }
647
648 if let Some((mut conn, direction)) = conn {
649 match nat::perform_nat::<nat::LocalEgressHook, _, _, _, _, _>(
653 core_ctx,
654 bindings_ctx,
655 state.nat_installed.get(),
656 &state.conntrack,
657 &mut conn,
658 direction,
659 &state.installed_routines.get().nat.local_egress,
660 packet,
661 Interfaces { ingress: None, egress: Some(interface) },
662 metadata,
663 ) {
664 Verdict::Stop(DropPacket) => return Verdict::Stop(DropOrReject::Drop),
665 Verdict::Proceed(Accept) => (),
666 }
667
668 let res = metadata.replace_connection_and_direction(conn, direction);
669 debug_assert!(res.is_none());
670 }
671
672 verdict
673 })
674 }
675
676 fn egress_hook<P, M>(
677 &mut self,
678 bindings_ctx: &mut BC,
679 packet: &mut P,
680 interface: &Self::DeviceId,
681 metadata: &mut M,
682 ) -> (EgressVerdict, ProofOfEgressCheck)
683 where
684 P: FilterIpPacket<I>,
685 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
686 {
687 let Self(this) = self;
688 let verdict = this.with_filter_state_and_nat_ctx(|state, core_ctx| {
689 let conn = match metadata.take_connection_and_direction() {
690 Some((c, d)) => Some((c, d)),
691 None => packet.conntrack_packet().and_then(|packet| {
695 match state.conntrack.get_connection_for_packet_and_update(bindings_ctx, packet)
696 {
697 Ok(result) => result,
698 Err(GetConnectionError::InvalidPacket(c, d)) => Some((c, d)),
701 }
702 }),
703 };
704
705 let verdict = check_routines_for_hook(
706 &state.installed_routines.get().ip.egress,
707 packet,
708 Interfaces { ingress: None, egress: Some(interface) },
709 metadata,
710 );
711
712 if verdict.is_stop() {
713 return verdict;
714 }
715
716 if let Some((mut conn, direction)) = conn {
717 match nat::perform_nat::<nat::EgressHook, _, _, _, _, _>(
721 core_ctx,
722 bindings_ctx,
723 state.nat_installed.get(),
724 &state.conntrack,
725 &mut conn,
726 direction,
727 &state.installed_routines.get().nat.egress,
728 packet,
729 Interfaces { ingress: None, egress: Some(interface) },
730 metadata,
731 ) {
732 Verdict::Stop(DropPacket) => return Verdict::Stop(DropPacket),
733 Verdict::Proceed(Accept) => (),
734 }
735
736 match state.conntrack.finalize_connection(bindings_ctx, conn) {
737 Ok((_inserted, conn)) => {
738 if let Some(conn) = conn {
739 let res = metadata.replace_connection_and_direction(
740 Connection::Shared(conn),
741 direction,
742 );
743 debug_assert!(res.is_none());
744 }
745 }
746 Err(FinalizeConnectionError::Conflict | FinalizeConnectionError::TableFull) => {
749 return Verdict::Stop(DropPacket);
750 }
751 }
752 }
753
754 verdict
755 });
756 (
757 verdict,
758 ProofOfEgressCheck { _private_field_to_prevent_construction_outside_of_module: () },
759 )
760 }
761}
762
763#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, GenericOverIp, Hash)]
765#[generic_over_ip(I, Ip)]
766pub enum FilterTimerId<I: Ip> {
767 ConntrackGc(IpVersionMarker<I>),
769}
770
771impl<I, BC, CC> HandleableTimer<CC, BC> for FilterTimerId<I>
772where
773 I: FilterIpExt,
774 BC: FilterBindingsContext<CC::DeviceId>,
775 CC: FilterIpContext<I, BC>,
776{
777 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
778 match self {
779 FilterTimerId::ConntrackGc(_) => core_ctx.with_filter_state(|state| {
780 state.conntrack.perform_gc(bindings_ctx);
781 }),
782 }
783 }
784}
785
786#[cfg(any(test, feature = "testutils"))]
787pub mod testutil {
788 use core::marker::PhantomData;
789
790 use net_types::ip::AddrSubnet;
791 use netstack3_base::AssignedAddrIpExt;
792 use netstack3_base::testutil::{FakeStrongDeviceId, FakeWeakAddressId, FakeWeakDeviceId};
793
794 use super::*;
795
796 pub struct NoopImpl<DeviceId>(PhantomData<DeviceId>);
803
804 impl<DeviceId> Default for NoopImpl<DeviceId> {
805 fn default() -> Self {
806 Self(PhantomData)
807 }
808 }
809
810 impl<DeviceId: FakeStrongDeviceId> DeviceIdContext<AnyDevice> for NoopImpl<DeviceId> {
811 type DeviceId = DeviceId;
812 type WeakDeviceId = FakeWeakDeviceId<DeviceId>;
813 }
814
815 impl<I: AssignedAddrIpExt, DeviceId: FakeStrongDeviceId> IpDeviceAddressIdContext<I>
816 for NoopImpl<DeviceId>
817 {
818 type AddressId = AddrSubnet<I::Addr, I::AssignedWitness>;
819 type WeakAddressId = FakeWeakAddressId<Self::AddressId>;
820 }
821
822 impl<I, BC, DeviceId> FilterHandler<I, BC> for NoopImpl<DeviceId>
823 where
824 I: FilterIpExt + AssignedAddrIpExt,
825 BC: FilterBindingsTypes,
826 DeviceId: FakeStrongDeviceId + InterfaceProperties<BC::DeviceClass>,
827 {
828 fn ingress_hook<P, M>(
829 &mut self,
830 _: &mut BC,
831 _: &mut P,
832 _: &Self::DeviceId,
833 _: &mut M,
834 ) -> IngressVerdict<I>
835 where
836 P: FilterIpPacket<I>,
837 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
838 {
839 Verdict::Proceed(Accept)
840 }
841
842 fn local_ingress_hook<P, M>(
843 &mut self,
844 _: &mut BC,
845 _: &mut P,
846 _: &Self::DeviceId,
847 _: &mut M,
848 ) -> LocalIngressVerdict
849 where
850 P: FilterIpPacket<I>,
851 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
852 {
853 Verdict::Proceed(Accept)
854 }
855
856 fn forwarding_hook<P, M>(
857 &mut self,
858 _: &mut P,
859 _: &Self::DeviceId,
860 _: &Self::DeviceId,
861 _: &mut M,
862 ) -> ForwardVerdict
863 where
864 P: FilterIpPacket<I>,
865 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
866 {
867 Verdict::Proceed(Accept)
868 }
869
870 fn local_egress_hook<P, M>(
871 &mut self,
872 _: &mut BC,
873 _: &mut P,
874 _: &Self::DeviceId,
875 _: &mut M,
876 ) -> LocalEgressVerdict
877 where
878 P: FilterIpPacket<I>,
879 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
880 {
881 Verdict::Proceed(Accept)
882 }
883
884 fn egress_hook<P, M>(
885 &mut self,
886 _: &mut BC,
887 _: &mut P,
888 _: &Self::DeviceId,
889 _: &mut M,
890 ) -> (EgressVerdict, ProofOfEgressCheck)
891 where
892 P: FilterIpPacket<I>,
893 M: FilterIpMetadata<I, Self::WeakAddressId, BC>,
894 {
895 (Verdict::Proceed(Accept), ProofOfEgressCheck::forge_proof_for_test())
896 }
897 }
898
899 impl ProofOfEgressCheck {
900 pub(crate) fn forge_proof_for_test() -> Self {
902 ProofOfEgressCheck { _private_field_to_prevent_construction_outside_of_module: () }
903 }
904 }
905}
906
907#[cfg(test)]
908mod tests {
909 use alloc::sync::Arc;
910 use alloc::vec;
911 use alloc::vec::Vec;
912
913 use assert_matches::assert_matches;
914 use derivative::Derivative;
915 use ip_test_macro::ip_test;
916 use net_types::ip::{AddrSubnet, Ipv4};
917 use netstack3_base::testutil::{FakeDeviceClass, FakeMatcherDeviceId};
918 use netstack3_base::{
919 AddressMatcher, AddressMatcherType, AssignedAddrIpExt, InterfaceMatcher, MarkDomain, Marks,
920 PortMatcher, SegmentHeader,
921 };
922 use test_case::test_case;
923
924 use super::*;
925 use crate::actions::MarkAction;
926 use crate::conntrack::{self, ConnectionDirection};
927 use crate::context::testutil::{FakeBindingsCtx, FakeCtx, FakeWeakAddressId};
928 use crate::logic::nat::NatConfig;
929 use crate::matchers::{PacketMatcher, TransportProtocolMatcher};
930 use crate::packets::IpPacket;
931 use crate::packets::testutil::internal::{
932 ArbitraryValue, FakeIpPacket, FakeTcpSegment, FakeUdpPacket, TransportPacketExt,
933 };
934 use crate::state::testutil::FakePacketMetadata;
935 use crate::state::{IpRoutines, NatRoutines, UninstalledRoutine};
936 use crate::testutil::TestIpExt;
937
938 impl<I: IpExt> Rule<I, FakeBindingsCtx<I>, ()> {
939 pub(crate) fn new(
940 matcher: PacketMatcher<I, FakeBindingsCtx<I>>,
941 action: Action<I, FakeBindingsCtx<I>, ()>,
942 ) -> Self {
943 Rule { matcher, action, validation_info: () }
944 }
945 }
946
947 #[test]
948 fn return_by_default_if_no_matching_rules_in_routine() {
949 assert_eq!(
950 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
951 &Routine { rules: Vec::new() },
952 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
953 Interfaces { ingress: None, egress: None },
954 &mut FakePacketMetadata::default(),
955 ),
956 RoutineResult::Return
957 );
958
959 let routine = Routine {
962 rules: vec![
963 Rule::new(
964 PacketMatcher::default(),
965 Action::Jump(UninstalledRoutine::new(Vec::new(), 0)),
966 ),
967 Rule::new(PacketMatcher::default(), Action::Drop),
968 ],
969 };
970 assert_eq!(
971 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
972 &routine,
973 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
974 Interfaces { ingress: None, egress: None },
975 &mut FakePacketMetadata::default(),
976 ),
977 RoutineResult::Drop
978 );
979 }
980
981 #[derive(Derivative)]
982 #[derivative(Default(bound = ""))]
983 struct PacketMetadata<I: IpExt + AssignedAddrIpExt, A, BT: FilterBindingsTypes> {
984 conn: Option<(Connection<I, NatConfig<I, A>, BT>, ConnectionDirection)>,
985 marks: Marks,
986 }
987
988 impl<I: TestIpExt, A, BT: FilterBindingsTypes> FilterIpMetadata<I, A, BT>
989 for PacketMetadata<I, A, BT>
990 {
991 fn take_connection_and_direction(
992 &mut self,
993 ) -> Option<(Connection<I, NatConfig<I, A>, BT>, ConnectionDirection)> {
994 let Self { conn, marks: _ } = self;
995 conn.take()
996 }
997
998 fn replace_connection_and_direction(
999 &mut self,
1000 new_conn: Connection<I, NatConfig<I, A>, BT>,
1001 direction: ConnectionDirection,
1002 ) -> Option<Connection<I, NatConfig<I, A>, BT>> {
1003 let Self { conn, marks: _ } = self;
1004 conn.replace((new_conn, direction)).map(|(conn, _dir)| conn)
1005 }
1006 }
1007
1008 impl<I, A, BT> FilterPacketMetadata for PacketMetadata<I, A, BT>
1009 where
1010 I: TestIpExt,
1011 BT: FilterBindingsTypes,
1012 {
1013 fn apply_mark_action(&mut self, domain: MarkDomain, action: MarkAction) {
1014 action.apply(self.marks.get_mut(domain))
1015 }
1016
1017 fn socket_info(&self) -> Option<crate::SocketInfo> {
1018 None
1019 }
1020
1021 fn marks(&self) -> &Marks {
1022 &self.marks
1023 }
1024 }
1025
1026 #[test]
1027 fn accept_by_default_if_no_matching_rules_in_hook() {
1028 assert_eq!(
1029 check_routines_for_hook::<
1030 Ipv4,
1031 _,
1032 FakeMatcherDeviceId,
1033 FakeBindingsCtx<Ipv4>,
1034 _,
1035 DropPacket,
1036 >(
1037 &Hook::default(),
1038 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1039 Interfaces { ingress: None, egress: None },
1040 &mut FakePacketMetadata::default(),
1041 ),
1042 Verdict::Proceed(Accept)
1043 );
1044 }
1045
1046 #[test]
1047 fn accept_by_default_if_return_from_routine() {
1048 let hook = Hook {
1049 routines: vec![Routine {
1050 rules: vec![Rule::new(PacketMatcher::default(), Action::Return)],
1051 }],
1052 };
1053
1054 assert_eq!(
1055 check_routines_for_hook::<
1056 Ipv4,
1057 _,
1058 FakeMatcherDeviceId,
1059 FakeBindingsCtx<Ipv4>,
1060 _,
1061 DropPacket,
1062 >(
1063 &hook,
1064 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1065 Interfaces { ingress: None, egress: None },
1066 &mut FakePacketMetadata::default(),
1067 ),
1068 Verdict::Proceed(Accept)
1069 );
1070 }
1071
1072 #[test]
1073 fn accept_terminal_for_installed_routine() {
1074 let routine = Routine {
1075 rules: vec![
1076 Rule::new(PacketMatcher::default(), Action::Accept),
1078 Rule::new(PacketMatcher::default(), Action::Drop),
1080 ],
1081 };
1082 assert_eq!(
1083 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1084 &routine,
1085 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1086 Interfaces { ingress: None, egress: None },
1087 &mut FakePacketMetadata::default(),
1088 ),
1089 RoutineResult::Accept
1090 );
1091
1092 let routine = Routine {
1094 rules: vec![
1095 Rule::new(
1097 PacketMatcher::default(),
1098 Action::Jump(UninstalledRoutine::new(
1099 vec![Rule::new(PacketMatcher::default(), Action::Accept)],
1100 0,
1101 )),
1102 ),
1103 Rule::new(PacketMatcher::default(), Action::Drop),
1105 ],
1106 };
1107 assert_eq!(
1108 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1109 &routine,
1110 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1111 Interfaces { ingress: None, egress: None },
1112 &mut FakePacketMetadata::default(),
1113 ),
1114 RoutineResult::Accept
1115 );
1116
1117 let hook = Hook {
1122 routines: vec![
1123 routine,
1124 Routine {
1125 rules: vec![
1126 Rule::new(PacketMatcher::default(), Action::Drop),
1128 ],
1129 },
1130 ],
1131 };
1132
1133 assert_eq!(
1134 check_routines_for_hook::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _, _>(
1135 &hook,
1136 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1137 Interfaces { ingress: None, egress: None },
1138 &mut FakePacketMetadata::default(),
1139 ),
1140 Verdict::Stop(DropPacket)
1141 );
1142 }
1143
1144 #[test]
1145 fn drop_terminal_for_entire_hook() {
1146 let hook = Hook {
1147 routines: vec![
1148 Routine {
1149 rules: vec![
1150 Rule::new(PacketMatcher::default(), Action::Drop),
1152 ],
1153 },
1154 Routine {
1155 rules: vec![
1156 Rule::new(PacketMatcher::default(), Action::Accept),
1158 ],
1159 },
1160 ],
1161 };
1162
1163 assert_eq!(
1164 check_routines_for_hook::<
1165 Ipv4,
1166 _,
1167 FakeMatcherDeviceId,
1168 FakeBindingsCtx<Ipv4>,
1169 _,
1170 DropPacket,
1171 >(
1172 &hook,
1173 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1174 Interfaces { ingress: None, egress: None },
1175 &mut FakePacketMetadata::default(),
1176 ),
1177 Verdict::Stop(DropPacket)
1178 );
1179 }
1180
1181 #[test]
1182 fn transparent_proxy_terminal_for_entire_hook() {
1183 const TPROXY_PORT: NonZeroU16 = NonZeroU16::new(8080).unwrap();
1184
1185 let ingress = Hook {
1186 routines: vec![
1187 Routine {
1188 rules: vec![Rule::new(
1189 PacketMatcher::default(),
1190 Action::TransparentProxy(TransparentProxy::LocalPort(TPROXY_PORT)),
1191 )],
1192 },
1193 Routine {
1194 rules: vec![
1195 Rule::new(PacketMatcher::default(), Action::Accept),
1197 ],
1198 },
1199 ],
1200 };
1201
1202 assert_eq!(
1203 check_routines_for_hook::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _, _>(
1204 &ingress,
1205 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1206 Interfaces { ingress: None, egress: None },
1207 &mut FakePacketMetadata::default(),
1208 ),
1209 IngressVerdict::Stop(IngressStopReason::TransparentLocalDelivery {
1210 addr: <Ipv4 as crate::packets::testutil::internal::TestIpExt>::DST_IP,
1211 port: TPROXY_PORT
1212 })
1213 );
1214 }
1215
1216 #[test]
1217 fn jump_recursively_evaluates_target_routine() {
1218 let routine = Routine {
1221 rules: vec![Rule::new(
1222 PacketMatcher::default(),
1223 Action::Jump(UninstalledRoutine::new(
1224 vec![Rule::new(PacketMatcher::default(), Action::Drop)],
1225 0,
1226 )),
1227 )],
1228 };
1229 assert_eq!(
1230 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1231 &routine,
1232 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1233 Interfaces { ingress: None, egress: None },
1234 &mut FakePacketMetadata::default(),
1235 ),
1236 RoutineResult::Drop
1237 );
1238
1239 let routine = Routine {
1242 rules: vec![
1243 Rule::new(
1244 PacketMatcher::default(),
1245 Action::Jump(UninstalledRoutine::new(
1246 vec![Rule::new(PacketMatcher::default(), Action::Accept)],
1247 0,
1248 )),
1249 ),
1250 Rule::new(PacketMatcher::default(), Action::Drop),
1251 ],
1252 };
1253 assert_eq!(
1254 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1255 &routine,
1256 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1257 Interfaces { ingress: None, egress: None },
1258 &mut FakePacketMetadata::default(),
1259 ),
1260 RoutineResult::Accept
1261 );
1262
1263 let routine = Routine {
1266 rules: vec![
1267 Rule::new(
1268 PacketMatcher::default(),
1269 Action::Jump(UninstalledRoutine::new(
1270 vec![Rule::new(PacketMatcher::default(), Action::Return)],
1271 0,
1272 )),
1273 ),
1274 Rule::new(PacketMatcher::default(), Action::Drop),
1275 ],
1276 };
1277 assert_eq!(
1278 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1279 &routine,
1280 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1281 Interfaces { ingress: None, egress: None },
1282 &mut FakePacketMetadata::default(),
1283 ),
1284 RoutineResult::Drop
1285 );
1286 }
1287
1288 #[test]
1289 fn return_terminal_for_single_routine() {
1290 let routine = Routine {
1291 rules: vec![
1292 Rule::new(PacketMatcher::default(), Action::Return),
1293 Rule::new(PacketMatcher::default(), Action::Drop),
1295 ],
1296 };
1297
1298 assert_eq!(
1299 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1300 &routine,
1301 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1302 Interfaces { ingress: None, egress: None },
1303 &mut FakePacketMetadata::default(),
1304 ),
1305 RoutineResult::Return
1306 );
1307 }
1308
1309 #[ip_test(I)]
1310 fn filter_handler_implements_ip_hooks_correctly<I: TestIpExt>() {
1311 fn drop_all_traffic<I: TestIpExt>(
1312 matcher: PacketMatcher<I, FakeBindingsCtx<I>>,
1313 ) -> Hook<I, FakeBindingsCtx<I>, ()> {
1314 Hook { routines: vec![Routine { rules: vec![Rule::new(matcher, Action::Drop)] }] }
1315 }
1316
1317 let mut bindings_ctx = FakeBindingsCtx::new();
1318
1319 let mut ctx = FakeCtx::with_ip_routines(
1322 &mut bindings_ctx,
1323 IpRoutines {
1324 ingress: drop_all_traffic(PacketMatcher {
1325 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1326 ..Default::default()
1327 }),
1328 ..Default::default()
1329 },
1330 );
1331 assert_eq!(
1332 FilterImpl(&mut ctx).ingress_hook(
1333 &mut bindings_ctx,
1334 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1335 &FakeMatcherDeviceId::wlan_interface(),
1336 &mut FakePacketMetadata::default(),
1337 ),
1338 Verdict::Stop(IngressStopReason::Drop)
1339 );
1340
1341 let mut ctx = FakeCtx::with_ip_routines(
1344 &mut bindings_ctx,
1345 IpRoutines {
1346 local_ingress: drop_all_traffic(PacketMatcher {
1347 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1348 ..Default::default()
1349 }),
1350 ..Default::default()
1351 },
1352 );
1353 assert_eq!(
1354 FilterImpl(&mut ctx).local_ingress_hook(
1355 &mut bindings_ctx,
1356 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1357 &FakeMatcherDeviceId::wlan_interface(),
1358 &mut FakePacketMetadata::default(),
1359 ),
1360 Verdict::Stop(DropOrReject::Drop)
1361 );
1362
1363 let mut ctx = FakeCtx::with_ip_routines(
1366 &mut bindings_ctx,
1367 IpRoutines {
1368 forwarding: drop_all_traffic(PacketMatcher {
1369 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1370 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Ethernet)),
1371 ..Default::default()
1372 }),
1373 ..Default::default()
1374 },
1375 );
1376 assert_eq!(
1377 FilterImpl(&mut ctx).forwarding_hook(
1378 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1379 &FakeMatcherDeviceId::wlan_interface(),
1380 &FakeMatcherDeviceId::ethernet_interface(),
1381 &mut FakePacketMetadata::default(),
1382 ),
1383 Verdict::Stop(DropOrReject::Drop)
1384 );
1385
1386 let mut ctx = FakeCtx::with_ip_routines(
1389 &mut bindings_ctx,
1390 IpRoutines {
1391 local_egress: drop_all_traffic(PacketMatcher {
1392 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1393 ..Default::default()
1394 }),
1395 ..Default::default()
1396 },
1397 );
1398 assert_eq!(
1399 FilterImpl(&mut ctx).local_egress_hook(
1400 &mut bindings_ctx,
1401 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1402 &FakeMatcherDeviceId::wlan_interface(),
1403 &mut FakePacketMetadata::default(),
1404 ),
1405 Verdict::Stop(DropOrReject::Drop)
1406 );
1407
1408 let mut ctx = FakeCtx::with_ip_routines(
1411 &mut bindings_ctx,
1412 IpRoutines {
1413 egress: drop_all_traffic(PacketMatcher {
1414 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1415 ..Default::default()
1416 }),
1417 ..Default::default()
1418 },
1419 );
1420 assert_eq!(
1421 FilterImpl(&mut ctx)
1422 .egress_hook(
1423 &mut bindings_ctx,
1424 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1425 &FakeMatcherDeviceId::wlan_interface(),
1426 &mut FakePacketMetadata::default(),
1427 )
1428 .0,
1429 Verdict::Stop(DropPacket)
1430 );
1431 }
1432
1433 #[ip_test(I)]
1434 #[test_case(22 => Verdict::Proceed(Accept); "port 22 allowed for SSH")]
1435 #[test_case(80 => Verdict::Proceed(Accept); "port 80 allowed for HTTP")]
1436 #[test_case(1024 => Verdict::Proceed(Accept); "ephemeral port 1024 allowed")]
1437 #[test_case(65535 => Verdict::Proceed(Accept); "ephemeral port 65535 allowed")]
1438 #[test_case(1023 => Verdict::Stop(DropOrReject::Drop); "privileged port 1023 blocked")]
1439 #[test_case(53 => Verdict::Stop(DropOrReject::Drop); "privileged port 53 blocked")]
1440 fn block_privileged_ports_except_ssh_http<I: TestIpExt>(port: u16) -> Verdict<DropOrReject> {
1441 fn tcp_port_rule<I: FilterIpExt>(
1442 src_port: Option<PortMatcher>,
1443 dst_port: Option<PortMatcher>,
1444 action: Action<I, FakeBindingsCtx<I>, ()>,
1445 ) -> Rule<I, FakeBindingsCtx<I>, ()> {
1446 Rule::new(
1447 PacketMatcher {
1448 transport_protocol: Some(TransportProtocolMatcher {
1449 proto: <&FakeTcpSegment as TransportPacketExt<I>>::proto().unwrap(),
1450 src_port,
1451 dst_port,
1452 }),
1453 ..Default::default()
1454 },
1455 action,
1456 )
1457 }
1458
1459 fn default_filter_rules<I: FilterIpExt>() -> Routine<I, FakeBindingsCtx<I>, ()> {
1460 Routine {
1461 rules: vec![
1462 tcp_port_rule(
1464 None,
1465 Some(PortMatcher { range: 22..=22, invert: false }),
1466 Action::Accept,
1467 ),
1468 tcp_port_rule(
1470 None,
1471 Some(PortMatcher { range: 80..=80, invert: false }),
1472 Action::Accept,
1473 ),
1474 tcp_port_rule(
1476 None,
1477 Some(PortMatcher { range: 1024..=65535, invert: false }),
1478 Action::Accept,
1479 ),
1480 tcp_port_rule(
1482 None,
1483 Some(PortMatcher { range: 1..=65535, invert: false }),
1484 Action::Drop,
1485 ),
1486 ],
1487 }
1488 }
1489
1490 let mut bindings_ctx = FakeBindingsCtx::new();
1491
1492 let mut ctx = FakeCtx::with_ip_routines(
1493 &mut bindings_ctx,
1494 IpRoutines {
1495 local_ingress: Hook { routines: vec![default_filter_rules()] },
1496 ..Default::default()
1497 },
1498 );
1499
1500 FilterImpl(&mut ctx).local_ingress_hook(
1501 &mut bindings_ctx,
1502 &mut FakeIpPacket::<I, _> {
1503 body: FakeTcpSegment {
1504 dst_port: port,
1505 src_port: 11111,
1506 segment: SegmentHeader::arbitrary_value(),
1507 payload_len: 8888,
1508 },
1509 ..ArbitraryValue::arbitrary_value()
1510 },
1511 &FakeMatcherDeviceId::wlan_interface(),
1512 &mut FakePacketMetadata::default(),
1513 )
1514 }
1515
1516 #[ip_test(I)]
1517 #[test_case(
1518 FakeMatcherDeviceId::ethernet_interface() => Verdict::Proceed(Accept);
1519 "allow incoming traffic on ethernet interface"
1520 )]
1521 #[test_case(
1522 FakeMatcherDeviceId::wlan_interface() => Verdict::Stop(DropOrReject::Drop);
1523 "drop incoming traffic on wlan interface"
1524 )]
1525 fn filter_on_wlan_only<I: TestIpExt>(interface: FakeMatcherDeviceId) -> Verdict<DropOrReject> {
1526 fn drop_wlan_traffic<I: IpExt>() -> Routine<I, FakeBindingsCtx<I>, ()> {
1527 Routine {
1528 rules: vec![Rule::new(
1529 PacketMatcher {
1530 in_interface: Some(InterfaceMatcher::Id(
1531 FakeMatcherDeviceId::wlan_interface().id,
1532 )),
1533 ..Default::default()
1534 },
1535 Action::Drop,
1536 )],
1537 }
1538 }
1539
1540 let mut bindings_ctx = FakeBindingsCtx::new();
1541
1542 let mut ctx = FakeCtx::with_ip_routines(
1543 &mut bindings_ctx,
1544 IpRoutines {
1545 local_ingress: Hook { routines: vec![drop_wlan_traffic()] },
1546 ..Default::default()
1547 },
1548 );
1549
1550 FilterImpl(&mut ctx).local_ingress_hook(
1551 &mut bindings_ctx,
1552 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1553 &interface,
1554 &mut FakePacketMetadata::default(),
1555 )
1556 }
1557
1558 #[test]
1559 fn ingress_reuses_cached_connection_when_available() {
1560 let mut bindings_ctx = FakeBindingsCtx::new();
1561 let mut core_ctx = FakeCtx::new(&mut bindings_ctx);
1562
1563 let mut packet = FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value();
1566 let mut metadata = PacketMetadata::default();
1567 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1568 &mut bindings_ctx,
1569 &mut packet,
1570 &FakeMatcherDeviceId::ethernet_interface(),
1571 &mut metadata,
1572 );
1573 assert_eq!(verdict, Verdict::Proceed(Accept));
1574
1575 let (stashed, _dir) =
1577 metadata.take_connection_and_direction().expect("metadata should include connection");
1578 let tuple = packet.conntrack_packet().expect("packet should be trackable").tuple();
1579 let table = core_ctx
1580 .conntrack()
1581 .get_connection(&tuple)
1582 .expect("packet should be inserted in table");
1583 assert_matches!(
1584 (table, stashed),
1585 (Connection::Shared(table), Connection::Shared(stashed)) => {
1586 assert!(Arc::ptr_eq(&table, &stashed));
1587 }
1588 );
1589
1590 let verdict = FilterImpl(&mut core_ctx).ingress_hook(
1593 &mut bindings_ctx,
1594 &mut packet,
1595 &FakeMatcherDeviceId::ethernet_interface(),
1596 &mut metadata,
1597 );
1598 assert_eq!(verdict, Verdict::Proceed(Accept));
1599
1600 let (after_ingress, _dir) =
1603 metadata.take_connection_and_direction().expect("metadata should include connection");
1604 let table = core_ctx
1605 .conntrack()
1606 .get_connection(&tuple)
1607 .expect("packet should be inserted in table");
1608 assert_matches!(
1609 (table, after_ingress),
1610 (Connection::Shared(before), Connection::Shared(after)) => {
1611 assert!(Arc::ptr_eq(&before, &after));
1612 }
1613 );
1614 }
1615
1616 #[ip_test(I)]
1617 fn drop_packet_on_finalize_connection_failure<I: TestIpExt>() {
1618 let mut bindings_ctx = FakeBindingsCtx::new();
1619 let mut ctx = FakeCtx::new(&mut bindings_ctx);
1620
1621 for i in 0..u32::try_from(conntrack::MAXIMUM_ENTRIES / 2).unwrap() {
1622 let (mut packet, mut reply_packet) = conntrack::testutils::make_test_udp_packets(i);
1623 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1624 &mut bindings_ctx,
1625 &mut packet,
1626 &FakeMatcherDeviceId::ethernet_interface(),
1627 &mut FakePacketMetadata::default(),
1628 );
1629 assert_eq!(verdict, Verdict::Proceed(Accept));
1630
1631 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1632 &mut bindings_ctx,
1633 &mut reply_packet,
1634 &FakeMatcherDeviceId::ethernet_interface(),
1635 &mut FakePacketMetadata::default(),
1636 );
1637 assert_eq!(verdict, Verdict::Proceed(Accept));
1638
1639 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1640 &mut bindings_ctx,
1641 &mut packet,
1642 &FakeMatcherDeviceId::ethernet_interface(),
1643 &mut FakePacketMetadata::default(),
1644 );
1645 assert_eq!(verdict, Verdict::Proceed(Accept));
1646 }
1647
1648 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1652 &mut bindings_ctx,
1653 &mut FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value(),
1654 &FakeMatcherDeviceId::ethernet_interface(),
1655 &mut FakePacketMetadata::default(),
1656 );
1657 assert_eq!(verdict, Verdict::Stop(DropPacket));
1658 }
1659
1660 #[ip_test(I)]
1661 fn implicit_snat_to_prevent_tuple_clash<I: TestIpExt>() {
1662 let mut bindings_ctx = FakeBindingsCtx::new();
1663 let mut ctx = FakeCtx::with_nat_routines_and_device_addrs(
1664 &mut bindings_ctx,
1665 NatRoutines {
1666 egress: Hook {
1667 routines: vec![Routine {
1668 rules: vec![Rule::new(
1669 PacketMatcher {
1670 src_address: Some(AddressMatcher {
1671 matcher: AddressMatcherType::Range(I::SRC_IP_2..=I::SRC_IP_2),
1672 invert: false,
1673 }),
1674 ..Default::default()
1675 },
1676 Action::Masquerade { src_port: None },
1677 )],
1678 }],
1679 },
1680 ..Default::default()
1681 },
1682 [(
1683 FakeMatcherDeviceId::ethernet_interface(),
1684 AddrSubnet::new(I::SRC_IP, I::SUBNET.prefix()).unwrap(),
1685 )],
1686 );
1687
1688 let mut packet = FakeIpPacket {
1691 src_ip: I::SRC_IP_2,
1692 dst_ip: I::DST_IP,
1693 body: FakeUdpPacket::arbitrary_value(),
1694 };
1695 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1696 &mut bindings_ctx,
1697 &mut packet,
1698 &FakeMatcherDeviceId::ethernet_interface(),
1699 &mut FakePacketMetadata::default(),
1700 );
1701 assert_eq!(verdict, Verdict::Proceed(Accept));
1702 assert_eq!(packet.src_ip, I::SRC_IP);
1703
1704 let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1711 let src_port = packet.body.src_port;
1712 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1713 &mut bindings_ctx,
1714 &mut packet,
1715 &FakeMatcherDeviceId::ethernet_interface(),
1716 &mut FakePacketMetadata::default(),
1717 );
1718 assert_eq!(verdict, Verdict::Proceed(Accept));
1719 assert_ne!(packet.body.src_port, src_port);
1720 }
1721
1722 #[ip_test(I)]
1723 fn packet_adopts_tracked_connection_in_table_if_identical<I: TestIpExt>() {
1724 let mut bindings_ctx = FakeBindingsCtx::new();
1725 let mut core_ctx = FakeCtx::new(&mut bindings_ctx);
1726
1727 let mut first_packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1730 let mut first_metadata = PacketMetadata::default();
1731 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1732 &mut bindings_ctx,
1733 &mut first_packet,
1734 &FakeMatcherDeviceId::ethernet_interface(),
1735 &mut first_metadata,
1736 );
1737 assert_eq!(verdict, Verdict::Proceed(Accept));
1738
1739 let mut second_packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1740 let mut second_metadata = PacketMetadata::default();
1741 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1742 &mut bindings_ctx,
1743 &mut second_packet,
1744 &FakeMatcherDeviceId::ethernet_interface(),
1745 &mut second_metadata,
1746 );
1747 assert_eq!(verdict, Verdict::Proceed(Accept));
1748
1749 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1751 &mut bindings_ctx,
1752 &mut first_packet,
1753 &FakeMatcherDeviceId::ethernet_interface(),
1754 &mut first_metadata,
1755 );
1756 assert_eq!(verdict, Verdict::Proceed(Accept));
1757
1758 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1761 &mut bindings_ctx,
1762 &mut second_packet,
1763 &FakeMatcherDeviceId::ethernet_interface(),
1764 &mut second_metadata,
1765 );
1766 assert_eq!(second_packet.body.src_port, first_packet.body.src_port);
1767 assert_eq!(verdict, Verdict::Proceed(Accept));
1768
1769 let (first_conn, _dir) = first_metadata.take_connection_and_direction().unwrap();
1770 let (second_conn, _dir) = second_metadata.take_connection_and_direction().unwrap();
1771 assert_matches!(
1772 (first_conn, second_conn),
1773 (Connection::Shared(first), Connection::Shared(second)) => {
1774 assert!(Arc::ptr_eq(&first, &second));
1775 }
1776 );
1777 }
1778
1779 #[ip_test(I)]
1780 fn both_source_and_destination_nat_configured<I: TestIpExt>() {
1781 let mut bindings_ctx = FakeBindingsCtx::new();
1782 let mut core_ctx = FakeCtx::with_nat_routines_and_device_addrs(
1785 &mut bindings_ctx,
1786 NatRoutines {
1787 local_egress: Hook {
1788 routines: vec![Routine {
1789 rules: vec![Rule::new(
1790 PacketMatcher::default(),
1791 Action::Redirect { dst_port: None },
1792 )],
1793 }],
1794 },
1795 egress: Hook {
1796 routines: vec![Routine {
1797 rules: vec![Rule::new(
1798 PacketMatcher::default(),
1799 Action::Masquerade { src_port: None },
1800 )],
1801 }],
1802 },
1803 ..Default::default()
1804 },
1805 [(
1806 FakeMatcherDeviceId::ethernet_interface(),
1807 AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap(),
1808 )],
1809 );
1810
1811 let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1814 let mut metadata = PacketMetadata::default();
1815 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1816 &mut bindings_ctx,
1817 &mut packet,
1818 &FakeMatcherDeviceId::ethernet_interface(),
1819 &mut metadata,
1820 );
1821 assert_eq!(verdict, Verdict::Proceed(Accept));
1822 assert_eq!(packet.dst_ip, *I::LOOPBACK_ADDRESS);
1823
1824 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1827 &mut bindings_ctx,
1828 &mut packet,
1829 &FakeMatcherDeviceId::ethernet_interface(),
1830 &mut metadata,
1831 );
1832 assert_eq!(verdict, Verdict::Proceed(Accept));
1833 assert_eq!(packet.src_ip, I::SRC_IP_2);
1834 }
1835
1836 #[ip_test(I)]
1837 #[test_case(
1838 Hook {
1839 routines: vec![
1840 Routine {
1841 rules: vec![
1842 Rule::new(
1843 PacketMatcher::default(),
1844 Action::Mark {
1845 domain: MarkDomain::Mark1,
1846 action: MarkAction::SetMark { clearing_mask: 0, mark: 1 },
1847 },
1848 ),
1849 Rule::new(PacketMatcher::default(), Action::Drop),
1850 ],
1851 },
1852 ],
1853 }; "non terminal for routine"
1854 )]
1855 #[test_case(
1856 Hook {
1857 routines: vec![
1858 Routine {
1859 rules: vec![Rule::new(
1860 PacketMatcher::default(),
1861 Action::Mark {
1862 domain: MarkDomain::Mark1,
1863 action: MarkAction::SetMark { clearing_mask: 0, mark: 1 },
1864 },
1865 )],
1866 },
1867 Routine {
1868 rules: vec![
1869 Rule::new(PacketMatcher::default(), Action::Drop),
1870 ],
1871 },
1872 ],
1873 }; "non terminal for hook"
1874 )]
1875 fn mark_action<I: TestIpExt>(ingress: Hook<I, FakeBindingsCtx<I>, ()>) {
1876 let mut metadata = PacketMetadata::<I, FakeWeakAddressId<I>, FakeBindingsCtx<I>>::default();
1877 assert_eq!(
1878 check_routines_for_hook::<I, _, FakeMatcherDeviceId, FakeBindingsCtx<I>, _, _>(
1879 &ingress,
1880 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1881 Interfaces { ingress: None, egress: None },
1882 &mut metadata,
1883 ),
1884 IngressVerdict::Stop(IngressStopReason::Drop),
1885 );
1886 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1)]));
1887 }
1888
1889 #[ip_test(I)]
1890 fn mark_action_applied_in_succession<I: TestIpExt>() {
1891 fn hook_with_single_mark_action<I: TestIpExt>(
1892 domain: MarkDomain,
1893 action: MarkAction,
1894 ) -> Hook<I, FakeBindingsCtx<I>, ()> {
1895 Hook {
1896 routines: vec![Routine {
1897 rules: vec![Rule::new(
1898 PacketMatcher::default(),
1899 Action::Mark { domain, action },
1900 )],
1901 }],
1902 }
1903 }
1904 let mut metadata = PacketMetadata::<I, FakeWeakAddressId<I>, FakeBindingsCtx<I>>::default();
1905 assert_eq!(
1906 check_routines_for_hook::<I, _, FakeMatcherDeviceId, FakeBindingsCtx<I>, _, _>(
1907 &hook_with_single_mark_action(
1908 MarkDomain::Mark1,
1909 MarkAction::SetMark { clearing_mask: 0, mark: 1 }
1910 ),
1911 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1912 Interfaces { ingress: None, egress: None },
1913 &mut metadata,
1914 ),
1915 IngressVerdict::Proceed(Accept),
1916 );
1917 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1)]));
1918
1919 assert_eq!(
1920 check_routines_for_hook(
1921 &hook_with_single_mark_action::<I>(
1922 MarkDomain::Mark2,
1923 MarkAction::SetMark { clearing_mask: 0, mark: 1 }
1924 ),
1925 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1926 Interfaces::<FakeMatcherDeviceId> { ingress: None, egress: None },
1927 &mut metadata,
1928 ),
1929 IngressVerdict::Proceed(Accept)
1930 );
1931 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1), (MarkDomain::Mark2, 1)]));
1932
1933 assert_eq!(
1934 check_routines_for_hook(
1935 &hook_with_single_mark_action::<I>(
1936 MarkDomain::Mark1,
1937 MarkAction::SetMark { clearing_mask: 1, mark: 2 }
1938 ),
1939 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1940 Interfaces::<FakeMatcherDeviceId> { ingress: None, egress: None },
1941 &mut metadata,
1942 ),
1943 IngressVerdict::Proceed(Accept)
1944 );
1945 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 2), (MarkDomain::Mark2, 1)]));
1946 }
1947
1948 #[ip_test(I)]
1950 fn transparent_proxy_drop_on_port_0<I: TestIpExt>() {
1951 let ingress = Hook {
1952 routines: vec![Routine {
1953 rules: vec![Rule::new(
1954 PacketMatcher::default(),
1955 Action::TransparentProxy(TransparentProxy::LocalAddr(I::DST_IP)),
1956 )],
1957 }],
1958 };
1959
1960 let packet = FakeIpPacket::<I, FakeTcpSegment> {
1961 body: FakeTcpSegment {
1962 dst_port: 0,
1963 src_port: 11111,
1964 segment: SegmentHeader::arbitrary_value(),
1965 payload_len: 0,
1966 },
1967 ..FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value()
1968 };
1969
1970 assert_eq!(
1971 check_routines_for_hook::<
1972 I,
1973 _,
1974 FakeMatcherDeviceId,
1975 FakeBindingsCtx<I>,
1976 _,
1977 IngressStopReason<I>,
1978 >(
1979 &ingress,
1980 &packet,
1981 Interfaces { ingress: None, egress: None },
1982 &mut FakePacketMetadata::default(),
1983 ),
1984 IngressVerdict::Stop(IngressStopReason::Drop),
1985 );
1986 }
1987}