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