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