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_or_id()) 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 fn packet_type(&self) -> netstack3_base::PacketType {
1026 netstack3_base::PacketType::Host
1027 }
1028
1029 fn gso_info(&self) -> Option<netstack3_base::GsoInfo> {
1030 None
1031 }
1032 }
1033
1034 #[test]
1035 fn accept_by_default_if_no_matching_rules_in_hook() {
1036 assert_eq!(
1037 check_routines_for_hook::<
1038 Ipv4,
1039 _,
1040 FakeMatcherDeviceId,
1041 FakeBindingsCtx<Ipv4>,
1042 _,
1043 DropPacket,
1044 >(
1045 &Hook::default(),
1046 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1047 Interfaces { ingress: None, egress: None },
1048 &mut FakePacketMetadata::default(),
1049 ),
1050 Verdict::Proceed(Accept)
1051 );
1052 }
1053
1054 #[test]
1055 fn accept_by_default_if_return_from_routine() {
1056 let hook = Hook {
1057 routines: vec![Routine {
1058 rules: vec![Rule::new(PacketMatcher::default(), Action::Return)],
1059 }],
1060 };
1061
1062 assert_eq!(
1063 check_routines_for_hook::<
1064 Ipv4,
1065 _,
1066 FakeMatcherDeviceId,
1067 FakeBindingsCtx<Ipv4>,
1068 _,
1069 DropPacket,
1070 >(
1071 &hook,
1072 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1073 Interfaces { ingress: None, egress: None },
1074 &mut FakePacketMetadata::default(),
1075 ),
1076 Verdict::Proceed(Accept)
1077 );
1078 }
1079
1080 #[test]
1081 fn accept_terminal_for_installed_routine() {
1082 let routine = Routine {
1083 rules: vec![
1084 Rule::new(PacketMatcher::default(), Action::Accept),
1086 Rule::new(PacketMatcher::default(), Action::Drop),
1088 ],
1089 };
1090 assert_eq!(
1091 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1092 &routine,
1093 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1094 Interfaces { ingress: None, egress: None },
1095 &mut FakePacketMetadata::default(),
1096 ),
1097 RoutineResult::Accept
1098 );
1099
1100 let routine = Routine {
1102 rules: vec![
1103 Rule::new(
1105 PacketMatcher::default(),
1106 Action::Jump(UninstalledRoutine::new(
1107 vec![Rule::new(PacketMatcher::default(), Action::Accept)],
1108 0,
1109 )),
1110 ),
1111 Rule::new(PacketMatcher::default(), Action::Drop),
1113 ],
1114 };
1115 assert_eq!(
1116 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1117 &routine,
1118 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1119 Interfaces { ingress: None, egress: None },
1120 &mut FakePacketMetadata::default(),
1121 ),
1122 RoutineResult::Accept
1123 );
1124
1125 let hook = Hook {
1130 routines: vec![
1131 routine,
1132 Routine {
1133 rules: vec![
1134 Rule::new(PacketMatcher::default(), Action::Drop),
1136 ],
1137 },
1138 ],
1139 };
1140
1141 assert_eq!(
1142 check_routines_for_hook::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _, _>(
1143 &hook,
1144 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1145 Interfaces { ingress: None, egress: None },
1146 &mut FakePacketMetadata::default(),
1147 ),
1148 Verdict::Stop(DropPacket)
1149 );
1150 }
1151
1152 #[test]
1153 fn drop_terminal_for_entire_hook() {
1154 let hook = Hook {
1155 routines: vec![
1156 Routine {
1157 rules: vec![
1158 Rule::new(PacketMatcher::default(), Action::Drop),
1160 ],
1161 },
1162 Routine {
1163 rules: vec![
1164 Rule::new(PacketMatcher::default(), Action::Accept),
1166 ],
1167 },
1168 ],
1169 };
1170
1171 assert_eq!(
1172 check_routines_for_hook::<
1173 Ipv4,
1174 _,
1175 FakeMatcherDeviceId,
1176 FakeBindingsCtx<Ipv4>,
1177 _,
1178 DropPacket,
1179 >(
1180 &hook,
1181 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1182 Interfaces { ingress: None, egress: None },
1183 &mut FakePacketMetadata::default(),
1184 ),
1185 Verdict::Stop(DropPacket)
1186 );
1187 }
1188
1189 #[test]
1190 fn transparent_proxy_terminal_for_entire_hook() {
1191 const TPROXY_PORT: NonZeroU16 = NonZeroU16::new(8080).unwrap();
1192
1193 let ingress = Hook {
1194 routines: vec![
1195 Routine {
1196 rules: vec![Rule::new(
1197 PacketMatcher::default(),
1198 Action::TransparentProxy(TransparentProxy::LocalPort(TPROXY_PORT)),
1199 )],
1200 },
1201 Routine {
1202 rules: vec![
1203 Rule::new(PacketMatcher::default(), Action::Accept),
1205 ],
1206 },
1207 ],
1208 };
1209
1210 assert_eq!(
1211 check_routines_for_hook::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _, _>(
1212 &ingress,
1213 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1214 Interfaces { ingress: None, egress: None },
1215 &mut FakePacketMetadata::default(),
1216 ),
1217 IngressVerdict::Stop(IngressStopReason::TransparentLocalDelivery {
1218 addr: <Ipv4 as crate::packets::testutil::internal::TestIpExt>::DST_IP,
1219 port: TPROXY_PORT
1220 })
1221 );
1222 }
1223
1224 #[test]
1225 fn jump_recursively_evaluates_target_routine() {
1226 let routine = Routine {
1229 rules: vec![Rule::new(
1230 PacketMatcher::default(),
1231 Action::Jump(UninstalledRoutine::new(
1232 vec![Rule::new(PacketMatcher::default(), Action::Drop)],
1233 0,
1234 )),
1235 )],
1236 };
1237 assert_eq!(
1238 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1239 &routine,
1240 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1241 Interfaces { ingress: None, egress: None },
1242 &mut FakePacketMetadata::default(),
1243 ),
1244 RoutineResult::Drop
1245 );
1246
1247 let routine = Routine {
1250 rules: vec![
1251 Rule::new(
1252 PacketMatcher::default(),
1253 Action::Jump(UninstalledRoutine::new(
1254 vec![Rule::new(PacketMatcher::default(), Action::Accept)],
1255 0,
1256 )),
1257 ),
1258 Rule::new(PacketMatcher::default(), Action::Drop),
1259 ],
1260 };
1261 assert_eq!(
1262 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1263 &routine,
1264 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1265 Interfaces { ingress: None, egress: None },
1266 &mut FakePacketMetadata::default(),
1267 ),
1268 RoutineResult::Accept
1269 );
1270
1271 let routine = Routine {
1274 rules: vec![
1275 Rule::new(
1276 PacketMatcher::default(),
1277 Action::Jump(UninstalledRoutine::new(
1278 vec![Rule::new(PacketMatcher::default(), Action::Return)],
1279 0,
1280 )),
1281 ),
1282 Rule::new(PacketMatcher::default(), Action::Drop),
1283 ],
1284 };
1285 assert_eq!(
1286 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1287 &routine,
1288 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1289 Interfaces { ingress: None, egress: None },
1290 &mut FakePacketMetadata::default(),
1291 ),
1292 RoutineResult::Drop
1293 );
1294 }
1295
1296 #[test]
1297 fn return_terminal_for_single_routine() {
1298 let routine = Routine {
1299 rules: vec![
1300 Rule::new(PacketMatcher::default(), Action::Return),
1301 Rule::new(PacketMatcher::default(), Action::Drop),
1303 ],
1304 };
1305
1306 assert_eq!(
1307 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1308 &routine,
1309 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1310 Interfaces { ingress: None, egress: None },
1311 &mut FakePacketMetadata::default(),
1312 ),
1313 RoutineResult::Return
1314 );
1315 }
1316
1317 #[ip_test(I)]
1318 fn filter_handler_implements_ip_hooks_correctly<I: TestIpExt>() {
1319 fn drop_all_traffic<I: TestIpExt>(
1320 matcher: PacketMatcher<I, FakeBindingsCtx<I>>,
1321 ) -> Hook<I, FakeBindingsCtx<I>, ()> {
1322 Hook { routines: vec![Routine { rules: vec![Rule::new(matcher, Action::Drop)] }] }
1323 }
1324
1325 let mut bindings_ctx = FakeBindingsCtx::new();
1326
1327 let mut ctx = FakeCtx::with_ip_routines(
1330 &mut bindings_ctx,
1331 IpRoutines {
1332 ingress: drop_all_traffic(PacketMatcher {
1333 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1334 ..Default::default()
1335 }),
1336 ..Default::default()
1337 },
1338 );
1339 assert_eq!(
1340 FilterImpl(&mut ctx).ingress_hook(
1341 &mut bindings_ctx,
1342 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1343 &FakeMatcherDeviceId::wlan_interface(),
1344 &mut FakePacketMetadata::default(),
1345 ),
1346 Verdict::Stop(IngressStopReason::Drop)
1347 );
1348
1349 let mut ctx = FakeCtx::with_ip_routines(
1352 &mut bindings_ctx,
1353 IpRoutines {
1354 local_ingress: drop_all_traffic(PacketMatcher {
1355 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1356 ..Default::default()
1357 }),
1358 ..Default::default()
1359 },
1360 );
1361 assert_eq!(
1362 FilterImpl(&mut ctx).local_ingress_hook(
1363 &mut bindings_ctx,
1364 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1365 &FakeMatcherDeviceId::wlan_interface(),
1366 &mut FakePacketMetadata::default(),
1367 ),
1368 Verdict::Stop(DropOrReject::Drop)
1369 );
1370
1371 let mut ctx = FakeCtx::with_ip_routines(
1374 &mut bindings_ctx,
1375 IpRoutines {
1376 forwarding: drop_all_traffic(PacketMatcher {
1377 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1378 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Ethernet)),
1379 ..Default::default()
1380 }),
1381 ..Default::default()
1382 },
1383 );
1384 assert_eq!(
1385 FilterImpl(&mut ctx).forwarding_hook(
1386 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1387 &FakeMatcherDeviceId::wlan_interface(),
1388 &FakeMatcherDeviceId::ethernet_interface(),
1389 &mut FakePacketMetadata::default(),
1390 ),
1391 Verdict::Stop(DropOrReject::Drop)
1392 );
1393
1394 let mut ctx = FakeCtx::with_ip_routines(
1397 &mut bindings_ctx,
1398 IpRoutines {
1399 local_egress: drop_all_traffic(PacketMatcher {
1400 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1401 ..Default::default()
1402 }),
1403 ..Default::default()
1404 },
1405 );
1406 assert_eq!(
1407 FilterImpl(&mut ctx).local_egress_hook(
1408 &mut bindings_ctx,
1409 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1410 &FakeMatcherDeviceId::wlan_interface(),
1411 &mut FakePacketMetadata::default(),
1412 ),
1413 Verdict::Stop(DropOrReject::Drop)
1414 );
1415
1416 let mut ctx = FakeCtx::with_ip_routines(
1419 &mut bindings_ctx,
1420 IpRoutines {
1421 egress: drop_all_traffic(PacketMatcher {
1422 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1423 ..Default::default()
1424 }),
1425 ..Default::default()
1426 },
1427 );
1428 assert_eq!(
1429 FilterImpl(&mut ctx)
1430 .egress_hook(
1431 &mut bindings_ctx,
1432 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1433 &FakeMatcherDeviceId::wlan_interface(),
1434 &mut FakePacketMetadata::default(),
1435 )
1436 .0,
1437 Verdict::Stop(DropPacket)
1438 );
1439 }
1440
1441 #[ip_test(I)]
1442 #[test_case(22 => Verdict::Proceed(Accept); "port 22 allowed for SSH")]
1443 #[test_case(80 => Verdict::Proceed(Accept); "port 80 allowed for HTTP")]
1444 #[test_case(1024 => Verdict::Proceed(Accept); "ephemeral port 1024 allowed")]
1445 #[test_case(65535 => Verdict::Proceed(Accept); "ephemeral port 65535 allowed")]
1446 #[test_case(1023 => Verdict::Stop(DropOrReject::Drop); "privileged port 1023 blocked")]
1447 #[test_case(53 => Verdict::Stop(DropOrReject::Drop); "privileged port 53 blocked")]
1448 fn block_privileged_ports_except_ssh_http<I: TestIpExt>(port: u16) -> Verdict<DropOrReject> {
1449 fn tcp_port_rule<I: FilterIpExt>(
1450 src_port: Option<PortMatcher>,
1451 dst_port: Option<PortMatcher>,
1452 action: Action<I, FakeBindingsCtx<I>, ()>,
1453 ) -> Rule<I, FakeBindingsCtx<I>, ()> {
1454 Rule::new(
1455 PacketMatcher {
1456 transport_protocol: Some(TransportProtocolMatcher {
1457 proto: <&FakeTcpSegment as TransportPacketExt<I>>::proto().unwrap(),
1458 src_port,
1459 dst_port,
1460 }),
1461 ..Default::default()
1462 },
1463 action,
1464 )
1465 }
1466
1467 fn default_filter_rules<I: FilterIpExt>() -> Routine<I, FakeBindingsCtx<I>, ()> {
1468 Routine {
1469 rules: vec![
1470 tcp_port_rule(
1472 None,
1473 Some(PortMatcher { range: 22..=22, invert: false }),
1474 Action::Accept,
1475 ),
1476 tcp_port_rule(
1478 None,
1479 Some(PortMatcher { range: 80..=80, invert: false }),
1480 Action::Accept,
1481 ),
1482 tcp_port_rule(
1484 None,
1485 Some(PortMatcher { range: 1024..=65535, invert: false }),
1486 Action::Accept,
1487 ),
1488 tcp_port_rule(
1490 None,
1491 Some(PortMatcher { range: 1..=65535, invert: false }),
1492 Action::Drop,
1493 ),
1494 ],
1495 }
1496 }
1497
1498 let mut bindings_ctx = FakeBindingsCtx::new();
1499
1500 let mut ctx = FakeCtx::with_ip_routines(
1501 &mut bindings_ctx,
1502 IpRoutines {
1503 local_ingress: Hook { routines: vec![default_filter_rules()] },
1504 ..Default::default()
1505 },
1506 );
1507
1508 FilterImpl(&mut ctx).local_ingress_hook(
1509 &mut bindings_ctx,
1510 &mut FakeIpPacket::<I, _> {
1511 body: FakeTcpSegment {
1512 dst_port: port,
1513 src_port: 11111,
1514 segment: SegmentHeader::arbitrary_value(),
1515 payload_len: 8888,
1516 },
1517 ..ArbitraryValue::arbitrary_value()
1518 },
1519 &FakeMatcherDeviceId::wlan_interface(),
1520 &mut FakePacketMetadata::default(),
1521 )
1522 }
1523
1524 #[ip_test(I)]
1525 #[test_case(
1526 FakeMatcherDeviceId::ethernet_interface() => Verdict::Proceed(Accept);
1527 "allow incoming traffic on ethernet interface"
1528 )]
1529 #[test_case(
1530 FakeMatcherDeviceId::wlan_interface() => Verdict::Stop(DropOrReject::Drop);
1531 "drop incoming traffic on wlan interface"
1532 )]
1533 fn filter_on_wlan_only<I: TestIpExt>(interface: FakeMatcherDeviceId) -> Verdict<DropOrReject> {
1534 fn drop_wlan_traffic<I: IpExt>() -> Routine<I, FakeBindingsCtx<I>, ()> {
1535 Routine {
1536 rules: vec![Rule::new(
1537 PacketMatcher {
1538 in_interface: Some(InterfaceMatcher::Id(
1539 FakeMatcherDeviceId::wlan_interface().id,
1540 )),
1541 ..Default::default()
1542 },
1543 Action::Drop,
1544 )],
1545 }
1546 }
1547
1548 let mut bindings_ctx = FakeBindingsCtx::new();
1549
1550 let mut ctx = FakeCtx::with_ip_routines(
1551 &mut bindings_ctx,
1552 IpRoutines {
1553 local_ingress: Hook { routines: vec![drop_wlan_traffic()] },
1554 ..Default::default()
1555 },
1556 );
1557
1558 FilterImpl(&mut ctx).local_ingress_hook(
1559 &mut bindings_ctx,
1560 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1561 &interface,
1562 &mut FakePacketMetadata::default(),
1563 )
1564 }
1565
1566 #[test]
1567 fn ingress_reuses_cached_connection_when_available() {
1568 let mut bindings_ctx = FakeBindingsCtx::new();
1569 let mut core_ctx = FakeCtx::new(&mut bindings_ctx);
1570
1571 let mut packet = FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value();
1574 let mut metadata = PacketMetadata::default();
1575 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1576 &mut bindings_ctx,
1577 &mut packet,
1578 &FakeMatcherDeviceId::ethernet_interface(),
1579 &mut metadata,
1580 );
1581 assert_eq!(verdict, Verdict::Proceed(Accept));
1582
1583 let (stashed, _dir) =
1585 metadata.take_connection_and_direction().expect("metadata should include connection");
1586 let tuple = packet.conntrack_packet().expect("packet should be trackable").tuple();
1587 let table = core_ctx
1588 .conntrack()
1589 .get_connection(&tuple)
1590 .expect("packet should be inserted in table");
1591 assert_matches!(
1592 (table, stashed),
1593 (Connection::Shared(table), Connection::Shared(stashed)) => {
1594 assert!(Arc::ptr_eq(&table, &stashed));
1595 }
1596 );
1597
1598 let verdict = FilterImpl(&mut core_ctx).ingress_hook(
1601 &mut bindings_ctx,
1602 &mut packet,
1603 &FakeMatcherDeviceId::ethernet_interface(),
1604 &mut metadata,
1605 );
1606 assert_eq!(verdict, Verdict::Proceed(Accept));
1607
1608 let (after_ingress, _dir) =
1611 metadata.take_connection_and_direction().expect("metadata should include connection");
1612 let table = core_ctx
1613 .conntrack()
1614 .get_connection(&tuple)
1615 .expect("packet should be inserted in table");
1616 assert_matches!(
1617 (table, after_ingress),
1618 (Connection::Shared(before), Connection::Shared(after)) => {
1619 assert!(Arc::ptr_eq(&before, &after));
1620 }
1621 );
1622 }
1623
1624 #[ip_test(I)]
1625 fn drop_packet_on_finalize_connection_failure<I: TestIpExt>() {
1626 let mut bindings_ctx = FakeBindingsCtx::new();
1627 let mut ctx = FakeCtx::new(&mut bindings_ctx);
1628
1629 for i in 0..u32::try_from(conntrack::MAXIMUM_ENTRIES / 2).unwrap() {
1630 let (mut packet, mut reply_packet) = conntrack::testutils::make_test_udp_packets(i);
1631 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1632 &mut bindings_ctx,
1633 &mut 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 reply_packet,
1642 &FakeMatcherDeviceId::ethernet_interface(),
1643 &mut FakePacketMetadata::default(),
1644 );
1645 assert_eq!(verdict, Verdict::Proceed(Accept));
1646
1647 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1648 &mut bindings_ctx,
1649 &mut packet,
1650 &FakeMatcherDeviceId::ethernet_interface(),
1651 &mut FakePacketMetadata::default(),
1652 );
1653 assert_eq!(verdict, Verdict::Proceed(Accept));
1654 }
1655
1656 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1660 &mut bindings_ctx,
1661 &mut FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value(),
1662 &FakeMatcherDeviceId::ethernet_interface(),
1663 &mut FakePacketMetadata::default(),
1664 );
1665 assert_eq!(verdict, Verdict::Stop(DropPacket));
1666 }
1667
1668 #[ip_test(I)]
1669 fn implicit_snat_to_prevent_tuple_clash<I: TestIpExt>() {
1670 let mut bindings_ctx = FakeBindingsCtx::new();
1671 let mut ctx = FakeCtx::with_nat_routines_and_device_addrs(
1672 &mut bindings_ctx,
1673 NatRoutines {
1674 egress: Hook {
1675 routines: vec![Routine {
1676 rules: vec![Rule::new(
1677 PacketMatcher {
1678 src_address: Some(AddressMatcher {
1679 matcher: AddressMatcherType::Range(I::SRC_IP_2..=I::SRC_IP_2),
1680 invert: false,
1681 }),
1682 ..Default::default()
1683 },
1684 Action::Masquerade { src_port: None },
1685 )],
1686 }],
1687 },
1688 ..Default::default()
1689 },
1690 [(
1691 FakeMatcherDeviceId::ethernet_interface(),
1692 AddrSubnet::new(I::SRC_IP, I::SUBNET.prefix()).unwrap(),
1693 )],
1694 );
1695
1696 let mut packet = FakeIpPacket {
1699 src_ip: I::SRC_IP_2,
1700 dst_ip: I::DST_IP,
1701 body: FakeUdpPacket::arbitrary_value(),
1702 };
1703 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1704 &mut bindings_ctx,
1705 &mut packet,
1706 &FakeMatcherDeviceId::ethernet_interface(),
1707 &mut FakePacketMetadata::default(),
1708 );
1709 assert_eq!(verdict, Verdict::Proceed(Accept));
1710 assert_eq!(packet.src_ip, I::SRC_IP);
1711
1712 let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1719 let src_port = packet.body.src_port;
1720 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1721 &mut bindings_ctx,
1722 &mut packet,
1723 &FakeMatcherDeviceId::ethernet_interface(),
1724 &mut FakePacketMetadata::default(),
1725 );
1726 assert_eq!(verdict, Verdict::Proceed(Accept));
1727 assert_ne!(packet.body.src_port, src_port);
1728 }
1729
1730 #[ip_test(I)]
1731 fn packet_adopts_tracked_connection_in_table_if_identical<I: TestIpExt>() {
1732 let mut bindings_ctx = FakeBindingsCtx::new();
1733 let mut core_ctx = FakeCtx::new(&mut bindings_ctx);
1734
1735 let mut first_packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1738 let mut first_metadata = PacketMetadata::default();
1739 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1740 &mut bindings_ctx,
1741 &mut first_packet,
1742 &FakeMatcherDeviceId::ethernet_interface(),
1743 &mut first_metadata,
1744 );
1745 assert_eq!(verdict, Verdict::Proceed(Accept));
1746
1747 let mut second_packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1748 let mut second_metadata = PacketMetadata::default();
1749 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1750 &mut bindings_ctx,
1751 &mut second_packet,
1752 &FakeMatcherDeviceId::ethernet_interface(),
1753 &mut second_metadata,
1754 );
1755 assert_eq!(verdict, Verdict::Proceed(Accept));
1756
1757 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1759 &mut bindings_ctx,
1760 &mut first_packet,
1761 &FakeMatcherDeviceId::ethernet_interface(),
1762 &mut first_metadata,
1763 );
1764 assert_eq!(verdict, Verdict::Proceed(Accept));
1765
1766 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1769 &mut bindings_ctx,
1770 &mut second_packet,
1771 &FakeMatcherDeviceId::ethernet_interface(),
1772 &mut second_metadata,
1773 );
1774 assert_eq!(second_packet.body.src_port, first_packet.body.src_port);
1775 assert_eq!(verdict, Verdict::Proceed(Accept));
1776
1777 let (first_conn, _dir) = first_metadata.take_connection_and_direction().unwrap();
1778 let (second_conn, _dir) = second_metadata.take_connection_and_direction().unwrap();
1779 assert_matches!(
1780 (first_conn, second_conn),
1781 (Connection::Shared(first), Connection::Shared(second)) => {
1782 assert!(Arc::ptr_eq(&first, &second));
1783 }
1784 );
1785 }
1786
1787 #[ip_test(I)]
1788 fn both_source_and_destination_nat_configured<I: TestIpExt>() {
1789 let mut bindings_ctx = FakeBindingsCtx::new();
1790 let mut core_ctx = FakeCtx::with_nat_routines_and_device_addrs(
1793 &mut bindings_ctx,
1794 NatRoutines {
1795 local_egress: Hook {
1796 routines: vec![Routine {
1797 rules: vec![Rule::new(
1798 PacketMatcher::default(),
1799 Action::Redirect { dst_port: None },
1800 )],
1801 }],
1802 },
1803 egress: Hook {
1804 routines: vec![Routine {
1805 rules: vec![Rule::new(
1806 PacketMatcher::default(),
1807 Action::Masquerade { src_port: None },
1808 )],
1809 }],
1810 },
1811 ..Default::default()
1812 },
1813 [(
1814 FakeMatcherDeviceId::ethernet_interface(),
1815 AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap(),
1816 )],
1817 );
1818
1819 let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1822 let mut metadata = PacketMetadata::default();
1823 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1824 &mut bindings_ctx,
1825 &mut packet,
1826 &FakeMatcherDeviceId::ethernet_interface(),
1827 &mut metadata,
1828 );
1829 assert_eq!(verdict, Verdict::Proceed(Accept));
1830 assert_eq!(packet.dst_ip, *I::LOOPBACK_ADDRESS);
1831
1832 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1835 &mut bindings_ctx,
1836 &mut packet,
1837 &FakeMatcherDeviceId::ethernet_interface(),
1838 &mut metadata,
1839 );
1840 assert_eq!(verdict, Verdict::Proceed(Accept));
1841 assert_eq!(packet.src_ip, I::SRC_IP_2);
1842 }
1843
1844 #[ip_test(I)]
1845 #[test_case(
1846 Hook {
1847 routines: vec![
1848 Routine {
1849 rules: vec![
1850 Rule::new(
1851 PacketMatcher::default(),
1852 Action::Mark {
1853 domain: MarkDomain::Mark1,
1854 action: MarkAction::SetMark { clearing_mask: 0, mark: 1 },
1855 },
1856 ),
1857 Rule::new(PacketMatcher::default(), Action::Drop),
1858 ],
1859 },
1860 ],
1861 }; "non terminal for routine"
1862 )]
1863 #[test_case(
1864 Hook {
1865 routines: vec![
1866 Routine {
1867 rules: vec![Rule::new(
1868 PacketMatcher::default(),
1869 Action::Mark {
1870 domain: MarkDomain::Mark1,
1871 action: MarkAction::SetMark { clearing_mask: 0, mark: 1 },
1872 },
1873 )],
1874 },
1875 Routine {
1876 rules: vec![
1877 Rule::new(PacketMatcher::default(), Action::Drop),
1878 ],
1879 },
1880 ],
1881 }; "non terminal for hook"
1882 )]
1883 fn mark_action<I: TestIpExt>(ingress: Hook<I, FakeBindingsCtx<I>, ()>) {
1884 let mut metadata = PacketMetadata::<I, FakeWeakAddressId<I>, FakeBindingsCtx<I>>::default();
1885 assert_eq!(
1886 check_routines_for_hook::<I, _, FakeMatcherDeviceId, FakeBindingsCtx<I>, _, _>(
1887 &ingress,
1888 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1889 Interfaces { ingress: None, egress: None },
1890 &mut metadata,
1891 ),
1892 IngressVerdict::Stop(IngressStopReason::Drop),
1893 );
1894 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1)]));
1895 }
1896
1897 #[ip_test(I)]
1898 fn mark_action_applied_in_succession<I: TestIpExt>() {
1899 fn hook_with_single_mark_action<I: TestIpExt>(
1900 domain: MarkDomain,
1901 action: MarkAction,
1902 ) -> Hook<I, FakeBindingsCtx<I>, ()> {
1903 Hook {
1904 routines: vec![Routine {
1905 rules: vec![Rule::new(
1906 PacketMatcher::default(),
1907 Action::Mark { domain, action },
1908 )],
1909 }],
1910 }
1911 }
1912 let mut metadata = PacketMetadata::<I, FakeWeakAddressId<I>, FakeBindingsCtx<I>>::default();
1913 assert_eq!(
1914 check_routines_for_hook::<I, _, FakeMatcherDeviceId, FakeBindingsCtx<I>, _, _>(
1915 &hook_with_single_mark_action(
1916 MarkDomain::Mark1,
1917 MarkAction::SetMark { clearing_mask: 0, mark: 1 }
1918 ),
1919 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1920 Interfaces { ingress: None, egress: None },
1921 &mut metadata,
1922 ),
1923 IngressVerdict::Proceed(Accept),
1924 );
1925 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1)]));
1926
1927 assert_eq!(
1928 check_routines_for_hook(
1929 &hook_with_single_mark_action::<I>(
1930 MarkDomain::Mark2,
1931 MarkAction::SetMark { clearing_mask: 0, mark: 1 }
1932 ),
1933 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1934 Interfaces::<FakeMatcherDeviceId> { ingress: None, egress: None },
1935 &mut metadata,
1936 ),
1937 IngressVerdict::Proceed(Accept)
1938 );
1939 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1), (MarkDomain::Mark2, 1)]));
1940
1941 assert_eq!(
1942 check_routines_for_hook(
1943 &hook_with_single_mark_action::<I>(
1944 MarkDomain::Mark1,
1945 MarkAction::SetMark { clearing_mask: 1, mark: 2 }
1946 ),
1947 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1948 Interfaces::<FakeMatcherDeviceId> { ingress: None, egress: None },
1949 &mut metadata,
1950 ),
1951 IngressVerdict::Proceed(Accept)
1952 );
1953 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 2), (MarkDomain::Mark2, 1)]));
1954 }
1955
1956 #[ip_test(I)]
1958 fn transparent_proxy_drop_on_port_0<I: TestIpExt>() {
1959 let ingress = Hook {
1960 routines: vec![Routine {
1961 rules: vec![Rule::new(
1962 PacketMatcher::default(),
1963 Action::TransparentProxy(TransparentProxy::LocalAddr(I::DST_IP)),
1964 )],
1965 }],
1966 };
1967
1968 let packet = FakeIpPacket::<I, FakeTcpSegment> {
1969 body: FakeTcpSegment {
1970 dst_port: 0,
1971 src_port: 11111,
1972 segment: SegmentHeader::arbitrary_value(),
1973 payload_len: 0,
1974 },
1975 ..FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value()
1976 };
1977
1978 assert_eq!(
1979 check_routines_for_hook::<
1980 I,
1981 _,
1982 FakeMatcherDeviceId,
1983 FakeBindingsCtx<I>,
1984 _,
1985 IngressStopReason<I>,
1986 >(
1987 &ingress,
1988 &packet,
1989 Interfaces { ingress: None, egress: None },
1990 &mut FakePacketMetadata::default(),
1991 ),
1992 IngressVerdict::Stop(IngressStopReason::Drop),
1993 );
1994 }
1995}