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