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