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 test_case::test_case;
918
919 use super::*;
920 use crate::actions::MarkAction;
921 use crate::conntrack::{self, ConnectionDirection};
922 use crate::context::testutil::{FakeBindingsCtx, FakeCtx, FakeWeakAddressId};
923 use crate::logic::nat::NatConfig;
924 use crate::matchers::{PacketMatcher, TransportProtocolMatcher};
925 use crate::packets::IpPacket;
926 use crate::packets::testutil::internal::{
927 ArbitraryValue, FakeIpPacket, FakeTcpSegment, FakeUdpPacket, TransportPacketExt,
928 };
929 use crate::state::{FakePacketMetadata, IpRoutines, NatRoutines, UninstalledRoutine};
930 use crate::testutil::TestIpExt;
931
932 impl<I: IpExt> Rule<I, FakeBindingsCtx<I>, ()> {
933 pub(crate) fn new(
934 matcher: PacketMatcher<I, FakeBindingsCtx<I>>,
935 action: Action<I, FakeBindingsCtx<I>, ()>,
936 ) -> Self {
937 Rule { matcher, action, validation_info: () }
938 }
939 }
940
941 #[test]
942 fn return_by_default_if_no_matching_rules_in_routine() {
943 assert_eq!(
944 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
945 &Routine { rules: Vec::new() },
946 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
947 Interfaces { ingress: None, egress: None },
948 &mut FakePacketMetadata::default(),
949 ),
950 RoutineResult::Return
951 );
952
953 let routine = Routine {
956 rules: vec![
957 Rule::new(
958 PacketMatcher::default(),
959 Action::Jump(UninstalledRoutine::new(Vec::new(), 0)),
960 ),
961 Rule::new(PacketMatcher::default(), Action::Drop),
962 ],
963 };
964 assert_eq!(
965 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
966 &routine,
967 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
968 Interfaces { ingress: None, egress: None },
969 &mut FakePacketMetadata::default(),
970 ),
971 RoutineResult::Drop
972 );
973 }
974
975 #[derive(Derivative)]
976 #[derivative(Default(bound = ""))]
977 struct PacketMetadata<I: IpExt + AssignedAddrIpExt, A, BT: FilterBindingsTypes> {
978 conn: Option<(Connection<I, NatConfig<I, A>, BT>, ConnectionDirection)>,
979 marks: Marks,
980 }
981
982 impl<I: TestIpExt, A, BT: FilterBindingsTypes> FilterIpMetadata<I, A, BT>
983 for PacketMetadata<I, A, BT>
984 {
985 fn take_connection_and_direction(
986 &mut self,
987 ) -> Option<(Connection<I, NatConfig<I, A>, BT>, ConnectionDirection)> {
988 let Self { conn, marks: _ } = self;
989 conn.take()
990 }
991
992 fn replace_connection_and_direction(
993 &mut self,
994 new_conn: Connection<I, NatConfig<I, A>, BT>,
995 direction: ConnectionDirection,
996 ) -> Option<Connection<I, NatConfig<I, A>, BT>> {
997 let Self { conn, marks: _ } = self;
998 conn.replace((new_conn, direction)).map(|(conn, _dir)| conn)
999 }
1000 }
1001
1002 impl<I, A, BT> FilterPacketMetadata for PacketMetadata<I, A, BT>
1003 where
1004 I: TestIpExt,
1005 BT: FilterBindingsTypes,
1006 {
1007 fn apply_mark_action(&mut self, domain: MarkDomain, action: MarkAction) {
1008 action.apply(self.marks.get_mut(domain))
1009 }
1010
1011 fn socket_info(&self) -> Option<crate::SocketInfo> {
1012 None
1013 }
1014
1015 fn marks(&self) -> &Marks {
1016 &self.marks
1017 }
1018 }
1019
1020 #[test]
1021 fn accept_by_default_if_no_matching_rules_in_hook() {
1022 assert_eq!(
1023 check_routines_for_hook::<
1024 Ipv4,
1025 _,
1026 FakeMatcherDeviceId,
1027 FakeBindingsCtx<Ipv4>,
1028 _,
1029 DropPacket,
1030 >(
1031 &Hook::default(),
1032 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1033 Interfaces { ingress: None, egress: None },
1034 &mut FakePacketMetadata::default(),
1035 ),
1036 Verdict::Proceed(Accept)
1037 );
1038 }
1039
1040 #[test]
1041 fn accept_by_default_if_return_from_routine() {
1042 let hook = Hook {
1043 routines: vec![Routine {
1044 rules: vec![Rule::new(PacketMatcher::default(), Action::Return)],
1045 }],
1046 };
1047
1048 assert_eq!(
1049 check_routines_for_hook::<
1050 Ipv4,
1051 _,
1052 FakeMatcherDeviceId,
1053 FakeBindingsCtx<Ipv4>,
1054 _,
1055 DropPacket,
1056 >(
1057 &hook,
1058 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1059 Interfaces { ingress: None, egress: None },
1060 &mut FakePacketMetadata::default(),
1061 ),
1062 Verdict::Proceed(Accept)
1063 );
1064 }
1065
1066 #[test]
1067 fn accept_terminal_for_installed_routine() {
1068 let routine = Routine {
1069 rules: vec![
1070 Rule::new(PacketMatcher::default(), Action::Accept),
1072 Rule::new(PacketMatcher::default(), Action::Drop),
1074 ],
1075 };
1076 assert_eq!(
1077 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1078 &routine,
1079 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1080 Interfaces { ingress: None, egress: None },
1081 &mut FakePacketMetadata::default(),
1082 ),
1083 RoutineResult::Accept
1084 );
1085
1086 let routine = Routine {
1088 rules: vec![
1089 Rule::new(
1091 PacketMatcher::default(),
1092 Action::Jump(UninstalledRoutine::new(
1093 vec![Rule::new(PacketMatcher::default(), Action::Accept)],
1094 0,
1095 )),
1096 ),
1097 Rule::new(PacketMatcher::default(), Action::Drop),
1099 ],
1100 };
1101 assert_eq!(
1102 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1103 &routine,
1104 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1105 Interfaces { ingress: None, egress: None },
1106 &mut FakePacketMetadata::default(),
1107 ),
1108 RoutineResult::Accept
1109 );
1110
1111 let hook = Hook {
1116 routines: vec![
1117 routine,
1118 Routine {
1119 rules: vec![
1120 Rule::new(PacketMatcher::default(), Action::Drop),
1122 ],
1123 },
1124 ],
1125 };
1126
1127 assert_eq!(
1128 check_routines_for_hook::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _, _>(
1129 &hook,
1130 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1131 Interfaces { ingress: None, egress: None },
1132 &mut FakePacketMetadata::default(),
1133 ),
1134 Verdict::Stop(DropPacket)
1135 );
1136 }
1137
1138 #[test]
1139 fn drop_terminal_for_entire_hook() {
1140 let hook = Hook {
1141 routines: vec![
1142 Routine {
1143 rules: vec![
1144 Rule::new(PacketMatcher::default(), Action::Drop),
1146 ],
1147 },
1148 Routine {
1149 rules: vec![
1150 Rule::new(PacketMatcher::default(), Action::Accept),
1152 ],
1153 },
1154 ],
1155 };
1156
1157 assert_eq!(
1158 check_routines_for_hook::<
1159 Ipv4,
1160 _,
1161 FakeMatcherDeviceId,
1162 FakeBindingsCtx<Ipv4>,
1163 _,
1164 DropPacket,
1165 >(
1166 &hook,
1167 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1168 Interfaces { ingress: None, egress: None },
1169 &mut FakePacketMetadata::default(),
1170 ),
1171 Verdict::Stop(DropPacket)
1172 );
1173 }
1174
1175 #[test]
1176 fn transparent_proxy_terminal_for_entire_hook() {
1177 const TPROXY_PORT: NonZeroU16 = NonZeroU16::new(8080).unwrap();
1178
1179 let ingress = Hook {
1180 routines: vec![
1181 Routine {
1182 rules: vec![Rule::new(
1183 PacketMatcher::default(),
1184 Action::TransparentProxy(TransparentProxy::LocalPort(TPROXY_PORT)),
1185 )],
1186 },
1187 Routine {
1188 rules: vec![
1189 Rule::new(PacketMatcher::default(), Action::Accept),
1191 ],
1192 },
1193 ],
1194 };
1195
1196 assert_eq!(
1197 check_routines_for_hook::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _, _>(
1198 &ingress,
1199 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1200 Interfaces { ingress: None, egress: None },
1201 &mut FakePacketMetadata::default(),
1202 ),
1203 IngressVerdict::Stop(IngressStopReason::TransparentLocalDelivery {
1204 addr: <Ipv4 as crate::packets::testutil::internal::TestIpExt>::DST_IP,
1205 port: TPROXY_PORT
1206 })
1207 );
1208 }
1209
1210 #[test]
1211 fn jump_recursively_evaluates_target_routine() {
1212 let routine = Routine {
1215 rules: vec![Rule::new(
1216 PacketMatcher::default(),
1217 Action::Jump(UninstalledRoutine::new(
1218 vec![Rule::new(PacketMatcher::default(), Action::Drop)],
1219 0,
1220 )),
1221 )],
1222 };
1223 assert_eq!(
1224 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1225 &routine,
1226 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1227 Interfaces { ingress: None, egress: None },
1228 &mut FakePacketMetadata::default(),
1229 ),
1230 RoutineResult::Drop
1231 );
1232
1233 let routine = Routine {
1236 rules: vec![
1237 Rule::new(
1238 PacketMatcher::default(),
1239 Action::Jump(UninstalledRoutine::new(
1240 vec![Rule::new(PacketMatcher::default(), Action::Accept)],
1241 0,
1242 )),
1243 ),
1244 Rule::new(PacketMatcher::default(), Action::Drop),
1245 ],
1246 };
1247 assert_eq!(
1248 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1249 &routine,
1250 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1251 Interfaces { ingress: None, egress: None },
1252 &mut FakePacketMetadata::default(),
1253 ),
1254 RoutineResult::Accept
1255 );
1256
1257 let routine = Routine {
1260 rules: vec![
1261 Rule::new(
1262 PacketMatcher::default(),
1263 Action::Jump(UninstalledRoutine::new(
1264 vec![Rule::new(PacketMatcher::default(), Action::Return)],
1265 0,
1266 )),
1267 ),
1268 Rule::new(PacketMatcher::default(), Action::Drop),
1269 ],
1270 };
1271 assert_eq!(
1272 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1273 &routine,
1274 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1275 Interfaces { ingress: None, egress: None },
1276 &mut FakePacketMetadata::default(),
1277 ),
1278 RoutineResult::Drop
1279 );
1280 }
1281
1282 #[test]
1283 fn return_terminal_for_single_routine() {
1284 let routine = Routine {
1285 rules: vec![
1286 Rule::new(PacketMatcher::default(), Action::Return),
1287 Rule::new(PacketMatcher::default(), Action::Drop),
1289 ],
1290 };
1291
1292 assert_eq!(
1293 check_routine::<Ipv4, _, FakeMatcherDeviceId, FakeBindingsCtx<Ipv4>, _>(
1294 &routine,
1295 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1296 Interfaces { ingress: None, egress: None },
1297 &mut FakePacketMetadata::default(),
1298 ),
1299 RoutineResult::Return
1300 );
1301 }
1302
1303 #[ip_test(I)]
1304 fn filter_handler_implements_ip_hooks_correctly<I: TestIpExt>() {
1305 fn drop_all_traffic<I: TestIpExt>(
1306 matcher: PacketMatcher<I, FakeBindingsCtx<I>>,
1307 ) -> Hook<I, FakeBindingsCtx<I>, ()> {
1308 Hook { routines: vec![Routine { rules: vec![Rule::new(matcher, Action::Drop)] }] }
1309 }
1310
1311 let mut bindings_ctx = FakeBindingsCtx::new();
1312
1313 let mut ctx = FakeCtx::with_ip_routines(
1316 &mut bindings_ctx,
1317 IpRoutines {
1318 ingress: drop_all_traffic(PacketMatcher {
1319 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1320 ..Default::default()
1321 }),
1322 ..Default::default()
1323 },
1324 );
1325 assert_eq!(
1326 FilterImpl(&mut ctx).ingress_hook(
1327 &mut bindings_ctx,
1328 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1329 &FakeMatcherDeviceId::wlan_interface(),
1330 &mut FakePacketMetadata::default(),
1331 ),
1332 Verdict::Stop(IngressStopReason::Drop)
1333 );
1334
1335 let mut ctx = FakeCtx::with_ip_routines(
1338 &mut bindings_ctx,
1339 IpRoutines {
1340 local_ingress: drop_all_traffic(PacketMatcher {
1341 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1342 ..Default::default()
1343 }),
1344 ..Default::default()
1345 },
1346 );
1347 assert_eq!(
1348 FilterImpl(&mut ctx).local_ingress_hook(
1349 &mut bindings_ctx,
1350 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1351 &FakeMatcherDeviceId::wlan_interface(),
1352 &mut FakePacketMetadata::default(),
1353 ),
1354 Verdict::Stop(DropOrReject::Drop)
1355 );
1356
1357 let mut ctx = FakeCtx::with_ip_routines(
1360 &mut bindings_ctx,
1361 IpRoutines {
1362 forwarding: drop_all_traffic(PacketMatcher {
1363 in_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1364 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Ethernet)),
1365 ..Default::default()
1366 }),
1367 ..Default::default()
1368 },
1369 );
1370 assert_eq!(
1371 FilterImpl(&mut ctx).forwarding_hook(
1372 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1373 &FakeMatcherDeviceId::wlan_interface(),
1374 &FakeMatcherDeviceId::ethernet_interface(),
1375 &mut FakePacketMetadata::default(),
1376 ),
1377 Verdict::Stop(DropOrReject::Drop)
1378 );
1379
1380 let mut ctx = FakeCtx::with_ip_routines(
1383 &mut bindings_ctx,
1384 IpRoutines {
1385 local_egress: drop_all_traffic(PacketMatcher {
1386 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1387 ..Default::default()
1388 }),
1389 ..Default::default()
1390 },
1391 );
1392 assert_eq!(
1393 FilterImpl(&mut ctx).local_egress_hook(
1394 &mut bindings_ctx,
1395 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1396 &FakeMatcherDeviceId::wlan_interface(),
1397 &mut FakePacketMetadata::default(),
1398 ),
1399 Verdict::Stop(DropOrReject::Drop)
1400 );
1401
1402 let mut ctx = FakeCtx::with_ip_routines(
1405 &mut bindings_ctx,
1406 IpRoutines {
1407 egress: drop_all_traffic(PacketMatcher {
1408 out_interface: Some(InterfaceMatcher::DeviceClass(FakeDeviceClass::Wlan)),
1409 ..Default::default()
1410 }),
1411 ..Default::default()
1412 },
1413 );
1414 assert_eq!(
1415 FilterImpl(&mut ctx)
1416 .egress_hook(
1417 &mut bindings_ctx,
1418 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1419 &FakeMatcherDeviceId::wlan_interface(),
1420 &mut FakePacketMetadata::default(),
1421 )
1422 .0,
1423 Verdict::Stop(DropPacket)
1424 );
1425 }
1426
1427 #[ip_test(I)]
1428 #[test_case(22 => Verdict::Proceed(Accept); "port 22 allowed for SSH")]
1429 #[test_case(80 => Verdict::Proceed(Accept); "port 80 allowed for HTTP")]
1430 #[test_case(1024 => Verdict::Proceed(Accept); "ephemeral port 1024 allowed")]
1431 #[test_case(65535 => Verdict::Proceed(Accept); "ephemeral port 65535 allowed")]
1432 #[test_case(1023 => Verdict::Stop(DropOrReject::Drop); "privileged port 1023 blocked")]
1433 #[test_case(53 => Verdict::Stop(DropOrReject::Drop); "privileged port 53 blocked")]
1434 fn block_privileged_ports_except_ssh_http<I: TestIpExt>(port: u16) -> Verdict<DropOrReject> {
1435 fn tcp_port_rule<I: FilterIpExt>(
1436 src_port: Option<PortMatcher>,
1437 dst_port: Option<PortMatcher>,
1438 action: Action<I, FakeBindingsCtx<I>, ()>,
1439 ) -> Rule<I, FakeBindingsCtx<I>, ()> {
1440 Rule::new(
1441 PacketMatcher {
1442 transport_protocol: Some(TransportProtocolMatcher {
1443 proto: <&FakeTcpSegment as TransportPacketExt<I>>::proto().unwrap(),
1444 src_port,
1445 dst_port,
1446 }),
1447 ..Default::default()
1448 },
1449 action,
1450 )
1451 }
1452
1453 fn default_filter_rules<I: FilterIpExt>() -> Routine<I, FakeBindingsCtx<I>, ()> {
1454 Routine {
1455 rules: vec![
1456 tcp_port_rule(
1458 None,
1459 Some(PortMatcher { range: 22..=22, invert: false }),
1460 Action::Accept,
1461 ),
1462 tcp_port_rule(
1464 None,
1465 Some(PortMatcher { range: 80..=80, invert: false }),
1466 Action::Accept,
1467 ),
1468 tcp_port_rule(
1470 None,
1471 Some(PortMatcher { range: 1024..=65535, invert: false }),
1472 Action::Accept,
1473 ),
1474 tcp_port_rule(
1476 None,
1477 Some(PortMatcher { range: 1..=65535, invert: false }),
1478 Action::Drop,
1479 ),
1480 ],
1481 }
1482 }
1483
1484 let mut bindings_ctx = FakeBindingsCtx::new();
1485
1486 let mut ctx = FakeCtx::with_ip_routines(
1487 &mut bindings_ctx,
1488 IpRoutines {
1489 local_ingress: Hook { routines: vec![default_filter_rules()] },
1490 ..Default::default()
1491 },
1492 );
1493
1494 FilterImpl(&mut ctx).local_ingress_hook(
1495 &mut bindings_ctx,
1496 &mut FakeIpPacket::<I, _> {
1497 body: FakeTcpSegment {
1498 dst_port: port,
1499 src_port: 11111,
1500 segment: SegmentHeader::arbitrary_value(),
1501 payload_len: 8888,
1502 },
1503 ..ArbitraryValue::arbitrary_value()
1504 },
1505 &FakeMatcherDeviceId::wlan_interface(),
1506 &mut FakePacketMetadata::default(),
1507 )
1508 }
1509
1510 #[ip_test(I)]
1511 #[test_case(
1512 FakeMatcherDeviceId::ethernet_interface() => Verdict::Proceed(Accept);
1513 "allow incoming traffic on ethernet interface"
1514 )]
1515 #[test_case(
1516 FakeMatcherDeviceId::wlan_interface() => Verdict::Stop(DropOrReject::Drop);
1517 "drop incoming traffic on wlan interface"
1518 )]
1519 fn filter_on_wlan_only<I: TestIpExt>(interface: FakeMatcherDeviceId) -> Verdict<DropOrReject> {
1520 fn drop_wlan_traffic<I: IpExt>() -> Routine<I, FakeBindingsCtx<I>, ()> {
1521 Routine {
1522 rules: vec![Rule::new(
1523 PacketMatcher {
1524 in_interface: Some(InterfaceMatcher::Id(
1525 FakeMatcherDeviceId::wlan_interface().id,
1526 )),
1527 ..Default::default()
1528 },
1529 Action::Drop,
1530 )],
1531 }
1532 }
1533
1534 let mut bindings_ctx = FakeBindingsCtx::new();
1535
1536 let mut ctx = FakeCtx::with_ip_routines(
1537 &mut bindings_ctx,
1538 IpRoutines {
1539 local_ingress: Hook { routines: vec![drop_wlan_traffic()] },
1540 ..Default::default()
1541 },
1542 );
1543
1544 FilterImpl(&mut ctx).local_ingress_hook(
1545 &mut bindings_ctx,
1546 &mut FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value(),
1547 &interface,
1548 &mut FakePacketMetadata::default(),
1549 )
1550 }
1551
1552 #[test]
1553 fn ingress_reuses_cached_connection_when_available() {
1554 let mut bindings_ctx = FakeBindingsCtx::new();
1555 let mut core_ctx = FakeCtx::new(&mut bindings_ctx);
1556
1557 let mut packet = FakeIpPacket::<Ipv4, FakeUdpPacket>::arbitrary_value();
1560 let mut metadata = PacketMetadata::default();
1561 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1562 &mut bindings_ctx,
1563 &mut packet,
1564 &FakeMatcherDeviceId::ethernet_interface(),
1565 &mut metadata,
1566 );
1567 assert_eq!(verdict, Verdict::Proceed(Accept));
1568
1569 let (stashed, _dir) =
1571 metadata.take_connection_and_direction().expect("metadata should include connection");
1572 let tuple = packet.conntrack_packet().expect("packet should be trackable").tuple();
1573 let table = core_ctx
1574 .conntrack()
1575 .get_connection(&tuple)
1576 .expect("packet should be inserted in table");
1577 assert_matches!(
1578 (table, stashed),
1579 (Connection::Shared(table), Connection::Shared(stashed)) => {
1580 assert!(Arc::ptr_eq(&table, &stashed));
1581 }
1582 );
1583
1584 let verdict = FilterImpl(&mut core_ctx).ingress_hook(
1587 &mut bindings_ctx,
1588 &mut packet,
1589 &FakeMatcherDeviceId::ethernet_interface(),
1590 &mut metadata,
1591 );
1592 assert_eq!(verdict, Verdict::Proceed(Accept));
1593
1594 let (after_ingress, _dir) =
1597 metadata.take_connection_and_direction().expect("metadata should include connection");
1598 let table = core_ctx
1599 .conntrack()
1600 .get_connection(&tuple)
1601 .expect("packet should be inserted in table");
1602 assert_matches!(
1603 (table, after_ingress),
1604 (Connection::Shared(before), Connection::Shared(after)) => {
1605 assert!(Arc::ptr_eq(&before, &after));
1606 }
1607 );
1608 }
1609
1610 #[ip_test(I)]
1611 fn drop_packet_on_finalize_connection_failure<I: TestIpExt>() {
1612 let mut bindings_ctx = FakeBindingsCtx::new();
1613 let mut ctx = FakeCtx::new(&mut bindings_ctx);
1614
1615 for i in 0..u32::try_from(conntrack::MAXIMUM_ENTRIES / 2).unwrap() {
1616 let (mut packet, mut reply_packet) = conntrack::testutils::make_test_udp_packets(i);
1617 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1618 &mut bindings_ctx,
1619 &mut packet,
1620 &FakeMatcherDeviceId::ethernet_interface(),
1621 &mut FakePacketMetadata::default(),
1622 );
1623 assert_eq!(verdict, Verdict::Proceed(Accept));
1624
1625 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1626 &mut bindings_ctx,
1627 &mut reply_packet,
1628 &FakeMatcherDeviceId::ethernet_interface(),
1629 &mut FakePacketMetadata::default(),
1630 );
1631 assert_eq!(verdict, Verdict::Proceed(Accept));
1632
1633 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1634 &mut bindings_ctx,
1635 &mut packet,
1636 &FakeMatcherDeviceId::ethernet_interface(),
1637 &mut FakePacketMetadata::default(),
1638 );
1639 assert_eq!(verdict, Verdict::Proceed(Accept));
1640 }
1641
1642 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1646 &mut bindings_ctx,
1647 &mut FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value(),
1648 &FakeMatcherDeviceId::ethernet_interface(),
1649 &mut FakePacketMetadata::default(),
1650 );
1651 assert_eq!(verdict, Verdict::Stop(DropPacket));
1652 }
1653
1654 #[ip_test(I)]
1655 fn implicit_snat_to_prevent_tuple_clash<I: TestIpExt>() {
1656 let mut bindings_ctx = FakeBindingsCtx::new();
1657 let mut ctx = FakeCtx::with_nat_routines_and_device_addrs(
1658 &mut bindings_ctx,
1659 NatRoutines {
1660 egress: Hook {
1661 routines: vec![Routine {
1662 rules: vec![Rule::new(
1663 PacketMatcher {
1664 src_address: Some(AddressMatcher {
1665 matcher: AddressMatcherType::Range(I::SRC_IP_2..=I::SRC_IP_2),
1666 invert: false,
1667 }),
1668 ..Default::default()
1669 },
1670 Action::Masquerade { src_port: None },
1671 )],
1672 }],
1673 },
1674 ..Default::default()
1675 },
1676 [(
1677 FakeMatcherDeviceId::ethernet_interface(),
1678 AddrSubnet::new(I::SRC_IP, I::SUBNET.prefix()).unwrap(),
1679 )],
1680 );
1681
1682 let mut packet = FakeIpPacket {
1685 src_ip: I::SRC_IP_2,
1686 dst_ip: I::DST_IP,
1687 body: FakeUdpPacket::arbitrary_value(),
1688 };
1689 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1690 &mut bindings_ctx,
1691 &mut packet,
1692 &FakeMatcherDeviceId::ethernet_interface(),
1693 &mut FakePacketMetadata::default(),
1694 );
1695 assert_eq!(verdict, Verdict::Proceed(Accept));
1696 assert_eq!(packet.src_ip, I::SRC_IP);
1697
1698 let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1705 let src_port = packet.body.src_port;
1706 let (verdict, _proof) = FilterImpl(&mut ctx).egress_hook(
1707 &mut bindings_ctx,
1708 &mut packet,
1709 &FakeMatcherDeviceId::ethernet_interface(),
1710 &mut FakePacketMetadata::default(),
1711 );
1712 assert_eq!(verdict, Verdict::Proceed(Accept));
1713 assert_ne!(packet.body.src_port, src_port);
1714 }
1715
1716 #[ip_test(I)]
1717 fn packet_adopts_tracked_connection_in_table_if_identical<I: TestIpExt>() {
1718 let mut bindings_ctx = FakeBindingsCtx::new();
1719 let mut core_ctx = FakeCtx::new(&mut bindings_ctx);
1720
1721 let mut first_packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1724 let mut first_metadata = PacketMetadata::default();
1725 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1726 &mut bindings_ctx,
1727 &mut first_packet,
1728 &FakeMatcherDeviceId::ethernet_interface(),
1729 &mut first_metadata,
1730 );
1731 assert_eq!(verdict, Verdict::Proceed(Accept));
1732
1733 let mut second_packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1734 let mut second_metadata = PacketMetadata::default();
1735 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1736 &mut bindings_ctx,
1737 &mut second_packet,
1738 &FakeMatcherDeviceId::ethernet_interface(),
1739 &mut second_metadata,
1740 );
1741 assert_eq!(verdict, Verdict::Proceed(Accept));
1742
1743 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1745 &mut bindings_ctx,
1746 &mut first_packet,
1747 &FakeMatcherDeviceId::ethernet_interface(),
1748 &mut first_metadata,
1749 );
1750 assert_eq!(verdict, Verdict::Proceed(Accept));
1751
1752 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1755 &mut bindings_ctx,
1756 &mut second_packet,
1757 &FakeMatcherDeviceId::ethernet_interface(),
1758 &mut second_metadata,
1759 );
1760 assert_eq!(second_packet.body.src_port, first_packet.body.src_port);
1761 assert_eq!(verdict, Verdict::Proceed(Accept));
1762
1763 let (first_conn, _dir) = first_metadata.take_connection_and_direction().unwrap();
1764 let (second_conn, _dir) = second_metadata.take_connection_and_direction().unwrap();
1765 assert_matches!(
1766 (first_conn, second_conn),
1767 (Connection::Shared(first), Connection::Shared(second)) => {
1768 assert!(Arc::ptr_eq(&first, &second));
1769 }
1770 );
1771 }
1772
1773 #[ip_test(I)]
1774 fn both_source_and_destination_nat_configured<I: TestIpExt>() {
1775 let mut bindings_ctx = FakeBindingsCtx::new();
1776 let mut core_ctx = FakeCtx::with_nat_routines_and_device_addrs(
1779 &mut bindings_ctx,
1780 NatRoutines {
1781 local_egress: Hook {
1782 routines: vec![Routine {
1783 rules: vec![Rule::new(
1784 PacketMatcher::default(),
1785 Action::Redirect { dst_port: None },
1786 )],
1787 }],
1788 },
1789 egress: Hook {
1790 routines: vec![Routine {
1791 rules: vec![Rule::new(
1792 PacketMatcher::default(),
1793 Action::Masquerade { src_port: None },
1794 )],
1795 }],
1796 },
1797 ..Default::default()
1798 },
1799 [(
1800 FakeMatcherDeviceId::ethernet_interface(),
1801 AddrSubnet::new(I::SRC_IP_2, I::SUBNET.prefix()).unwrap(),
1802 )],
1803 );
1804
1805 let mut packet = FakeIpPacket::<I, FakeUdpPacket>::arbitrary_value();
1808 let mut metadata = PacketMetadata::default();
1809 let verdict = FilterImpl(&mut core_ctx).local_egress_hook(
1810 &mut bindings_ctx,
1811 &mut packet,
1812 &FakeMatcherDeviceId::ethernet_interface(),
1813 &mut metadata,
1814 );
1815 assert_eq!(verdict, Verdict::Proceed(Accept));
1816 assert_eq!(packet.dst_ip, *I::LOOPBACK_ADDRESS);
1817
1818 let (verdict, _proof) = FilterImpl(&mut core_ctx).egress_hook(
1821 &mut bindings_ctx,
1822 &mut packet,
1823 &FakeMatcherDeviceId::ethernet_interface(),
1824 &mut metadata,
1825 );
1826 assert_eq!(verdict, Verdict::Proceed(Accept));
1827 assert_eq!(packet.src_ip, I::SRC_IP_2);
1828 }
1829
1830 #[ip_test(I)]
1831 #[test_case(
1832 Hook {
1833 routines: vec![
1834 Routine {
1835 rules: vec![
1836 Rule::new(
1837 PacketMatcher::default(),
1838 Action::Mark {
1839 domain: MarkDomain::Mark1,
1840 action: MarkAction::SetMark { clearing_mask: 0, mark: 1 },
1841 },
1842 ),
1843 Rule::new(PacketMatcher::default(), Action::Drop),
1844 ],
1845 },
1846 ],
1847 }; "non terminal for routine"
1848 )]
1849 #[test_case(
1850 Hook {
1851 routines: vec![
1852 Routine {
1853 rules: vec![Rule::new(
1854 PacketMatcher::default(),
1855 Action::Mark {
1856 domain: MarkDomain::Mark1,
1857 action: MarkAction::SetMark { clearing_mask: 0, mark: 1 },
1858 },
1859 )],
1860 },
1861 Routine {
1862 rules: vec![
1863 Rule::new(PacketMatcher::default(), Action::Drop),
1864 ],
1865 },
1866 ],
1867 }; "non terminal for hook"
1868 )]
1869 fn mark_action<I: TestIpExt>(ingress: Hook<I, FakeBindingsCtx<I>, ()>) {
1870 let mut metadata = PacketMetadata::<I, FakeWeakAddressId<I>, FakeBindingsCtx<I>>::default();
1871 assert_eq!(
1872 check_routines_for_hook::<I, _, FakeMatcherDeviceId, FakeBindingsCtx<I>, _, _>(
1873 &ingress,
1874 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1875 Interfaces { ingress: None, egress: None },
1876 &mut metadata,
1877 ),
1878 IngressVerdict::Stop(IngressStopReason::Drop),
1879 );
1880 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1)]));
1881 }
1882
1883 #[ip_test(I)]
1884 fn mark_action_applied_in_succession<I: TestIpExt>() {
1885 fn hook_with_single_mark_action<I: TestIpExt>(
1886 domain: MarkDomain,
1887 action: MarkAction,
1888 ) -> Hook<I, FakeBindingsCtx<I>, ()> {
1889 Hook {
1890 routines: vec![Routine {
1891 rules: vec![Rule::new(
1892 PacketMatcher::default(),
1893 Action::Mark { domain, action },
1894 )],
1895 }],
1896 }
1897 }
1898 let mut metadata = PacketMetadata::<I, FakeWeakAddressId<I>, FakeBindingsCtx<I>>::default();
1899 assert_eq!(
1900 check_routines_for_hook::<I, _, FakeMatcherDeviceId, FakeBindingsCtx<I>, _, _>(
1901 &hook_with_single_mark_action(
1902 MarkDomain::Mark1,
1903 MarkAction::SetMark { clearing_mask: 0, mark: 1 }
1904 ),
1905 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1906 Interfaces { ingress: None, egress: None },
1907 &mut metadata,
1908 ),
1909 IngressVerdict::Proceed(Accept),
1910 );
1911 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1)]));
1912
1913 assert_eq!(
1914 check_routines_for_hook(
1915 &hook_with_single_mark_action::<I>(
1916 MarkDomain::Mark2,
1917 MarkAction::SetMark { clearing_mask: 0, mark: 1 }
1918 ),
1919 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1920 Interfaces::<FakeMatcherDeviceId> { ingress: None, egress: None },
1921 &mut metadata,
1922 ),
1923 IngressVerdict::Proceed(Accept)
1924 );
1925 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 1), (MarkDomain::Mark2, 1)]));
1926
1927 assert_eq!(
1928 check_routines_for_hook(
1929 &hook_with_single_mark_action::<I>(
1930 MarkDomain::Mark1,
1931 MarkAction::SetMark { clearing_mask: 1, mark: 2 }
1932 ),
1933 &FakeIpPacket::<_, FakeTcpSegment>::arbitrary_value(),
1934 Interfaces::<FakeMatcherDeviceId> { ingress: None, egress: None },
1935 &mut metadata,
1936 ),
1937 IngressVerdict::Proceed(Accept)
1938 );
1939 assert_eq!(metadata.marks, Marks::new([(MarkDomain::Mark1, 2), (MarkDomain::Mark2, 1)]));
1940 }
1941
1942 #[ip_test(I)]
1944 fn transparent_proxy_drop_on_port_0<I: TestIpExt>() {
1945 let ingress = Hook {
1946 routines: vec![Routine {
1947 rules: vec![Rule::new(
1948 PacketMatcher::default(),
1949 Action::TransparentProxy(TransparentProxy::LocalAddr(I::DST_IP)),
1950 )],
1951 }],
1952 };
1953
1954 let packet = FakeIpPacket::<I, FakeTcpSegment> {
1955 body: FakeTcpSegment {
1956 dst_port: 0,
1957 src_port: 11111,
1958 segment: SegmentHeader::arbitrary_value(),
1959 payload_len: 0,
1960 },
1961 ..FakeIpPacket::<I, FakeTcpSegment>::arbitrary_value()
1962 };
1963
1964 assert_eq!(
1965 check_routines_for_hook::<
1966 I,
1967 _,
1968 FakeMatcherDeviceId,
1969 FakeBindingsCtx<I>,
1970 _,
1971 IngressStopReason<I>,
1972 >(
1973 &ingress,
1974 &packet,
1975 Interfaces { ingress: None, egress: None },
1976 &mut FakePacketMetadata::default(),
1977 ),
1978 IngressVerdict::Stop(IngressStopReason::Drop),
1979 );
1980 }
1981}