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